C 编译器:理解小程序生成的汇编代码
我正在自学编译器的工作原理。我通过阅读小型64位Linux程序中GCC生成代码的反汇编来学习 我写了这个C程序:C 编译器:理解小程序生成的汇编代码,c,linux,gcc,x86-64,disassembly,C,Linux,Gcc,X86 64,Disassembly,我正在自学编译器的工作原理。我通过阅读小型64位Linux程序中GCC生成代码的反汇编来学习 我写了这个C程序: #包括 int main() { 对于(int i=0;i关于第二个问题,由于C99标准不允许在main函数中有显式返回0,编译器将隐式添加它。请注意,这仅适用于main函数,没有其他函数 至于第三个问题,rbp登记册充当第一个问题 最后是PS。被调用函数很可能正在使用16字节(0x10)作为传递给函数的参数。减法就是从堆栈中“删除”这些变量的方法。它可能是作为参数传递的两个指针吗
#包括
int main()
{
对于(int i=0;i关于第二个问题,由于C99标准不允许在main
函数中有显式返回0
,编译器将隐式添加它。请注意,这仅适用于main
函数,没有其他函数
至于第三个问题,rbp
登记册充当第一个问题
最后是PS。被调用函数很可能正在使用16
字节(0x10
)作为传递给函数的参数。减法就是从堆栈中“删除”这些变量的方法。它可能是作为参数传递的两个指针吗
如果您正在认真学习编译器的一般工作原理,并且可能希望创建自己的编译器(这很有趣!),那么我建议你投资一些关于it理论和实践的书籍。这是程序员书架上的一个极好的补充。在ret
之后的任何东西都不能被依赖为代码。解码为nop
意味着“无操作”
第二点是编译器检测到您离开main
函数而不返回值,并插入return0
(仅为main
定义)
rbp
寄存器,其中bp
表示“基指针”,指向currect函数的堆栈帧。函数调用通常会导致函数条目保存rbp
,并将当前值rsp
用于rbp
。获取/存储函数参数和局部变量是相对于rbp
完成的。
我认为您的第三个问题需要更多关注,“为什么编译器不在堆栈上用子rsp,0x10
分配空间?为什么不使用rbp寄存器引用本地堆栈数据?”
实际上,编译器确实在堆栈上分配了空间。但它不会更改堆栈指针。它可以这样做,因为函数不调用其他函数。它只使用当前sp
(堆栈变小)下的空间,并使用rbp
访问i
([rbp-0x8]
)和k
([rbp-0x4]
)。
我必须补充以下注意事项:不调整sp
以使用局部变量似乎不是中断安全的,因此编译器依赖硬件在中断发生时自动切换到系统堆栈。否则,出现的第一个中断会将指令指针推到堆栈上,并覆盖loc变量
中断问题解决于
是的,nop是用于对齐的。编译器使用不同的指令进行不同长度的填充,知道现代CPU将提前预取和解码多条指令
正如其他人所说,如果没有显式的return语句(请参见中的5.1.2.2.3),C99标准默认情况下会从main()返回0,因此不会发出警告
在当前堆栈指针下保留一个128字节的“红色区域”,叶函数(不调用任何其他函数的函数-main()就是这样一个函数)可以将其用于局部变量和其他临时值,而无需子rsp/添加rsp。因此rbp==rsp
对于PS:当您在for()循环(或main()中的任何位置)中调用函数时,main()不再是叶函数,因此编译器不能再使用红色区域。这就是它在堆栈上分配空间的原因,子rsp为0x10。但是,它知道rsp和rbp之间的关系,因此在访问数据时可以使用两者。加1/3整数arithmetic@Bathsheba我甚至没想到会有那么多!:)邓尼,也许吧“为什么我们不能有一个合理的投票数类型"这是一个元问题吗?因为我们不希望upvots从3变为2.99999997,所以有一点提示:如果你想玩编译器,请检查:没有一个答案真正抓住了#3出现的原因。你可以在Linux 64位代码中找到这一点。编译器正在利用。你可以在。中找到这个红色区域的信息文件回顾第3.2.2节:堆栈框架的py。由于您的函数是叶函数(不调用其他函数),它可以利用以下事实,即当前RSP下128字节不会被信号处理等破坏,因为RSP(叶函数中)的当前值下128字节如果基于堆栈的函数数据可以容纳128字节,则无需调整RSP。这就是为什么您在这里看不到调整的RSP。一个观察结果。在32位Linux中不存在红色区域,因此编译为32位代码时,您应该会看到您可能预期的行为。至于添加测试时为什么会出现问题ode>函数编译器减去16个字节,尽管需要更少的字节与64位Linux ABI要求有关,即在任何函数调用(如调用test
)时,堆栈需要16个字节(可能32个字节对齐)。由于堆栈在调用点对齐,因此调用本身将推送8字节的返回地址。这将使堆栈偏移8。push rbp
减去另一个8,使其再次对齐16字节。现在需要局部变量数据。编译器通常通过减去16字节来分配足够的字节以保持16字节的对齐,b当它到达调用test
时,堆栈保持16字节对齐,一切正常。在ret调用之后,编译器只抛出随机操作码?@Ofey:这里的任何答案都没有捕捉到NOP存在的原因。在大多数情况下,当它位于函数之后(在本例中为ret
之后),它已放置在那里,以便下一个f
#include <stdio.h>
int main()
{
for(int i=0;i<10;i++){
int k=0;
}
}
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]