本文承接汇编语言学习笔记 上
上篇文章记录了汇编语言寄存器,汇编语言基本组成部分,数据传送指令,寻址指令,加减法指令,堆栈,过程,条件处理,整数运算的内容
高级过程
大多数现代编程语言在调用子程序的时候会把参数压入对战
子程序也常常把局部变量压入堆栈
- 子程序在C和C++中被称为函数
- 在Java中被称为方法
- 在宏汇编程序(MASM)中被称为过程
堆栈帧
堆栈参数
堆栈帧是一块堆栈保留区域
存放
- 被传递的实际参数
- 子程序的返回值
- 局部变量
- 被保存的寄存器
创建步骤
- 将被传递的实际参数压入堆栈
- 当子程序被调用时,该子程序的返回值压入堆栈
- 子程序开始执行的时候,EBP被压入堆栈
- 设置EBP等于ESP,EBP成为子程序所有参数的引用基址
- 如果有局部变量,修改ESP在堆栈中为其预留空间
- 需要保留的寄存器,将它们压入堆栈
Fastcall调用方式
顾名思义,是一种希望快速的调用方式
我们来分析一下这个调用方式:
调用自过程的时候,需要首先将参数传入EAX,EBX,ECX,EDX,少数情况还会传入ESI,EDI
我们知道寄存器是CPU内部的原件,堆栈在内存上,寄存器调用明显更快
但是我们知道通用寄存器很少,很多都有特定的功能,乘法需要用到EAX,还有许多寄存器用来循环数值和参与计算的操作数
因此寄存器不可能一直存放传递给过程的参数
在过程调用之前, 存放参数的寄存器需要首先入栈,然后向其分配过程参数
但是这些额外的入栈操作会让代码变得混乱,还有可能消除性能优势
值传递
一个参数通过数值传递时,该值的副本会被压入堆栈
-
.
data
-
val1
DWORD
3
-
val2
DWORD
6
-
.code
-
push val2
-
push val1
-
call
AddTwo
引用传递
通过引用来传递的参数包含的是对象的地址
-
push
OFFSET val2
-
push
OFFSET val1
传递数组
将数组的地址压入堆栈
不愿意采用将每个数组元素压入堆栈的原因是这样很慢而且浪费堆栈空间
访问堆栈的参数
1.将传递的参数压入堆栈,调用子过程
2.EBP寄存器存放的是原来栈帧的基址,我们需要现将EBP压入栈保存
3.然后将当前的ESP作为新的栈帧的基址
示例
-
int AddTwo(int x,int y)
-
{
-
return x+y;
-
}
将EBP入栈,设置ebp位esp的值
-
AddTwo PROC
-
push ebp
-
mov ebp,esp
ADD(5,6)
6 |
[EBP+12] |
5 | [EBP+8] |
返回地址 | [EBP+4] |
EBP | mov ebp,esp |
这样通过当前EBP和偏移量就能访问传入的参数和原来的ebp(返回地址)
显式的堆栈参数
堆栈参数的引用表达式形如[esp+8],称它们为显式的堆栈参数
清除堆栈
子程序返回时,必须将参数从堆栈中删除
否则会导致内存泄露,堆栈会被破坏
C调用方式-cdecl
用于C和C++语言
子程序的参数按逆序入栈
解决了运行时堆栈的问题
在调用子过程后,紧跟一条语句让堆栈指针ESP加上一个数,该数的值即为子程序参数所占的堆栈空间
-
main PROC
-
push
6
-
push
5
-
call AddTwo
-
add esp,
8
-
ret
-
main ENDP
能将参数从堆栈中删除
STDCALL调用规范
给RET指令添加了一个参数,使程序在返回调用过程的时候,ESP会加上这个参数
这个添加的整数和过程参数占用的堆栈空间字节数相等
-
AddTwo PROC
-
push ebp
-
mov ebp,esp
-
mov eax,
[ebp+12]
-
add eax,
[ebp+8]
-
pop ebp
-
ret
8
-
AddTwo ENDP
局部变量
在子过程中创建的变量
局部变量在ebp下
-
void Mysub()
-
{
-
int X=
10;
-
int Y=
20;
-
}
每个变量的存储大小都要向上取整保存为4的倍数
两个局部变量一共保留8个字节
-
MySub PROC
-
push ebp
-
mov ebp,esp
-
sub esp,
8
-
mov DWORD PTR [ebp
-4],
10
-
mov DWORD PTR [ebp
-8],
20
-
mov esp,ebp
-
pop ebp
-
ret
-
MySub ENDP
转载:https://blog.csdn.net/m0_73644864/article/details/128747113