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

Ofe*_*fey 15 c linux gcc x86-64 disassembly

我正在自学,编译器是如何工作的.我正在学习从小型64位Linux程序中读取GCC生成代码的反汇编.

我写了这个C程序:

#include <stdio.h>

int main()
{
    for(int i=0;i<10;i++){
        int k=0;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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]
Run Code Online (Sandbox Code Playgroud)

现在我有些疑惑.

  1. 那个NOP到底是什么,为什么会出现?(对准?)

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

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

    PS:如果我printffor循环中调用函数(如),为什么编译器会突然生成sub rsp,0x10?为什么它仍然使用rsp寄存器引用本地数据.我希望生成的代码能够引用本地堆栈数据rbp!

Som*_*ude 12

关于第二个问题,由于C99标准允许return 0main函数中没有显式,编译器将隐式添加它.请注意,这仅适用于该main功能,不适用于其他功能.

至于第三个问题,rbp寄存器充当帧指针.

最后是PS.被调用的函数可能是使用16bytes(0x10)来传递给函数的参数.减法是从堆栈中"移除"那些变量的.它可能是你作为参数传递的两个指针吗?

如果您正在认真学习编译器的工作方式,并且可能想要创建自己的编程器(这很有趣!:)),那么我建议您投资一些关于它的理论和实践的书籍.龙书是任何程序员书架的绝佳补充.

  • [什么是基本指针和堆栈指针?他们指出了什么?](http://stackoverflow.com/q/1395591/995714),[EBP帧指针寄存器的用途是什么?](http://stackoverflow.com/q/579262/995714 ) (3认同)
  • 疑案.也许"为什么我们不能有一个理性的投票数量"是一个元问题? (2认同)

Pau*_*vie 6

ret无法依赖的任何东西都是代码.解码为nop"无操作"

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

rbp寄存器具有bp"Base Pointer"的含义,指向当前函数的堆栈帧.函数调用通常会导致函数条目保存rbp并使用rspfor 的当前值rbp.获取/存储函数参数和局部变量是相对于rbp.


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

实际上,编译器会在堆栈上分配空间.但它并没有改变stackpointer.它可以做到这一点,因为功能不会调用其他功能.它只是使用了curent下方的空间sp(堆栈向下增长),它用于rbp访问i([rbp-0x8])和k([rbp-0x4]).


我必须添加以下注释:不调整sp局部变量的使用似乎不是中断安全,因此编译器依赖于硬件在发生中断时自动切换到系统堆栈.否则,出现的第一个中断会将指令指针推入堆栈并覆盖本地变量.

不调整RSP的情况下使用局部变量编译器中解决了中断问题

  • @Ofey:这里的任何答案都没有捕到的是NOP存在的原因.在大多数情况下,当它在一个函数之后(在这种情况下是`ret`之后),它被放置在那里,以便下一个函数在16字节边界上开始.获取NOP开头的地址(objdump中的0x4004fb)并添加NOP所需的5个字节.你得到0x400500.0x400500可以被16整除.这是出于性能原因(与缓存行相关)完成的.在你的objdump中你有一个函数出现在'main`之后.你会在函数中看到NOP来对齐像这样的循环好. (5认同)

use*_*803 6

  1. 是的,nop用于对齐.编译器对所需的不同填充长度使用不同的指令,因为他们知道现代CPU将预先取出并解码几条指令.

  2. 正如其他人所说,如果没有明确的return语句(参见C99 TC3中的 5.1.2.2.3 ),C99标准默认从main()返回0 ,因此不会引发警告.

  3. 64位系统V的Linux ABI保留该叶功能(即不调用任何其他函数的函数-和你的main()就是这样的)当前堆栈指针低于128字节的"红番区"可以使用局部变量和其他临时值而不必使用sub rsp/add rsp.所以rbp == rsp.

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