Saturday, January 5, 2008

x86 assembly generated by various GCC releases for a classic function

In this new article, I will have a look at the same C source as in my previous article, except that it will be in its own function instead of being inlined in main(). We will see that some oddities spotted out previously only occured because we were in the main() function.

All tests are compiled with the following GCC command-line:
gcc -S -O test.c


The C source file is:

int
function(int ac, char *av[])
{
char buf[16];

if (ac < 2)
return 0;
strcpy(buf, av[1]);
return 1;
}

int
main(int ac, char *av[])
{

function(ac, av);
return 0;
}

Expectation, GCC 2.8.1 and GCC 2.95.3


The expectation, GCC 2.8.1 and GCC 2.95.3 assembly versions are the same as in the previous article. The stack frames are therefore identical too.

GCC 3.4.6


Fortunately, the assembly code generated by GCC 3.4.6 is far less puzzling when the C source code stands in a mere function instead of main(). Actually, the code is nearly identical to the one generated by GCC 2.95.3. The stack pointer has been aligned in the main() function on 16 bytes boundary.

function:
pushl %ebp
movl %esp, %ebp
subl $24, %esp /* Alloc a 24 bytes buffer */
movl $0, %eax
cmpl $1, 8(%ebp)
jle .L1
subl $8, %esp /* Alloc an unused 8 bytes */
/* buffer */
movl 12(%ebp), %eax
pushl 4(%eax)
leal -24(%ebp), %eax /* The 24 bytes buffer is */
/* used for strcpy() */
pushl %eax
call strcpy
movl $1, %eax
.L1:
leave
ret


The corresponding stack frame, similar to the GCC 2.95.3 one but the entire 24 bytes buffer is provided to strcpy().

| av |
| ac |
| ret |
ebp-> | sebp |
|/ / / / | ^
| / / / /| |
|/ / / / | |
| / / / /| | buf, 24 bytes wide
|/ / / / | |
| / / / /| v
|\\\\\\\\| ^
|\\\\\\\\| v 8 unused bytes
| av[1] |
esp-> | &buf |


GCC 4.2.1


Astonishingly, GCC 4.2.1 does not keep with 16 bytes alignment, although it seemed to do so in the main() function.


function:
pushl %ebp
movl %esp, %ebp
subl $24, %esp /* Alloc a 24 bytes buffer */
movl $0, %eax
cmpl $1, 8(%ebp)
jle .L4
movl 12(%ebp), %edx
movl 4(%edx), %eax
movl %eax, 4(%esp) /* Fake push */
leal -16(%ebp), %eax /* A 16 bytes buffer is */
/* used for strcpy() */
movl %eax, (%esp) /* Fake push */
call strcpy
movl $1, %eax
.L4:
leave
ret


And now the stack frame:

| av |
| ac |
| ret |
ebp-> | sebp |
|/ / / / | ^ ^
| / / / /| | |
|/ / / / | | | buf, 16 bytes wide
| / / / /| | v
| av[1] | |
esp-> | &buf | v


The stack frame looks exactly like the expectation. One thing worth noting however is that GCC 4.2.1 always uses peculiar code for arguments storage. Instead of using the push instruction, it reserves space for arguments of further function calls in the same time as local variables are allocated. Arguments are the stored relative to %esp.

No comments: