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

时间:2017-03-24 07:58:57

标签: 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;
    }
}

使用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的堆栈上分配空间?为什么不使用rbp寄存器来引用本地堆栈数据?

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

3 个答案:

答案 0 :(得分:12)

关于第二个问题,由于C99标准允许sigmoid_cross_entropy_with_logits函数中没有明确的return 0,编译器将隐式添加它。请注意,这仅适用于main函数,不包含其他函数。

关于第三个问题,main注册表充当frame pointer

最后是PS。被调用的函数可能正在使用rbp字节(16)来传递给函数的参数。减法是&#34;删除&#34;来自堆栈的那些变量。它可能是你作为参数传递的两个指针吗?

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

答案 1 :(得分:6)

ret之后的任何内容都不能成为代码。解码为nop表示&#34;没有操作&#34;

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

rbp寄存器,bp含义&#34; Base Pointer&#34;,指向当前函数的堆栈帧。函数调用通常会导致函数条目保存rbp并使用rsp的当前值rbp。获取/存储函数参数和局部变量是相对于rbp完成的。

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

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

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

Compiler using local variables without adjusting RSP

解决了中断问题

答案 2 :(得分:6)

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

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

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

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