为了理解下面发生了什么,我正在制作小型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
答案 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。