理解一些汇编语句的目的

我试图了解一些汇编代码并设法完成大部分汇编代码,除了几行。 我能够理解大部分内部发生的事情,但是我无法完全理解在代码的开头和结尾发生了什么(及其原因)。 有人可以对此有所了解吗?

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 ; ?? 

堆栈向下增长。 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可以正常返回。 (推送是神奇的,似乎能够在同一指令中加载和存储到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 ; ?? 

这主要是标准的function序言(以前的东西是主要的特殊function)。 这是一个堆栈框架(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 ; ?? 

这是function结局。 由于开头的奇怪堆栈对齐代码,因此更难理解。 尽管如此,我在弄清楚为什么堆栈的调整次数比次序更低时我会遇到一些麻烦。

很明显,这个特定的代码没有使用优化编译。 如果它在那里可能不会那么多,因为编译器可以看到,即使它没有在你的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]