【发布时间】:2017-08-17 01:56:26
【问题描述】:
我正在自学编译器的工作原理。我通过阅读从小型 64 位 Linux 程序生成的 GCC 代码的反汇编来学习。
我写了这个C程序:
#include <stdio.h>
int main()
{
for(int i=0;i<10;i++){
int k=0;
}
}
使用 objdump 后我得到:
00000000004004d6 <main>:
4004d6: 55 push rbp
4004d7: 48 89 e5 mov rbp,rsp
4004da: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0
4004e1: eb 0b jmp 4004ee <main+0x18>
4004e3: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
4004ea: 83 45 f8 01 add DWORD PTR [rbp-0x8],0x1
4004ee: 83 7d f8 09 cmp DWORD PTR [rbp-0x8],0x9
4004f2: 7e ef jle 4004e3 <main+0xd>
4004f4: b8 00 00 00 00 mov eax,0x0
4004f9: 5d pop rbp
4004fa: c3 ret
4004fb: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
现在我有些怀疑了。
最后的 NOP 是什么,为什么会出现? (对齐?)
我正在使用
gcc -Wall <program.c>进行编译。为什么我没有收到警告control reaches end of non-void function?-
为什么编译器不使用
sub rsp,0x10在堆栈上分配空间?为什么不使用rbp寄存器来引用本地堆栈数据?PS:如果我在
for循环中调用了一个函数(比如printf),为什么编译器会突然生成sub rsp,0x10?为什么它仍然使用rsp寄存器引用本地数据。我希望生成的代码使用rbp! 引用本地堆栈数据!
【问题讨论】:
-
一个小提示:如果你想玩转编译器,请查看:godbolt.org
-
没有一个答案能真正说明为什么会出现 #3。您将在 Linux 64 位代码中找到它。编译器正在利用Red Zone。您可以在64-bit System V Linux ABI 找到这个红色区域的信息。在该文档的副本中查看第 3.2.2 节:堆栈帧。由于您的函数是叶函数(不调用其他函数),它可以利用低于当前 128 个字节的事实RSP 不会被信号处理等破坏。
-
因为 RSP 的当前值以下 128 字节(在叶函数中)是安全的,如果函数堆栈,则无需调整 RSP基于数据的数据可以容纳在这 128 个字节中。这就是您在此处看不到 RSP 调整的原因。一项观察。 Red Zone 在 32 位 Linux 中不存在,因此编译为 32 位代码应该会看到预期的行为。
-
至于为什么当你添加
test函数时编译器减去 16 个字节,尽管需要更少的字节与 64 位 Linux ABI 要求有关,在任何函数调用时(就像调用test)堆栈需要是16(并且可能是32字节对齐)。由于堆栈在调用点对齐,因此 CALL 本身会推送 8 字节的返回地址。这会使堆栈错位 8。push rbp减去另一个 8,使其再次对齐 16 字节。现在您需要局部变量数据。编译器通常会分配足够的字节来保持 16 字节对齐 -
减去 16 字节,当它到达对
test的调用时,堆栈仍然保持 16 字节对齐,一切都很好..
标签: c linux gcc x86-64 disassembly