离开 x86 NASM 汇编语言中的函数时,堆栈是否会自动弹出? [英] Does the stack automatically get popped when leaving a function in x86 NASM assembly language?

查看:29
本文介绍了离开 x86 NASM 汇编语言中的函数时,堆栈是否会自动弹出?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如让我们进入一个函数...

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 本质上是堆栈顶部"的地址.压入和弹出堆栈会更改此寄存器.因此,如果您更改此寄存器,您将更改下一次 pushpop 发生的位置.

所以上面的指令 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 位参数(跳过推送的 ebpeip).

在您的函数期间,堆栈可能如下所示:

+------------+ |"push" 减少 "esp"|<arg>||+------------+ <-- ebp+8 ||<prev eip>|v+------------+ <-- ebp+4|<prev ebp>|+------------+ <-- ebp|<当地人>|+------------+ <-- ebp-4|<当地人>|^+------------+ <-- ebp-8 ||... ||+------------+ <-- esp |pop"增加esp"

在函数结束时,会发生这种情况:

mov esp, ebp ;弹出"局部变量和其他所有被推送的东西弹出 ebp ;恢复前一个堆栈帧

最后:

ret ;本质上这是一个pop eip",所以程序执行得到;在call fn"之后转回指令

(PS:调用代码必须弹出传递给函数的参数,例如,在 call fn 之后执行 add esp, 4.)

我好久没学过汇编语言了,全凭记忆.我可能在一些细节上有些偏离,但我希望你能大致了解一下.

For instance lets enter into a function...

push ebp ;Saving ebp
mov ebp, esp ;Saving esp into ebp
sub esp, 4 ;Saving four bytes onto the stack

And exit out of function...

mov esp, ebp ;Restoring the saved value of esp
pop ebp ;Restoring the value of ebp from the stack

(Yes I know I can use enter and leave, but I like it better this way.)

My question is when esp is restored, does the four byte variable on the stack get popped or somehow magically disappears? I couldn't see how pop ebp wouldn't just pop the four bytes reserved (and most likely used) from the stack. To my eye if you pushed anything onto the stack during the function it would still be there when the pop ebp happens, and thus pop ebp would yield not the saved ebp, but what was on the top of the stack. Does changing the esp register just lop off the top of the stack when it's value is restored?

解决方案

"To my eye if you pushed anything onto the stack during the function it would still be there when the pop ebp happens[…]"

No, because immediately before the pop ebp instruction, you have this:

mov esp, ebp ;Restoring the saved value of esp

Remember that esp is essentially the address of the "top" of the stack. Pushing onto and popping from the stack changes this register. So if you change this register, you're changing the location where the next push or pop will happen.

So the above instruction, mov esp, ebp essentially resets the stack pointer to the location it had immediately after the initial push ebp. (That location was saved to the ebp register right through the mov ebp, esp instruction.)

That's why pop ebp will pop the right thing.

This does however assume that your function did not change the ebp register.

Update:

I am assuming a certain calling convention here, but let's make an example. Let's say we have a function that takes one 32-bit argument, which gets passed to the function via the call stack.

In order to call our function, we do this:

push eax      ; push argument on stack
call fn       ; call our function; this pushes `eip` onto the stack

The first thing fn does is to set up its own stack frame (and making sure that the previous one can be restored at the end):

push ebp      ; so we can later restore the previous stack frame
mov ebp, esp  ; initialize our own function's stack frame
sub esp, 8    ; make room for 8 bytes (for local variables)

The sub esp, 8 is like pushing 8 bytes onto the stack, only that nothing will be written to the memory locations; so we essentially end up with 8 uninitialized bytes; this is the the memory area our function can use for local variables. It can refer to these local variables via e.g. [ebp-4] or [ebp-8], and it can refer to its 32-bit argument via [ebp+8] (skipping over the pushed ebp and eip).

During your function, the stack might then look like this:

+------------+                         | "push" decreases "esp"
|  <arg>     |                         |
+------------+  <-- ebp+8              |
| <prev eip> |                         v
+------------+  <-- ebp+4
| <prev ebp> |
+------------+  <-- ebp
|  <locals>  |
+------------+  <-- ebp-4
|  <locals>  |                         ^
+------------+  <-- ebp-8              |
|  ...       |                         |
+------------+  <-- esp                | "pop" increases "esp"

At the end of the function, this will happen:

mov esp, ebp  ; "pop" over local variables and everything else that was pushed
pop ebp       ; restore previous stack frame

And finally:

ret           ; essentially this does a "pop eip" so program execution gets
              ; transferred back to instruction after the "call fn"

(PS: The calling code would have to pop the arguments passed to the function, e.g. by doing a add esp, 4 right after the call fn.)

I haven't done assembly language in a long time so this is all from memory. I might be off on some of the fine points, but I hope you get the general picture.

这篇关于离开 x86 NASM 汇编语言中的函数时,堆栈是否会自动弹出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆