Question

I have this test.c on my Ubuntu14.04 x86_64 system.

void foo(int a, long b, int c) {
}

int main() {
    foo(0x1, 0x2, 0x3);
}

I compiled this with gcc --no-stack-protector -g test.c -o test and got the assembly code with objdump -dS test -j .text

00000000004004ed <_Z3fooili>:
void foo(int a, long b, int c) {
  4004ed:       55                      push   %rbp
  4004ee:       48 89 e5                mov    %rsp,%rbp
  4004f1:       89 7d fc                mov    %edi,-0x4(%rbp)
  4004f4:       48 89 75 f0             mov    %rsi,-0x10(%rbp)
  4004f8:       89 55 f8                mov    %edx,-0x8(%rbp) // !!Attention here!!
}
  4004fb:       5d                      pop    %rbp
  4004fc:       c3                      retq   

00000000004004fd <main>:

int main() {
  4004fd:       55                      push   %rbp
  4004fe:       48 89 e5                mov    %rsp,%rbp
    foo(0x1, 0x2, 0x3);
  400501:       ba 03 00 00 00          mov    $0x3,%edx
  400506:       be 02 00 00 00          mov    $0x2,%esi
  40050b:       bf 01 00 00 00          mov    $0x1,%edi
  400510:       e8 d8 ff ff ff          callq  4004ed <_Z3fooili>
}
  400515:       b8 00 00 00 00          mov    $0x0,%eax
  40051a:       5d                      pop    %rbp
  40051b:       c3                      retq   
  40051c:       0f 1f 40 00             nopl   0x0(%rax)

I know that the function parameters should be pushed to stack from right to left in sequence. So I was expecting this

void foo(int a, long b, int c) {
      push   %rbp
      mov    %rsp,%rbp
      mov    %edi,-0x4(%rbp)
      mov    %rsi,-0x10(%rbp)
      mov    %edx,-0x14(%rbp) // c should be push on stack after b, not after a

But gcc seemed clever enough to push parameter c(0x3) right after a(0x1) to save the four bytes which should be reserved for data alignment of b(0x2). Can someone please explain this and show me some documentation on why gcc did this?

Was it helpful?

Solution

The parameters are passed in registers - edi, esi, edx (then rcx, r8, r9 and only then pushed on stack) - just what the Linux amd64 calling convention mandates.

What you see in your function is just how the compiler saves them upon entry when compiling with -O0, so they're in memory where a debugger can modify them. It is free to do it in any way it wants, and it cleverly does this space optimization.

The only reason it does this is that gcc -O0 always spills/reloads all C variables between C statements to support modifying variables and jumping between lines in a function with a debugger.

All this would be optimized out in release build in the end.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top