编译器:了解从小程序生成的汇编代码

我正在研究编译器是如何工作的。 我正在通过阅读从小型64位Linux程序反汇编生成的代码来学习。

我写了这个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] 

现在我有些怀疑。

  1. NOP到底是什么,为什么呢? (对准?)

  2. 我在用gcc -Wall <program.c>编译。 为什么我没有得到警告control reaches end of non-void function

  3. 为什么编译器没有在sub rsp,0x10分配空间sub rsp,0x10 ? 为什么不使用rbp寄存器来引用本地堆栈数据?

    PS:如果我在for循环中调用函数(如printf ),为什么编译器突然生成sub rsp,0x10 ? 为什么它仍然使用rsp寄存器引用本地数据。 我期望生成的代码引用本地堆栈数据与rbp

Solutions Collecting From Web of "编译器:了解从小程序生成的汇编代码"

关于第二个问题,由于C99标准允许在main函数中没有显式return 0 ,因此编译器会隐式地添加它。 请注意,这只是main功能,没有其他功能。

至于第三个问题, rbp寄存器作为帧指针

最后是PS。 被调用函数可能使用16个字节( 0x10 )作为传递给函数的参数。 减法就是从堆栈中“移除”这些变量。 它可能是两个指针,你作为参数传递?

如果你认真学习编译器是如何工作的,并且可能想创建自己的(这很有趣!:)),那么我建议你投入一些关于它的理论和实践的书籍。 龙书是任何程序员书架上的一个很好的补充。

  1. 是的,nop是为了对齐。 编译器使用不同的指令来填充所需的不同填充长度,因为现在的CPU将会提前获取并解码几条指令。

  2. 正如其他人所说,如果没有显式的返回语句(参见C99 TC3中的 5.1.2.2.3),C99标准默认从main()返回0,所以不会产生警告。

  3. 64位System V Linux ABI在当前的堆栈指针下面保留一个128字节的“红色区域”,这些指针指向叶函数(不调用任何其他函数的函数,而main()就是这样的函数)可用于局部变量其他临时值无需sub rsp / add rsp。 等rbp == rsp。

对于PS:在for()循环(或main())中的任何位置调用函数时,main()不再是叶函数,因此编译器不能再使用红色区域。 这就是为什么它使用sub rsp,0x10在堆栈上分配空间。 但是,它知道rsp和rbp之间的关系,因此它可以在访问数据时使用。

ret之后的任何东西都不能被依赖为代码。 解码为nop意味着“无操作”

第二点是编译器检测到你离开main函数而没有返回一个值,并且它插入一个return 0return 0 (仅为main定义)。

rbp寄存器, bp意思是“Base Pointer”,指向currect函数的堆栈帧。 函数调用通常会导致函数入口保存rbp并使用rbp的当前值rbp 。 获取/存储函数参数和局部变量是相对于rbp完成的。


我认为你的第三个问题需要更多的注意,“ 为什么编译器没有使用sub rsp,0x10分配堆栈空间?为什么不使用rbp寄存器来引用本地堆栈数据呢?

实际上,编译器确实在堆栈上分配空间。 但是它不会改变堆栈指针。 它可以这样做,因为functon没有其他功能。 它只是使用curent sp (堆栈增长)下面的空间,它使用rbp来访问i[rbp-0x8] )和k[rbp-0x4] )。


我必须添加下面的注意事项:不调整sp使用局部变量似乎不是中断安全的 ,所以编译器依靠硬件在中断发生时自动切换到系统堆栈。 否则,出现的第一个中断会将指针指向堆栈并覆盖局部变量。

编译器中使用局部变量 解决中断问题, 而不调整RSP