汇编语言访问堆栈参数详解
高级语言有多种方式来对函数调用的参数进行初始化和访问。以 C 和 C++ 语言为例,它们以保存 EBP 寄存器并使该寄存器指向栈顶的语句为开始 (prologue)。
然后,根据实际情况,它们可以把某些寄存器入栈,以便在函数返回时恢复这些寄存器的值。在函数结尾 (epilogue) 部分,恢复 EBP 寄存器,并用 RET 指令返回调用者。
AddTwo 在其他寄存器入栈时,不用通过 EEP 来修改堆栈参数的偏移量。数值会改变的是 ESP,而 EBP 则不会。
main 部分试图忽略这个问题,并希望程序能正常结束。但是,如果循环调用 AddTwo,堆栈就会溢出。因为每次调用都会占用 12 字节的堆栈空间——每个参数需要 4 个字节,再加 4 个字节留给 CALL 指令的返回地址。如果在 main 中调用 Example1,而它又要调用 AddTwo 就会导致更加严重的问题:
RET 指令把整数 5 加载到指令指针寄存器,尝试将控制转移到内存地址为 5 的位置。假设这个地址在程序代码边界之外,那么处理器将给出运行时异常,通知 OS 终止程序。
然后,根据实际情况,它们可以把某些寄存器入栈,以便在函数返回时恢复这些寄存器的值。在函数结尾 (epilogue) 部分,恢复 EBP 寄存器,并用 RET 指令返回调用者。
AddTwo示例
下面是用 C 编写的 AddTwo 函数,它接收了两个值传递的整数,然后返回这两个数之和:int AddTwo( int x, int y ) { return x + y; }现在用汇编语言实现同样的功能。在函数开始的时候,AddTwo 将 EBP 入栈,以保存其当前值:
AddTwo PROC
push ebp
AddTwo PROC
push ebp
mov ebp,esp
AddTwo 在其他寄存器入栈时,不用通过 EEP 来修改堆栈参数的偏移量。数值会改变的是 ESP,而 EBP 则不会。
基址-偏移量寻址
可以使用基址-偏移量寻址 (base-offset addressing) 方式来访问堆栈参数。其中,EBP 是基址寄存器,偏移量是常数。通常,EAX 为 32 位返回值。AddTwo 的实现如下所示,参数相加后,EAX 返回它们的和数:AddTwo PROC push ebp mov ebp, esp ;堆栈帧的基址 mov eax, [ebp + 12] ;第二个参数 add eax, [ebp + 8] ;第一个参数 pop ebp ret AddTwo ENDP
显式的堆栈参数
若堆栈参数的引用表达式形如 [ebp+8],则称它们为显式的堆栈参数 (explicit stack parameters)。这个名称的含义是:汇编代码显式地说明了参数的偏移量是一个常数。有些程序员定义符号常量来表示显式的堆栈参数,以使其代码更具易读性:y_param EQU [ebp + 12] x_param EQU [ebp + 8] AddTwo PROC push ebp mov ebp,esp mov eax,y_param add eax,x_param pop ebp ret AddTwo ENDP
清除堆栈
子程序返回时,必须将参数从堆栈中删除。否则将导致内存泄露,堆栈就会被破坏。例如,设如下语句在 main 中调用 AddTwo:
push 6
push 5
call AddTwo
main 部分试图忽略这个问题,并希望程序能正常结束。但是,如果循环调用 AddTwo,堆栈就会溢出。因为每次调用都会占用 12 字节的堆栈空间——每个参数需要 4 个字节,再加 4 个字节留给 CALL 指令的返回地址。如果在 main 中调用 Example1,而它又要调用 AddTwo 就会导致更加严重的问题:
main PROC call Example1 exit main ENDP Example1 PROC push 6 push 5 call AddTwo ret ;堆栈被破坏了! Example1 ENDP当 Example1 的 RET 指令将要执行时,ESP 指向整数 5 而不是能将其带回 main 的返回地址:
RET 指令把整数 5 加载到指令指针寄存器,尝试将控制转移到内存地址为 5 的位置。假设这个地址在程序代码边界之外,那么处理器将给出运行时异常,通知 OS 终止程序。
所有教程
- socket
- Python基础教程
- C#教程
- MySQL函数
- MySQL
- C语言入门
- C语言专题
- C语言编译器
- C语言编程实例
- GCC编译器
- 数据结构
- C语言项目案例
- C++教程
- OpenCV
- Qt教程
- Unity 3D教程
- UE4
- STL
- Redis
- Android教程
- JavaScript
- PHP
- Mybatis
- Spring Cloud
- Maven
- vi命令
- Spring Boot
- Spring MVC
- Hibernate
- Linux
- Linux命令
- Shell脚本
- Java教程
- 设计模式
- Spring
- Servlet
- Struts2
- Java Swing
- JSP教程
- CSS教程
- TensorFlow
- 区块链
- Go语言教程
- Docker
- 编程笔记
- 资源下载
- 关于我们
- 汇编语言
- 大数据
- 云计算
- VIP视频