gpt4 book ai didi

assembly - 汇编中的栈帧是什么?

转载 作者:行者123 更新时间:2023-12-02 10:17:50 25 4
gpt4 key购买 nike

堆栈帧的结构是什么以及在汇编中调用函数时如何使用它?

最佳答案

每个例程都使用堆栈的一部分,我们将其称为堆栈帧。尽管汇编程序员并不被迫遵循以下风格,但强烈建议将其作为良好实践。

每个例程的堆栈帧分为三部分:函数参数、指向前一个堆栈帧的反向指针和局部变量。

第 1 部分:函数参数

例程堆栈帧的这一部分由调用者设置。使用“push”指令,调用者将参数压入堆栈。不同的语言可能会以不同的顺序推送参数。 C,如果我没记错的话,将它们从右推到左。也就是说,如果您打电话...

foo (a, b, c);

调用者会将其转换为...

push c
push b
push a
call foo

当每个项目被插入堆栈时,堆栈会向下增长。也就是说,堆栈指针寄存器递减四 (4) 个字节(在 32 位模式下),并且该项目被复制到堆栈指针寄存器指向的内存位置。请注意,“call”指令将隐式地将返回地址压入堆栈。参数的清理将在第 5 部分中解决。

第 2 部分:Stackframe 后退指针

此时,“call”指令已发出,我们现在正处于被调用例程的开始处。如果我们想访问我们的参数,我们可以像...

[esp + 0]   - return address
[esp + 4] - parameter 'a'
[esp + 8] - parameter 'b'
[esp + 12] - parameter 'c'

但是,在我们为局部变量和其他内容留出空间后,这可能会变得很笨拙。因此,除了堆栈指针寄存器之外,我们还使用堆栈基指针寄存器。但是,我们希望将堆栈基指针寄存器设置为当前帧,而不是前一个函数。因此,我们将旧的保存在堆栈上(这会修改堆栈上参数的偏移量),然后将当前堆栈指针寄存器复制到堆栈基指针寄存器。

push ebp        ; save previous stackbase-pointer register
mov ebp, esp ; ebp = esp

有时您可能会看到仅使用“ENTER”指令即可完成此操作。

第三部分:为局部变量留出空间

局部变量存储在堆栈中。由于堆栈向下增长,我们减去一些字节(足以存储我们的局部变量):

sub esp, n_bytes ; n_bytes = 局部变量所需的字节数

第 4 部分:将所有内容放在一起。使用栈基指针寄存器访问参数...

[ebp + 16]  - parameter 'c'
[ebp + 12] - parameter 'b'
[ebp + 8] - parameter 'a'
[ebp + 4] - return address
[ebp + 0] - saved stackbase-pointer register

使用堆栈指针寄存器访问局部变量...

[esp + (# - 4)] - top of local variables section
[esp + 0] - bottom of local variables section

第 5 部分:堆栈框架清理

当我们离开例程时,必须清理堆栈帧。

mov esp, ebp   ; undo the carving of space for the local variables
pop ebp ; restore the previous stackbase-pointer register

有时您可能会看到“LEAVE”指令取代了这两条指令。

根据您使用的语言,您可能会看到“RET”指令的两种形式之一。

ret
ret <some #>

选择哪一个将取决于语言的选择(或者如果使用汇编程序编写,您希望遵循的风格)。第一种情况表明调用者负责从堆栈中删除参数(在 foo(a,b,c) 示例中,它将通过 ... add esp, 12 执行此操作),这就是“C”的方式它。第二种情况表示返回指令返回时会从堆栈中弹出 # 个字(或 # 个字节,我不记得是哪一个),从而从堆栈中删除参数。如果我没记错的话,这是 Pascal 使用的风格。

关于assembly - 汇编中的栈帧是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3699283/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com