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

标签 c gcc assembly x86 x86-64

在做作业的时候无意中发现了x86上GCC的-fomit-frame-pointer的一些奇怪的东西。
看下面的代码(这看起来很废话,但在某种程度上与我发现问题的方式有关)

#include <stdio.h>

void foo(void);

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

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

当使用 -m64 -O1 标志(启用 -fomit-frame-pointer)编译时,反汇编如下所示

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-pointer 成为默认值,而我的是 GCC 4.8.2)甚至使用 -fomit-frame -pointer 明确地,反汇编如下。

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-pointer 编译的):

push %ebp
mov %esp, %ebp

类似于正常的 x86 代码。
经过几次实验我发现如果main调用另外一个函数,代码会像上面那个,如果main中没有函数调用,代码会像64位的。

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

最佳答案

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

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

所以 esp 必须保存和恢复:为什么不保存到被调用者保存寄存器,例如 ebp?事实上 ebp 是一个不错的选择,因为有一条专用指令 leave 来执行所需的 movl %ebp, %esp/popl %ebpmain 的末尾。

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

关于c - main() 有时会在 x86 上使用 -fomit-frame-pointer 保留帧指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20124426/

相关文章:

assembly - 与 Cortex-A53 相比,NEON 汇编代码在 Cortex-A72 上需要更多周期

sql - sqlite3 中的日期处理令人困惑。

c - 系统("clear");不工作,但系统 ("cls");作品

c++ - AIX 上的 std::locale::operator=(std::locale const&) 崩溃

assembly - 在哪里可以找到 x86_64 汇编指令列表?

java - 使用 ASM 或 Javassist 提高字段获取和设置性能

c - C 中的嵌套结构和指针

ios - 如何在 objective-c 中比较自定义 sqlite 排序规则中的字符?

gcc - linux/kfifo.h 文件中的 'typeof((fifo) + 1)' 是什么意思?

C++0x 编译器支持问题