提问者:小点点

main()有时在x86上使用-fomit-frame指针保留帧指针


我在做作业的时候无意中发现了x86上带有GCC的-fomit-frame指针的一些奇怪的事情。
看看下面的代码(这似乎很无稽之谈,但不知何故与我发现问题的方式有关)

#include <stdio.h>

void foo(void);

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

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

使用-m64-O1标志编译(启用-fomit-frame-指针)时,反汇编如下

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 

一切看起来都很好,因为%rbp根本没有显示出来。但是,当代码使用-m32-O1标志编译时(从gcc 4.6开始-fomit-frame-poer变为默认值,我的GCC4.8.2),甚至显式使用-fomit-frame-poer,反汇编如下所示。

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    

函数foo在32位和64位中看起来完全相同。但是,与64位不同的是,main的前两条指令是(注意它是使用-fomit-frame-poster编译的):

push %ebp
mov %esp, %ebp


经过几次实验,我发现如果main调用另一个函数,代码会像上面的一样,如果main中没有函数调用,代码会像64位的。

我知道这个问题可能看起来很奇怪,但我只是好奇为什么x86和x86_64代码之间存在这种差异,并且只存在于main()函数中。


共1个答案

匿名用户

据我所知,这与-fomit-frame-poster无关-相反,它是堆栈对齐的结果。

main需要对齐堆栈(使用和$0xfffffff0,%esp),以便它调用的函数得到它们期望的对齐。这会破坏esp的旧值,因此必须保存和恢复该值,以便ret做正确的事情。(当执行ret时,esp必须指向与进入main时相同的位置:即保存在堆栈上的返回地址)。

在x64上,堆栈可以在进入main时对齐,因此对齐以及由此产生的esp的保存和恢复是不必要的。