汇编语言:基于x86处理器-学习笔记-第八章
第八章学习笔记
堆栈帧
《汇编语言:基于x86处理器(原书第7版)》 Page 227
堆栈帧 (stack frame)(或活动记录 (activation record))是一块堆栈保留区域,用于存放被传递的实际参数、子程序的返回值、局部变量以及被保存的寄存器。堆栈帧的创建步骤如下所示:
- 被传递的实际参数。如果有,则压入堆栈。
- 当子程序被调用时,使该子程序的返回值压入堆栈。
- 子程序开始执行时,EBP 被压入堆栈。
- 设置 EBP 等于 ESP。从这时开始,EBP 就变成了该子程序所有参数的引用基址。
- 如果有局部变量,修改 ESP 以便在堆栈中为这些变量预留空间。
- 如果需要保存寄存器,就将它们压入堆栈。
ESP
用于寻址堆栈(一种系统内存结构)数据。它极少用于一般算术运算和数据传输,通常被称为扩展堆栈指针 (extended stack pointer) 寄存器。- 高级语言通过
EBP
来引用堆栈中的函数参数和局部变量。除了高级编程,它不用于一般算术运算和数据传输。它常常被称为扩展帧指针 (extended frame pointer) 寄存器。
程序内存模式和对参数传递规则的选择直接影响到堆栈帧的结构。
学习用堆栈传递参数有个好理由:几乎所有的高级语言都会用到它们。比如如果想要在 32 位 Windows 应用程序接口(API)中调用函数,就必须用堆栈传递参数。而 64 位程序可以使用另一种不同的参数传递规则。
访问堆栈参数
高级语言有多种方式来对函数调用的参数进行初始化和访问。以 C 和 C++ 语言为例,它们以保存 EBP 寄存器并使该寄存器指向栈顶的语句为开始 (prologue)。然后,根据实际情况,它们可以把某些寄存器入栈,以便在函数返回时恢复这些寄存器的值。在函数结尾 (epilogue) 部分,恢复 EBP 寄存器,并用 RET 指令返回调用者。
ENTER 指令
ENTER 指令为被调用过程自动创建堆栈帧。它为局部变量保留堆栈空间,把 EBP 入栈。具体来说,它执行三个操作:
- 把 EBP 入栈(
push EBP
) - 把 EBP 设置为堆栈帧的基址(
mov EBP, ESP
) - 为局部变量保留空间(
sub ESP, numbytes
)
ENTER有两个操作数:第一个是常数,定义为局部变量保存的堆栈空间字节数;第二个定义了过程的词法嵌套级。
$$
ENTER \space \space numbytes, \space nestinglevel
$$
这两个操作数都是立即数。Numbytes 总是向上舍入为 4 的倍数,以便 ESP 对齐双字边界。Nestinglevel 确定了从主调过程堆栈帧复制到当前帧的堆栈帧指针的个数。
示例
ENTER 指令为局部变量保留了 8 个字节的堆栈空间:
1 |
|
它与如下指令等效:
1 |
|
下图为执行 ENTER 指令前后的堆栈示意图。
LEAVE 指令
LEAVE 指令结束一个过程的堆栈帧。它反转了之前的 ENTER 指令操作:恢复了过程被调用时 ESP 和 EBP 的值。再次以 MySub 过程为例,现在可以编码如下:
1 |
|
下面是与之等效的指令序列,其功能是在堆栈中保存和删除 8 个字节的局部变量:
1 |
|
LOCAL 指令
不难想象,Microsoft 创建 LOCAL 伪指令是作为 ENTER 指令的高级替补。
LOCAL 声明一个或多个变量名,并定义其大小属性。(另一方面,ENTER 则只为局部变量保留一块未命名的堆栈空间。)如果要使用 LOCAL 伪指令,它必须紧跟在 PROC 伪指令的后面。其语法如下所示:
$$
LOCAL \space \space varlist
$$
$varlist$ 是变量定义列表,用逗号分隔表项,可选为跨越多行。每个变量定义采用如下格式:
$$
label: \space type
$$
其中,标号可以为任意有效标识符,类型既可以是标准类型(WORD、DWORD等),也可以是用户定义类型。
示例
1 |
|