堆栈框架和 returns

Stack frame and returns

所以我目前正在阅读 Practical reverse engineering_ x86, x64, ARM, Windows Kernel, reversing tools, and obfuscation 这本书包括以下示例来解释堆栈框架。

addme(x, y)

01: 004113A0 55 push ebp
02: 004113A1 8B EC mov ebp, esp
03: ...
04: 004113BE 0F BF 45 08 movsx eax, word ptr [ebp+8]
05: 004113C2 0F BF 4D 0C movsx ecx, word ptr [ebp+0Ch]
06: 004113C6 03 C1 add eax, ecx
07: ...
08: 004113CB 8B E5 mov esp, ebp
09: 004113CD 5D pop ebp
10: 004113CE C3 retn

函数调用

01: 004129F3 50 push eax ;param2
02: ...
03: 004129F8 51 push ecx ;param1
04: 004129F9 E8 F1 E7 FF FF call addme
05: 004129FE 83 C4 08 add esp, 8

我知道在 addme 函数的第 10 行我们做了 mov ebp, esp 来为当前函数启动一个新的堆栈帧,但我不明白的是为什么我们在 pop 之前调用 mov esp, ebp。如果我理解正确的话,这是调用 mov esp, ebp 之前堆栈的当前状态。

           TOP
**************************
*         param2         *
**************************
*         param1         *
**************************
*         return addrs   *
**************************
*         old edp        *
**************************
esp and edp are pointing after odl edp.

那么为什么要调用 mov esp, ebp

如果我提出问题的方式有问题,请告诉我。谢谢。

指令对

mov esp, ebp
pop ebp

用于恢复调用函数的栈帧。也可以通过单条指令完成

leave

但这可能比使用两条指令慢(参见 this post)。

但是mov esp, ebp真的有必要吗?

这取决于函数内完成的工作。编译器通常会添加这一行,因为它会从堆栈中删除局部变量。如果在函数中间使用 return ,堆栈上也可能有其他值,其中中间结果也可以在堆栈上。想象一下 return 是用这个堆栈赋值调用的:

+---------------------------+
|         param 2           |
+---------------------------+
|         param 1           |
+---------------------------+
| old eip (return address)  |
+---------------------------+
| old ebp (last stackframe) | <-- ebp
+---------------------------+
|     local variable 1      |
+---------------------------+
|     local variable 2      |
+---------------------------+
|       interim value       | <-- esp
+---------------------------+

如果寄存器不再足以存储所有值,则可能会出现临时值。并且 ebp 指向旧的 ebp,而不是之后的内存位置(参见 Does push esp update ESP before or after storing?