例如让我们进入一个函数...
push ebp ;保存 ebpmov ebp, esp ;将esp存入ebpsub esp, 4 ;将四个字节保存到堆栈中
然后退出函数...
mov esp, ebp ;恢复esp的保存值pop ebp ;从栈中恢复 ebp 的值
(是的,我知道我可以使用进入和离开,但我更喜欢这种方式.)
我的问题是,当 esp 恢复时,堆栈上的四字节变量是被弹出还是以某种方式神奇地消失了?我看不出 pop ebp 如何不只是从堆栈中弹出保留的(并且最有可能使用的)四个字节.在我看来,如果你在函数期间将任何东西压入堆栈,当 pop ebp 发生时它仍然存在,因此 pop ebp 不会产生保存的 ebp,而是堆栈顶部的东西.当esp寄存器的值被恢复时,更改esp寄存器是否会从栈顶跳出?
解决方案在我看来,如果你在函数期间将任何东西压入堆栈,当 pop ebp 发生时它仍然会在那里[…]"
不,因为在 pop ebp
指令之前,你有这个:
mov esp, ebp ;恢复esp的保存值
请记住,esp
本质上是堆栈顶部"的地址.压入和弹出堆栈会更改此寄存器.因此,如果您更改此寄存器,您将更改下一次 push
或 pop
发生的位置.
所以上面的指令 mov esp, ebp
实质上将堆栈指针重置到它在初始 push ebp
之后的位置.(该位置通过 mov ebp, esp
指令直接保存到 ebp
寄存器中.)
这就是为什么 pop ebp
会弹出正确的东西.
但这确实假设您的函数没有更改 ebp
寄存器.
更新:
我在这里假设一个特定的调用约定,但让我们举个例子.假设我们有一个接受一个 32 位参数的函数,该参数通过调用堆栈传递给函数.
为了调用我们的函数,我们这样做:
push eax ;压栈参数调用 fn ;调用我们的函数;这会将 `eip` 推入堆栈
fn
做的第一件事就是建立自己的栈帧(并确保最后能恢复之前的栈帧):
push ebp ;所以我们可以稍后恢复之前的堆栈帧移动 ebp, esp ;初始化我们自己函数的栈帧子 esp, 8 ;为 8 个字节腾出空间(用于局部变量)
sub esp, 8
就像将 8 个字节压入堆栈,只是不会将任何内容写入内存位置;所以我们基本上得到了 8 个未初始化的字节;这是我们的函数可以用于局部变量的内存区域.它可以通过例如引用这些局部变量.[ebp-4]
或 [ebp-8]
,它可以通过 [ebp+8]
引用它的 32 位参数(跳过推送的 ebp
和 eip
).
在您的函数期间,堆栈可能如下所示:
+------------+ |"push" 减少 "esp"|<arg>||+------------+ <-- ebp+8 ||<prev eip>|v+------------+
相关推荐
最新文章