John Ding
John Ding

Reputation: 73

main() sometimes keeps frame pointer with -fomit-frame-pointer on x86

I accidentally discovered some strange thing about the -fomit-frame-pointer with GCC on x86 when I was doing homework.
Look at the following code (which seems quite nonsense but somehow related to how I discovered the problem)

#include <stdio.h>

void foo(void);

int main()
{
    foo();
    return 0;
}

void foo()
{
    printf("0x%x\n", *(unsigned char *)main);
}

when compiled with -m64 -O1 flag (-fomit-frame-pointer enabled), the disassembly is like the following

0000000000400500 <foo>:
  400500:   48 83 ec 08             sub    $0x8,%rsp
  400504:   0f b6 35 14 00 00 00    movzbl 0x14(%rip),%esi        # 40051f <main>
  40050b:   bf c4 05 40 00          mov    $0x4005c4,%edi
  400510:   b8 00 00 00 00          mov    $0x0,%eax
  400515:   e8 c6 fe ff ff          callq  4003e0 <printf@plt>
  40051a:   48 83 c4 08             add    $0x8,%rsp
  40051e:   c3                      retq   

000000000040051f <main>:
  40051f:   48 83 ec 08             sub    $0x8,%rsp
  400523:   e8 d8 ff ff ff          callq  400500 <foo>
  400528:   b8 00 00 00 00          mov    $0x0,%eax
  40052d:   48 83 c4 08             add    $0x8,%rsp
  400531:   c3                      retq 

Everything looks fine because %rbp does not show up at all. However when the code is compiled with -m32 -O1 flag (starting from gcc 4.6 -fomit-frame-pointer becomes default and mine is GCC 4.8.2) or even use -fomit-frame-pointer explicitly, the disassembly is like the following.

08048400 <foo>:
 8048400:   83 ec 1c                sub    $0x1c,%esp
 8048403:   0f b6 05 1e 84 04 08    movzbl 0x804841e,%eax
 804840a:   89 44 24 04             mov    %eax,0x4(%esp)
 804840e:   c7 04 24 c0 84 04 08    movl   $0x80484c0,(%esp)
 8048415:   e8 b6 fe ff ff          call   80482d0 <printf@plt>
 804841a:   83 c4 1c                add    $0x1c,%esp
 804841d:   c3                      ret    

0804841e <main>:
 804841e:   55                      push   %ebp
 804841f:   89 e5                   mov    %esp,%ebp
 8048421:   83 e4 f0                and    $0xfffffff0,%esp
 8048424:   e8 d7 ff ff ff          call   8048400 <foo>
 8048429:   b8 00 00 00 00          mov    $0x0,%eax
 804842e:   c9                      leave  
 804842f:   c3                      ret    

The function foo looks quite the same in 32 bit and 64 bit. However, unlike the 64-bit one, the first two instructions of main are (notice that it is compiled with -fomit-frame-pointer):

push %ebp
mov %esp, %ebp

which resembles normal x86 code.
After several experiments I found that if main calls another function, the code will be like the one above, and if there is no function call in main, the code will resembles the 64-bit ones.

I know this question may seem strange but I'm just curious about why this difference exists between x86 and x86_64 code, and only exists with main() function.

Upvotes: 6

Views: 235

Answers (1)

gsg
gsg

Reputation: 9377

This is unrelated to -fomit-frame-pointer as far as I know - instead it is a result of stack alignment.

main needs to align the stack (with and $0xfffffff0, %esp) so that the functions that it calls get the alignment they expect. This destroys the old value of esp, which consequently has to be saved and restored so that the ret does the right thing. (When the ret is executed, esp must be pointing at the same location it was upon entry to main: that is, at the return address that was saved on the stack).

So esp has to be saved and restored: and why not to a callee-save register such as ebp? In fact ebp is a good choice because there is a dedicated instruction, leave, to perform the desired movl %ebp, %esp/popl %ebp at the end of main.

On x64 the stack can be expected to be aligned upon entry to main, so alignment and the resulting saving and restoring of esp is unnecessary.

Upvotes: 6

Related Questions