我正在从"Assembly Language Step-by-Step: Programming with Linux" by Jeff Dunteman这本书中学习汇编语言,并且在书中遇到了一个我很可能会误解的有趣段落,因此会对以下内容有所了解:
“在我们销毁堆栈帧并返回控制之前,堆栈必须是干净的。这只是意味着在程序运行期间我们可能已经推入堆栈的任何临时值都必须消失。堆栈上剩下的所有值都应该消失。是来电者的EBP,EBX,ESI和EDI值。
...
一旦堆栈干净,为了破坏堆栈帧,我们必须首先将调用者的寄存器值弹回到它们的寄存器中,确保pops的顺序正确。
...
我们通过将值从EBP移动到ESP来恢复调用者的ESP,最后将调用者的EBP值从堆栈中弹出。“
考虑从Visual Studio 2008生成的以下代码:
int myFuncSum( int a, int b)
{
001B1020 push ebp
001B1021 mov ebp,esp
001B1023 push ecx <------------------
int c;
c = a + b;
001B1024 mov eax,dword ptr [ebp+8]
001B1027 add eax,dword ptr [ebp+0Ch]
001B102A mov dword ptr [ebp-4],eax
return c;
001B102D mov eax,dword ptr [ebp-4]
}
001B1030 mov esp,ebp
001B1032 pop ebp
001B1033 ret
ecx(指示)的值被推送到我的变量c的堆栈上,就我所知,当我们重置ESP时,它只是从堆栈中消失了;然而,正如引用的那样,该书指出在我们重置ESP之前,堆栈必须是干净的 。有人可以澄清我是否遗漏了什么?
答案 0 :(得分:1)
Visual Studio 2008中的示例与本书并不矛盾。这本书涵盖了最精细的电话案例。请参阅x86-32 Calling Convention作为交叉参考,并将其与图片拼写出来。
在您的示例中,堆栈上没有保存调用者寄存器,因此不会执行pop
指令。这是&#34;清理&#34;的一部分。必须在本书所指的mov esp, ebp
之前发生。更具体地说,假设被调用者正在为调用者保存si
和di
,那么函数的前奏和后置可能如下所示:
push ebp ; save base pointer
mov ebp, esp ; setup stack frame in base pointer
sub esp, 4 ; reserve 4 bytes of local data
push si ; save caller's registers
push di
; do some stuff, reference 32-bit local variable -4(%ebp), etc
; Use si and di for our own purposes...
; clean up
pop di ; do the stack clean up
pop si ; restoring the caller's values
mov esp, ebp ; restore the stack pointer
pop ebp
ret
在您的简单示例中,没有保存的调用者寄存器,因此最后不需要最终的pop
指令。
也许是因为它更简单或更快,编译器选择执行以下指令代替sub esp, 4
:
push ecx
但效果是一样的:为局部变量保留4个字节。
答案 1 :(得分:0)
注意说明:
push ebp
mov ebp,esp ; <<<<=== saves the stack base pointer
和说明:
mov esp,ebp ; <<<<<== restore the stack base pointer
pop ebp
因此,在此序列之后,堆栈再次清洁