在做作业的时候无意中发现了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 %ebp
在 main
的末尾。
在 x64 上,堆栈可以预期在进入 main
时对齐,因此不需要对齐以及由此产生的 esp
的保存和恢复。
关于c - main() 有时会在 x86 上使用 -fomit-frame-pointer 保留帧指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20124426/