Category Archives: Linux

Analyzing C compiler options, ELF files & optimization on x86_64 architecture

Compiling a simple hello world C program and exploring some of the different compiler options and how they affect the ELF (Executable and Linkable Format) executable file. This will be done on an x86-64 architecture system.


#include <stdio.h>

int main()
  printf("Hello world!\n");

gcc options:

-g           # enable debugging information
-O0          # do not optimize
-fno-builtin # do not use builtin function optimizations

objdump options:

-f       # display header information for the entire file
-s       # display per-section summary information
-d       # disassemble sections containing code
--source # (implies -d) show source code, if available, along with disassembly

To find out which section headers in the executable contain our code, we will use the -d option:

objdump -d hello

The 3 disassembled sections we see are .init, .plt and .text.

To find out which section contains our string to be printed, we use:

objdump -s hello

If we search for the string “Hello”, we will see that it is inside of the .rodata section.

Contents of section .rodata:

400590 01000200 00000000 00000000 00000000 ................
4005a0 48656c6c 6f20776f 726c6421 0a00     Hello world!..

Now we will compare our original ELF file with new ones using different compiler options:

gcc -g -O0 -fno-builtin hello.c -o hello

ls -l hello
Size of the executable is 11000 bytes.

objdump --source hello

00000000004004f6 :

int main()
4004f6: 55             push %rbp
4004f7: 48 89 e5       mov %rsp,%rbp
printf("Hello world!\n");
4004fa: bf a0 05 40 00 mov $0x4005a0,%edi
4004ff: b8 00 00 00 00 mov $0x0,%eax
4004ff: e8 ec fe ff ff callq 4003f0 &lt;puts@plt&gt; 400504: b8 00 00 00 00 mov $0x0,%eax
400509: 5d             pop %rbp
40050a: c3             retq
40050b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

Adding/Removing compile options

1. Adding the -static option

gcc -g -O0 -fno-builtin -static hello.c -o hello-1

Size of ELF file has grown to 916088 bytes since the dynamic libraries are added to the binary file. This is good for portability since the binary does not require dependencies at runtime in order to run.

2. Removing -fno-builtin option

gcc -g -O0 hello.c -o hello-2

objdump --source hello-2

00000000004004f6 :

int main()
4004f6: 55             push %rbp
4004f7: 48 89 e5       mov %rsp,%rbp
printf("Hello world!\n");
4004fa: bf a0 05 40 00 mov $0x4005a0,%edi
<del datetime="2017-02-02T16:23:20+00:00">4004ff: b8 00 00 00 00 mov $0x0,%eax</del>
400504: e8 e7 fe ff ff callq 4003f0 &lt;printf@plt&gt; 400509: b8 00 00 00 00 mov $0x0,%eax
40050e: 5d             pop %rbp
40050f: c3             retq

Since built-in function optimization is not enabled, we can see one instruction has been removed from the ELF file – size is now 8528 bytes.

3. Removing -g option

gcc -O0 hello.c -o hello-3

00000000004004f6 :
4004f6: 55             push %rbp
4004f7: 48 89 e5       mov %rsp,%rbp
4004fa: bf a0 05 40 00 mov $0x4005a0,%edi
4004ff: e8 ec fe ff ff callq 4003f0 &lt;puts@plt&gt; 400504: b8 00 00 00 00 mov $0x0,%eax
400509: 5d             pop %rbp
40050a: c3             retq
40050b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

This compiles without attaching debugging information, and we cannot see the inline source code – size is now 917680 bytes.

4. Adding additional arguments to printf() function

To analyze the differences in registers used for every argument added, 10 sequential integer arguments were added to the printf() function:



int main()
printf("Hello World!, %d%d%d%d%d%d%d%d%d%d\n", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

gcc -O0 -g hello.c -o hello-4

objdump --source lab4-4

00000000004004f6 <main>:
#include <stdio.h>

int main()
  4004f6:       55                      push   %rbp
  4004f7:       48 89 e5                mov    %rsp,%rbp
    printf("Hello world!, %d%d%d%d%d%d%d%d%d%d\n", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  4004fa:       48 83 ec 08             sub    $0x8,%rsp
  4004fe:       6a 0a                   pushq  $0xa
  400500:       6a 09                   pushq  $0x9
  400502:       6a 08                   pushq  $0x8
  400504:       6a 07                   pushq  $0x7
  400506:       6a 06                   pushq  $0x6
  400508:       41 b9 05 00 00 00       mov    $0x5,%r9d
  40050e:       41 b8 04 00 00 00       mov    $0x4,%r8d
  400514:       b9 03 00 00 00          mov    $0x3,%ecx
  400519:       ba 02 00 00 00          mov    $0x2,%edx
  40051e:       be 01 00 00 00          mov    $0x1,%esi
  400523:       bf d0 05 40 00          mov    $0x4005d0,%edi
  400528:       b8 00 00 00 00          mov    $0x0,%eax
  40052d:       e8 be fe ff ff          callq  4003f0 <printf@plt>
  400532:       48 83 c4 30             add    $0x30,%rsp
  400536:       b8 00 00 00 00          mov    $0x0,%eax
  40053b:       c9                      leaveq 
  40053c:       c3                      retq   
  40053d:       0f 1f 00                nopl   (%rax)

From lines 21 to 15, you can see each argument being stored into registers with the mov) function up until it reaches the 6th argument where it uses pushq.

5. Move printf to new function and call function from main


#include <stdio.h>

void output()
    printf("Hello world!\n");

int main()
    return 0;

g++ -g -fno-builtin -O0 hello-5.c -o hello5

We can see a new line for main, where we call the new output function:

00000000004005cc <main>:
4005d0:       e8 e1 ff ff ff          callq  4005b6 <_Z6outputv>

And the output function:

00000000004005b6 <_Z6outputv>:
#include <stdio.h>

void output()
  4005b6:       55                      push   %rbp
  4005b7:       48 89 e5                mov    %rsp,%rbp
    printf("Hello world!\n");
  4005ba:       bf 70 06 40 00          mov    $0x400670,%edi
  4005bf:       b8 00 00 00 00          mov    $0x0,%eax
  4005c4:       e8 e7 fe ff ff          callq  4004b0 <printf@plt>
  4005c9:       90                      nop
  4005ca:       5d                      pop    %rbp
  4005cb:       c3                      retq

6. Add optimization O3

We previously compiled our original hello world program with the -O0 optimization.
From mangcc:
-O0 Reduce compilation time and make debugging produce the expected results. This is the default.
-O3 Optimize yet more. -O3 turns on all optimizations specified by -O2 and also turns on the -finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-loop-vectorize, -ftree-loop-distribute-patterns, -fsplit-paths -ftree-slp-vectorize, -fvect-cost-model, -ftree-partial-pre and -fipa-cp-clone options.

Set O3 optimization:
g++ -g -fno-builtin -O3 hello.c -o hello_O3

Our original binary file size was 11000 bytes, whereas hello_O3 is 11248.

Now we’ll compare the two ELF files and run a diff comparison:

readelf -h hello > hello_readelfh.txt
readelf -h hello6 > hello6_readelfh.txt
diff hello_readelfh.txt hello6_readelfh.txt

< hello_readelfh.txt
> hello6_readelfh.txt

<   Entry point address:               0x4004c0
>   Entry point address:               0x4004e0
<   Start of section headers:          8784 (bytes into file)
>   Start of section headers:          8944 (bytes into file)
<   Number of section headers:         35
<   Section header string table index: 32
>   Number of section headers:         36
>   Section header string table index: 33

The start of the section headers starts 160 bytes later than the O0 optimized binary, and there is one extra section header for O3.

Contributing to open source project – npm mysql packages.json

I chose the npm mysql package to contribute to. In this post, I will go over the steps I took for contributing a minor fix to an open source project, using a Linux terminal, Vi editor and Git.

Review Process

Looking at the package.json file, I noticed that the repository field URL did not contain an absolute URL path.  So I ran the file through the package-json-validator:


The validator also recommends to include the “keywords” and “bugs” fields.  I read over the official npm documentation to see how to properly write these fields in package.json.


"repository" :
  { "type" : "git",
    "url" : ""

Keywords: An array of strings to help developers find the package using ‘npm search’.


  "url" : "",
  "email" : ""

I can adjust the fields now to match these formats.

Adding my changes

  • Fork repository from
  • Clone project to my local workspace: git clone
  • I used Vim editor to edit the package.json file: vi packages.json
  • Adjust repository URL to:
    "repository": {
      "type": "git",
      "url": ""
  • Add change to git staging: git add package.json
  • I thought it might be a good idea to keep this change separate from the “keywords + bugs” fields change for the sake of having separate commit messages for each, so I committed this change first: git commit -m "replaced repository url with valid repository url and type"

  • Then back to editing package.json in vi to add the “keywords” and “bugs” fields:
"keywords": [
"bugs": "",

Since I only included the issues URL in the bugs field, I followed the suggestion in npm of having it as only a single string instead of an object.

  • Add change: git add package.json
  • Commit change: git commit -m "added keywords + bugs url"
  • View changes in commits before pushing to repo: git show

After running git show, I noticed the indentation was off (4 spaces instead of 2) on the “sql” keyword line.  I had already set my tabwidth setting to 2, but found out that this setting does not insert spaces in replacement of a tab character. To do this you have to set the shiftwidth and set expandtab. So I added these two lines to my ~/.vimrc file (as root user, or in Fedora /etc/vimrc):

:set shiftwidth=2
:set expandtab

I switched back to my regular user and opened the package.json file again in vi.  If I press enter now from the “mysql” line, vi now automatically inserts 2 spaces at the beginning of the line.  To double check this, after hitting enter you can backspace and notice the cursor now moves back one space instead of the tabwidth’s set number of spaces.

An alternate quick fix for this of course would be to use a regular expression substitution in vi on lines 13 to 23:

:13,23s/\t/  /g

This will replace all tab characters with two spaces from lines 13 to 23.

Continuing with my changes… now I save these changes and exit vi CTRL + ZZ

  • See if changes are correct: git diff package.json
  • Spaces look good now, so I can add my changes: git add package.json
  • Commit changes to git: git commit -m "fixed vi indent to 2 spaces for sql keyword"

Now looking at the last three commits I made, I figured since the latest commit was only a minor fix to the previous, I wanted to have the two as only one commit.

git log --max-count 3


You can do this with a “git rebase”:

git rebase --interactive HEAD~2

This opens the git rebase file:


Here I want to pick the “added keywords + bugs url” as the main commit, and squash the “fixed vi indent…” commit into the main commit.


Now I can hit Shift + ZZ to save my changes and exit git rebase.

Git now displays the combination of commit messages:


Since I want to only use the “added keywords + bugs url” commit message, I can delete the 2nd commit message.


Hit Shift + ZZ again to save the changes and exit.

Now the “fixed vi indent…” commit has been squashed with the “added keywords + bugs url” commit as one commit.


Now I can push to the remote repository:
git push

One thing to note, as mentioned in github regarding git rebase, if the commits you squashed locally were previously already pushed to your remote repository, you would have to run:

git push origin branch-name --force

Although you have to be careful when choosing to do a rebase on already pushed commits and make sure they have not been reviewed or used in any way.  Use –force only only for very recent commits.

Creating Pull Request

Create the pull request by clicking the ‘Create pull request’ button in GitHub, then write a description of the changes you’ve made and create the new pull request.


Pull Request Review

One of the collaborators were quick to respond within 3 minutes of the pull request.  It was noted that npm automatically populates these fields now so adjusting the packages.json file is not necessary.  The new npm also no longer uses the keywords array in the search and the validator URL is also valid as a shortcut URL for git.

So I ended up creating a new issue for the validator-tool and the maintainer confirmed that the validator is slightly outdated and said they would be looking into updating it when they had the chance.

List listening ports and applicationli

netstat -ano | egrep 'Proto|LISTEN'

  • -a Show the state of all sockets, including passive sockets used by server processes
  • -n Show network addresses as numbers
  • -o Show process Id

netstat -ano | grep 8080

Show which application is using that port:

netstat -anb | grep -A 1 "8080"

  • -b Display executable listening on port
  • grep
  • -A option is to display the preceding line after greps returned result

SED command line utility – split string (& grep)

SED is a useful Unix command line tool I use for simple text replacement.

Here’s an example to see the directories included in your Windows PATH environment variable.  (I use MinGW for a Unix-like Shell in Windows)

echo $PATH

displays all directories on one line separated by semicolons.

With sed, we can list each directory on its own line.

echo $PATH | sed 's/:/\n/g'

First we echo $PATH, then pipe the output to the sed command to do our string replacement. The /g modifier will substitute ALL matches, not just the first.

Now each directory is listed on a separate line.

Additionally, if you are looking for a specific directory, for example you want to see which SVN installation is included in your PATH, you can use the grep command.

echo $PATH | sed -e 's/:/\n/g' | grep -i 'svn'

Once again we pipe the output from sed and use grep to show only directories that include SVN. The -i option is to ignore case (if your folder name includes the text “Svn”, “SVN”, etc.)

Adding unversioned files from Linux command line

Add all unversioned files from the command line using regular expressions.

svn status | grep '^?' | cut -c9- | sed -rn 's/^(.*)$/\"\1\"/p' | xargs svn add

svn status | grep '^?'
lists only un-versioned files.

cut -c9-
grabs only filename and path by cutting up to the 9th column removing the ? symbol and whitespace

sed -rn 's/^(.*)$/\"\1\"/p'
surrounds the filename with “double quotes” in case there are any spaces or your command prompt has an issue with the slashes being used in the path.

xargs svn add
executes svn add for each file.