【问题标题】:Understanding the purpose of some assembly statements理解一些汇编语句的目的
【发布时间】:2010-11-19 18:39:54
【问题描述】:

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

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

【问题讨论】:

  • 您可能会发现画出堆栈上的位置很有帮助...
  • 只为 cmets 中的操作码编写英文名称对您没有多大帮助。尝试使它们更“高级”。而且,如果您真的想从反汇编中学习一些东西,请尝试使用人工代码,而不是编译器。
  • 通常开头和结尾的内容是通用堆栈设置代码。分配和对齐等。
  • 也许您应该在启用优化的情况下进行编译,这样很多不必要的/无法解释的东西可能会消失。
  • 恕我直言,汇编代码无法运行(因为奇怪的 %esp 算法)。在指令@0x80483c1 之后,随后的两个pops 和(缺失?)ret 分别处理not %ecx%ebp<returnaddress> 的值。我尝试自己组装代码,确实引发了分段错误......

标签: c assembly x86


【解决方案1】:

堆栈向下增长。 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 之前被加载为指向栈顶返回地址另一侧的东西的指针,所以由于 this 有一个 -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                  ; ??

这主要是标准的函数序言(之前的东西是 main 的特殊内容)。这是一个堆栈帧(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 中列出的数学运算,程序的最终结果也是相同的。对于实际做某事(有副作用或结果)的程序,有时更容易阅读轻微优化的代码(gcc 的 -O1 或 -0s 参数)。

对于不是main 的函数,读取编译器生成的程序集通常要容易得多。如果您想通过阅读来理解代码,那么请自己编写一个函数,该函数接受一些参数来产生结果或对全局变量起作用,您将能够更好地理解它。

另一件可能对您有所帮助的事情是让 gcc 为您生成汇编文件,而不是反汇编它们。 -S 标志告诉它生成这个(但不生成其他文件),并在最后用.s 命名程序集文件。这应该比反汇编版本更容易阅读。

【讨论】:

  • 感谢您的宝贵时间。非常详细的解释。正是我想要的。
  • Q1:为什么要先加 4 个字节再减去。为什么不只是lea (%esp), %ecx 然后pushl (%ecx)? Q2:是否有任何标志可以禁用这种对齐方式?(-fno-align-functions 不起作用)。
【解决方案2】:

不知道为什么编译器会做所有这些事情,但这是我可以破译的:

 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]

【讨论】:

    猜你喜欢
    • 2012-09-07
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 2017-06-13
    • 1970-01-01
    • 2012-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多