在函数尾声混淆之前堆栈必须是干净的

Stack must be clean before function epilogue confusion

我正在学习 "Assembly Language Step-by-Step: Programming with Linux" by Jeff Dunteman 一书中的汇编语言,并且在书中发现了一个有趣的段落,我很可能误解了它,因此希望得到一些澄清:

"The stack must be clean before we destroy the stack frame and return control. This simply means that any temporary values that we may have pushed onto the stack during the program’s run must be gone. All that is left on the stack should be the caller’s EBP, EBX, ESI, and EDI values.

...

Once the stack is clean, to destroy the stack frame we must first pop the caller’s register values back into their registers, ensuring that the pops are in the correct order.

...

We restore the caller’s ESP by moving the value from EBP into ESP, and finally pop the caller’s EBP value off the stack."

考虑从 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 推送到堆栈上 space,据我所知,只有在我们重置 ESP 时才从堆栈中消失;然而,正如所引用的那样,该书指出在我们重置 ESP 之前 堆栈必须是干净的 。有人可以澄清我是否遗漏了什么吗?

注意指令:

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

和说明:

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

所以在这个序列之后堆栈再次干净

Visual Studio 2008 年的例子与本书并不矛盾。这本书涵盖了最复杂的呼叫案例。请参阅 x86-32 Calling Convention 作为交叉引用,用图片将其拼写出来。

在您的示例中,堆栈中没有保存调用者寄存器,因此没有要执行的 pop 指令。这是 "clean up" 的一部分,必须出现在本书所指的 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个字节。