在函数尾声混淆之前堆栈必须是干净的
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
之前。所以更具体地说,假设被调用者正在为调用者保存 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个字节。
我正在学习 "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
之前。所以更具体地说,假设被调用者正在为调用者保存 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个字节。