我正在尝试理解一些汇编代码并设法完成除了几行之外的大部分汇编代码。我能够理解大部分内部发生的事情,但我无法完全理解代码开头和结尾发生了什么(以及为什么会发生)。有人可以对此有所了解吗?
int main() {
int a, b;
a = 12;
b = 20;
b = a + 123;
return 0;
}
反汇编版本:
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 ; ??
答案 0 :(得分:29)
堆栈增长向下。 push
从堆栈指针(esp)中减去,pop
加到esp。你必须记住这一点,以了解很多这一点。
8048394:8d 4c 24 04 lea 0x4(%esp),%ecx ; ??
lea =加载有效地址
这将4字节的东西的地址保存到堆栈中。由于这是32位(4字节字)x86代码,这意味着堆栈中的第二项。由于这是函数的代码(在本例中为main),因此堆栈顶部的4个字节是返回地址。
8048398:83 e4 f0 and $0xfffffff0,%esp ; ??
此代码确保堆栈对齐到16个字节。在此操作之后,esp将小于或等于此操作之前的值,因此堆栈可能会增长,从而保护可能已存在于堆栈中的任何内容。这有时在main
中完成,以防函数使用未对齐的堆栈调用,这可能会导致事情变得非常慢(我认为16字节是x86上的缓存行宽,尽管4字节对齐是什么这里非常重要)。如果main有一个未对齐的堆栈,那么程序的其余部分也是如此。
804839b:ff 71 fc pushl -0x4(%ecx) ; ??
因为ecx之前被加载为指向堆栈前一个顶部的返回地址另一侧的东西的指针,所以因为它有一个-4索引,这指的是回到当前函数的返回地址被推回到堆栈的顶部,以便main可以正常返回。 (Push是魔术,似乎能够在同一指令中加载和存储到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 ; ??
这主要是标准的功能序言(之前的东西是主要的特殊功能)。这是一个堆栈框架(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
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 ; ??
这是功能结尾。由于开头的奇怪堆栈对齐代码,因此更难理解。尽管如此,我在弄清楚为什么堆栈的调整次数比次序更低时会有一点麻烦。
很明显,这个特定代码没有使用优化编译。如果它在那里可能不会那么多,因为编译器可以看到,即使它没有在你的main
中列出的数学,程序的最终结果是相同的。对于实际执行某些操作的程序(具有副作用或结果),有时更容易阅读轻度优化的代码(-cc或-0s参数为gcc)。
对于非main
的函数,读取编译器生成的汇编通常要容易得多。如果你想阅读以理解代码,那么自己写一个函数,它接受一些参数来产生一个结果或者对全局变量起作用,你将能够更好地理解它。
可能对你有帮助的另一件事就是让gcc为你生成汇编文件,而不是反汇编它们。 -S
标志告诉它生成它(但不生成其他文件),并在最后用.s
命名程序集文件。这比阅读反汇编版本更容易阅读。
答案 1 :(得分:6)
不确定为什么编译器会完成所有这些工作,但这就是我能解读的内容:
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]