我试图理解一些汇编代码,并设法完成大部分汇编代码,除了几行.我能够理解大部分内部发生的事情,但我无法完全理解代码开头和结尾发生了什么(以及为什么会发生).有人可以对此有所了解吗?
int main() {
int a, b;
a = 12;
b = 20;
b = a + 123;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
拆卸版本:
8048394:8d 4c 24 04 lea 0x4(%esp),%ecx ; ??
8048398:83 e4 f0 and $0xfffffff0,%esp ; ??
804839b:ff 71 fc pushl -0x4(%ecx) ; ??
804839e:55 push %ebp ; Store the Base pointer
804839f:89 e5 mov %esp,%ebp ; Initialize the Base pointer with the stack pointer
80483a1:51 push %ecx ; ??
80483a2:83 ec 4c sub $0x4c,%esp ; ??
80483a5:c7 45 f8 0c 00 00 00 movl $0xc,-0x8(%ebp) ; Move 12 into -0x8(%ebp)
80483ac:c7 45 f4 14 00 00 00 movl $0x14,-0xc(%ebp) ; Move 20 into -0xc(%ebp)
80483b3:8b 45 f8 mov -0x8(%ebp),%eax ; Move 12@-0x8(%ebp) into eax
80483b6:83 c0 7b add $0x7b,%eax ; Add 123 to 12@eax
80483b9:89 45 f4 mov %eax,-0xc(%ebp) ; Store the result into b@-0xc(%ebp)
80483bc:b8 00 00 00 00 mov $0x0,%eax ; Move 0 into eax
80483c1:83 c4 10 add $0x10,%esp ; ??
80483c4:59 pop %ecx ; ??
80483c5:5d pop %ebp ; ??
80483c6:8d 61 fc lea -0x4(%ecx),%esp ; ??
Run Code Online (Sandbox Code Playgroud)
nat*_*ose 30
堆栈向下增长.A push从堆栈指针(esp)中减去并pop添加到esp.你必须牢记这一点,以了解很多这一点.
8048394:8d 4c 24 04 lea 0x4(%esp),%ecx ; ??
Run Code Online (Sandbox Code Playgroud)
lea =加载有效地址
这将4字节的东西的地址保存到堆栈中.由于这是32位(4字节字)x86代码,这意味着堆栈中的第二项.由于这是函数的代码(在这种情况下为main),因此堆栈顶部的4个字节是返回地址.
8048398:83 e4 f0 and $0xfffffff0,%esp ; ??
Run Code Online (Sandbox Code Playgroud)
此代码确保堆栈对齐到16个字节.在此操作之后,esp将小于或等于此操作之前的值,因此堆栈可能会增长,从而保护可能已存在于堆栈中的任何内容.这有时是main为了防止使用未对齐的堆栈调用函数,这可能会导致事情变得非常慢(我认为16字节是x86上的缓存行宽,尽管4字节对齐在这里非常重要) .如果main有一个未对齐的堆栈,那么程序的其余部分也将如此.
804839b:ff 71 fc pushl -0x4(%ecx) ; ??
Run Code Online (Sandbox Code Playgroud)
由于ecx之前被加载为指向前一个堆栈顶部返回地址另一侧的东西的指针,所以因为它有一个-4索引,这指的是返回当前函数被推回的返回地址到堆栈的顶部,以便main可以正常返回.(推送是神奇的,似乎能够在同一指令中加载和存储到RAM中的不同位置).
804839e:55 push %ebp ; Store the Base pointer
804839f:89 e5 mov %esp,%ebp ; Initialize the Base pointer with the stack pointer
80483a1:51 push %ecx ; ??
80483a2:83 ec 4c sub $0x4c,%esp ; ??
Run Code Online (Sandbox Code Playgroud)
这主要是标准的功能序言(以前的东西是主要的特殊功能).这是一个堆栈框架(ebp和esp之间的区域),其中局部变量可以存在.推送ebp,以便可以在结尾(在当前函数的末尾)恢复旧的堆栈帧.
80483a5:c7 45 f8 0c 00 00 00 movl $0xc,-0x8(%ebp) ; Move 12 into -0x8(%ebp)
80483ac:c7 45 f4 14 00 00 00 movl $0x14,-0xc(%ebp) ; Move 20 into -0xc(%ebp)
80483b3:8b 45 f8 mov -0x8(%ebp),%eax ; Move 12@-0x8(%ebp) into eax
80483b6:83 c0 7b add $0x7b,%eax ; Add 123 to 12@eax
80483b9:89 45 f4 mov %eax,-0xc(%ebp) ; Store the result into b@-0xc(%ebp)
80483bc:b8 00 00 00 00 mov $0x0,%eax ; Move 0 into eax
Run Code Online (Sandbox Code Playgroud)
eax是存储整数函数返回值的地方.这是设置为从main返回0.
80483c1:83 c4 10 add $0x10,%esp ; ??
80483c4:59 pop %ecx ; ??
80483c5:5d pop %ebp ; ??
80483c6:8d 61 fc lea -0x4(%ecx),%esp ; ??
Run Code Online (Sandbox Code Playgroud)
这是功能结局.由于开头的奇怪堆栈对齐代码,因此更难理解.尽管如此,我在弄清楚为什么堆栈的调整次数比次序更低时我会遇到一些麻烦.
很明显,这个特定的代码没有使用优化编译.如果那里可能不会那么多,因为编译器可以看到,即使它没有在你main的程序的最终结果中列出的数学是相同的.对于实际执行某些操作(具有副作用或结果)的程序,有时更容易阅读轻微优化的代码(-cc或-0s参数到gcc).
对于没有的函数,编译器生成的读取汇编通常要容易得多main.如果你想阅读以理解代码,那么你自己写一个函数,它接受一些参数来产生一个结果或者对全局变量起作用,你将能够更好地理解它.
可能对你有帮助的另一件事就是让gcc为你生成汇编文件,而不是反汇编它们.该-S标志告诉它生成它(但不生成其他文件),并.s在末尾命名汇编文件.这比阅读反汇编版本更容易阅读.
不知道为什么编译器会完成所有这些工作,但这就是我能解读的内容:
8048394:8d 4c 24 04 lea 0x4(%esp),%ecx ; ecx := esp+4
8048398:83 e4 f0 and $0xfffffff0,%esp ; align the stack to 16 bytes
804839b:ff 71 fc pushl -0x4(%ecx) ; push [ecx-4] ([esp])
80483a1:51 push %ecx ; push ecx
80483a2:83 ec 4c sub $0x4c,%esp ; allocate 19 dwords on stack
80483c1:83 c4 10 add $0x10,%esp ; deallocate 4 dwords from stack
80483c4:59 pop %ecx ; restore ecx
80483c5:5d pop %ebp ; and ebp
80483c6:8d 61 fc lea -0x4(%ecx),%esp ; esp := [ecx-4]
Run Code Online (Sandbox Code Playgroud)