学习拆卸

时间:2011-01-10 19:35:34

标签: c gcc assembly disassembly

为了理解下面发生了什么,我正在制作小型C程序,然后将其反转,并尝试理解它的objdump输出。

C程序是:

#include <stdio.h>

int function(int a, int b, int c) {
    printf("%d, %d, %d\n", a,b,c);
}

int main() {
    int a;
    int *ptr;

    asm("nop");
    function(1,2,3);
}

函数的objdump输出给出了以下内容。

080483a4 <function>:
 80483a4:   55                      push   ebp
 80483a5:   89 e5                   mov    ebp,esp
 80483a7:   83 ec 08                sub    esp,0x8
 80483aa:   ff 75 10                push   DWORD PTR [ebp+16]
 80483ad:   ff 75 0c                push   DWORD PTR [ebp+12]
 80483b0:   ff 75 08                push   DWORD PTR [ebp+8]
 80483b3:   68 04 85 04 08          push   0x8048504
 80483b8:   e8 fb fe ff ff          call   80482b8 <printf@plt>
 80483bd:   83 c4 10                add    esp,0x10
 80483c0:   c9                      leave  

请注意,在调用printf之前,三个带有偏移量8,16,12的DWORD(它们必须是反向顺序的function的参数)被压入堆栈。之后会推送一个必须是格式字符串地址的十六进制地址。

My doubt is

  1. 我没有直接将3个DWORDS和格式说明符直接压入堆栈,而是希望看到esp被手动递减,然后将值推入堆栈。怎么能解释这种行为?

6 个答案:

答案 0 :(得分:5)

好吧,有些机器有一个堆栈指针,就像任何其他寄存器一样,所以推送东西的方式是,减少后跟商店。

但是有些机器,比如x86 32/64 有一个 push 指令执行宏操作:递减指针执行存储。

宏观操作,顺便说一句,有一个有趣的历史。有时,某些机器上的某些示例比使用简单指令执行基本操作要慢。

我怀疑今天是否经常出现这种情况。现代x86非常精致。 CPU会将您的操作码本身分解为微操作,然后将其存储在缓存中。微操作具有特定的流水线和时隙要求,最终结果是x86内部有一个RISC cpu,整个过程非常快具有良好的架构层代码密度

答案 1 :(得分:1)

使用push指令调整堆栈指针。所以它被复制到ebp并且参数被压入堆栈,因此它们分别存在于2个位置:function的堆栈和printf的堆栈。 push会影响esp,因此ebp会被复制。

答案 2 :(得分:1)

没有mov [esp + x],[ebp + y]指令,操作数太多。它需要两个指令并使用寄存器。推送在一条指令中完成。

答案 3 :(得分:1)

这是x86机器的标准cdecl调用约定。有几种不同类型的调用约定。您可以在维基百科中阅读以下文章:

http://en.wikipedia.org/wiki/X86_calling_conventions

它解释了基本原理。

答案 4 :(得分:0)

你提出了一个有趣的观点,我认为到目前为止还没有直接解决。我想你已经看到汇编代码看起来像这样:

sub esp, X
...
mov [ebp+Y], eax
call Z

这种反汇编是由某些编译器生成的。它正在做的是扩展堆栈,然后将新空间的值分配为eax(希望在那一点上填充有意义的东西)。这实际上等同于push助记符的作用。我无法回答为什么某些编译器会生成此代码,但我的猜测是,在某些时候这样做会被认为更有效率。

答案 5 :(得分:0)

在学习汇编语言和反汇编二进制文件的过程中,您可能会发现ODA很有用。它是一个基于Web的反汇编程序,可以方便地拆卸许多不同的体系结构,而无需为每个体系结构构建binutil的objdump。

http://onlinedisassembler.com/

相关问题