什么是装配中的堆叠框架?

时间:2010-09-13 09:43:23

标签: assembly x86

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

4 个答案:

答案 0 :(得分:130)

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

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

第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'

然而,在我们为局部变量和东西划分空间之后,这可能会变得笨拙。因此,除堆栈指针寄存器外,我们还使用stackbase-pointer寄存器。但是,我们希望stackbase-pointer寄存器设置为当前帧,而不是前一个函数。因此,我们将旧的保存在堆栈上(这会修改堆栈上参数的偏移量),然后将当前堆栈指针寄存器复制到堆栈库指针寄存器。

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

有时您可能只使用“ENTER”指令看到这一点。

第3部分:为局部变量创建空间

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

sub esp, n_bytes ; n_bytes = number of bytes required for local variables

第4部分:全部放在一起。 使用stackbase-pointer寄存器访问参数...

[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部分:Stackframe清理

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

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)示例,它将通过...添加esp,12),这就是'C'的做法它。第二种情况表明返回指令会在返回时从堆栈弹出#words(或#字节,我记不清哪些),从而从堆栈中删除参数。如果我没记错的话,这就是Pascal使用的风格。

很长,但我希望这有助于您更好地理解堆栈框架。

答案 1 :(得分:13)

通过执行

创建x86-32堆栈帧
function_start:
    push ebp
    mov ebp, esp

所以它可以通过ebp访问,看起来像

ebp+00 (current_frame) : prev_frame
ebp+04                 : return_address
                         ....
prev_frame             : prev_prev_frame
prev_frame+04          : prev_return_address

通过汇编指令设计将堆栈帧用于ebp有一些优点,因此通常使用ebp寄存器访问参数和本地。

答案 2 :(得分:2)

这取决于所使用的操作系统和语言。因为ASM中没有堆栈的通用格式,堆栈在ASM中唯一要做的就是在执行跳转子例程时存储返回地址。当执行从子程序返回时,地址从堆栈中被拾取并放入程序计数器(存储器位置,其中下一个CPU执行指令将来自其中)

您需要查阅您正在使用的编译器的文档。

答案 3 :(得分:0)

编译器(取决于编译器)可以使用x86堆栈帧来传递参数(或指向参数的指针)并返回值。见this