SPO 600 - Lab 2

In lab 2, I will be looking at how different compiler options would affect the executable. I will be using gcc in this lab, and this lab is running on x86_64 architecture.

First, let's start with a simple C hello world program:


#include <stdio.h>  
int main() {
 printf("Hello World!\n"); 
}

Next, I'll compile it with the option: gcc -g -O0 -fno-builtin -o hello hello.c 
(Note: -g enables the debugging option, -O0 stands for no optimization, and -fno-builtin stands for don't use built in function optimizations
If I run the executable by entering ./hello, it should give me:
Hello World!

Using objdump --source hello, I can find the <main> section contains the code I wrote:
And using objdump -s hello, I can also find where the string is stored (It's stored in .rodata, or read-only data segment):

Right now I'll make a couple changes to see how the option would affect the executable.

1. Add the -static option
When I recompile it as hello1, I noticed the hello1 is much, much bigger than hello!
Yes, they do the same thing, but hello1 is 102.261 KB, while the original hello is only 1.325 KB.




Also, in the hexdump of hello1 and the <main>, the function call for <printf> became <_IO_printf>. It is largely because of the -static option, as it actually copies all the functions into the assembly code. 


2. Remove the -fno-builtin flag (i.e. no built-in function optimization)
When I recompile it as hello2 and , the file size is pretty much the same, but when I open it with objdump, I noticed in the <main> section, the <printf@plt> is replaced <puts@plt>. 
According to the GCC documentation, “Many of these functions are only optimized in certain cases; if they are not optimized in a particular case, a call to the library function will be emitted.” Although both printf() and puts() are built-in functions, the former isn’t optimized for what I’m using it for. Since I’m not outputting any variables, GCC simply chooses puts() for me.

3. Remove the -g flag (i.e. No debugging information)
 When I recompile it as hello3, I noticed the file is much smaller (1.021KB), since it no longer contains any debugging information.

4. Add 10 arguments to the printf() function
When I recompile it as hello4 and open it with objdump, I noticed the 10 arguments are showing in <main>, and they’re stored in the following registers: %esi, %edx, %ecx, %r8d, %r9d (Note: they’re counting from back to front)

Just in case you’re wondering where are the remaining 5, they’re all stored in a stack. (That’s also why they’re using pushq instead of mov.)

5. Move the printf() inside a function called output()
When I recompile it as hello5 and open it with objdump, the <main> would call <output> and prints “Hello world!”.

Look closely at the callq in <main>, and you can see it's calling 4004d7, which is the register of <output>.

6.  Replace -O0 with -O3
When I recompile it as hello6 and open it with objdump, I noticed in the <main> section, the "mov $0x0,%eax" is replaced by "xor    %eax,%eax", which is a more efficient way to set %eax to 0. It also uses sub instead of push.
Furthermore, hello6 runs faster than the original hello.




Comments

Popular posts from this blog

SPO 600 Project - Stage 1

SPO 600 - Lab 1

SPO 600 - Lab 5