堆栈必须在函数结束语混淆之前清理干净

Stack must be clean before function epilogue confusion

本文关键字:函数 结束语 堆栈      更新时间:2023-10-16

我正在学习书中的汇编语言"汇编语言循序渐进:用Linux编程";作者:Jeff Dunteman,我在书中看到了一段有趣的话,我很可能误解了这段话,因此我希望能澄清一下:

"在销毁堆栈框架并返回控制之前,堆栈必须是干净的。这只是意味着,在程序运行期间,我们可能推送到堆栈上的任何临时值都必须消失。堆栈上剩下的应该是调用者的EBP、EBX、ESI和EDI值。

一旦堆栈清理干净,要销毁堆栈帧,我们必须首先将调用方的寄存器值弹出到它们的寄存器中,确保弹出的顺序正确。

我们通过将值从EBP移动到ESP来恢复调用者的ESP,并最终将调用者的EBP值从堆栈中弹出"

考虑以下从VisualStudio2008生成的代码:

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之前,堆栈必须是干净的。有人能澄清我是否遗漏了什么吗?

Visual Studio 2008中的示例与本书并不矛盾。这本书涵盖了一个电话中最复杂的案例。请参阅x86-32调用约定作为交叉参考,并用图片说明。

在您的示例中,堆栈上没有保存调用方寄存器,因此没有要执行的pop指令。这是本书所指的mov esp, ebp之前必须进行的"清理"的一部分。因此,更具体地说,假设被调用者为调用者保存sidi,那么函数的前奏和尾曲可能如下所示:

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个字节。

注意指令:

push        ebp  
mov         ebp,esp   ; <<<<=== saves the stack base pointer

和指令:

mov         esp,ebp   ;  <<<<<== restore the stack base pointer 
pop         ebp  

因此,在该序列之后,堆栈再次清洁