assembly 什么是组件中的堆栈框架?

rbl8hiat  于 2022-12-27  发布在  其他
关注(0)|答案(4)|浏览(131)

堆栈帧的结构是什么?在程序集中调用函数时如何使用堆栈帧?

rn0zuynd

rn0zuynd1#

每个例程都使用堆栈的一部分,我们称之为堆栈框架。虽然汇编程序员不一定要遵循下面的风格,但强烈推荐这种做法。
每个例程的堆栈帧分为三个部分:函数参数、指向上一个堆栈帧的反向指针和局部变量。

第1部分:函数参数

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

foo (a, b, c);

调用者将把它转换为...

push c
push b
push a
call foo

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

第2部分:堆栈帧返回指针

此时,“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”指令即可完成此操作。

第3部分:为局部变量雕刻空间

局部变量存储在堆栈中,由于堆栈变小,我们减去一些字节数(足够存储局部变量):
第一个月

第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)示例,它将通过... addesp来这样做,12),这是“C”执行此操作的方式。第二种情况表示return指令将弹出# words(或者# bytes,我记不清是哪一个了),这样就把参数从堆栈中移除了。如果我没记错的话,这是Pascal使用的风格。

eoigrqb6

eoigrqb62#

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寄存器访问参数和局部变量。

xn1cxnb4

xn1cxnb43#

这取决于操作系统和使用的语言。由于ASM中的堆栈没有通用格式,堆栈在ASM中所做的唯一事情是在执行跳转子例程时存储返回地址。在执行从子例程返回时,从堆栈中获取地址并将其放入程序计数器(下一个CPU执行指令将从其中读取的存储器位置)
您需要查阅文档以了解所使用的编译器。

cidc1ykv

cidc1ykv4#

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

相关问题