本文承接汇编语言学习笔记 上
上篇文章记录了汇编语言寄存器,汇编语言基本组成部分,数据传送指令,寻址指令,加减法指令,堆栈,过程,条件处理,整数运算的内容
编辑
高级过程
大多数现代编程语言在调用子程序的时候会把参数压入对战
子程序也常常把局部变量压入堆栈
- 子程序在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
从堆栈中删除局部变量,只需要执行:
mov esp,ebp
esp向上移动=内存释放
可以给局部变量的偏移量定义一个符号,在代码中使用这些符号
X_local EQU DWORD PTR [ebp-4] Y_local EQU DWORD PTR [ebp-8] MySub PROC push ebp mov ebp,esp sub esp,8 mov X_local,10 mov Y_local ,20 mov esp,ebp pop ebp ret MySub ENDP
保存和恢复寄存器
子程序在修改寄存器之前将它们的当前值保存到堆栈
通常在ebp入栈,设置ebp等于esp之后,相关寄存器入栈
栈帧
栈 | 解释说明 |
传递的参数 | [EBP+8] |
返回地址 |
[EBP+4](原来栈帧的EBP) |
EBP | 当前栈帧的EBP |
ECX | |
EDX | 当前ESP指向的位置 |
EBP被初始化之后,整个过程中它的值将保持不变
ECX,EDX入栈并不影响EBP按照原来的偏移量访问传递的参数
引用参数
引用参数通常是基址-偏移量寻址方式进行访问
每个引用参数都是一个指针
.data count=100 array WORD count DUP(?) .code push OFFSET array push count call ArrayFill
ArrayFill PROC push ebp mov ebp,esp
数组偏移量 |
数组长度 |
返回地址 |
EBP |
LEA指令
返回间接操作数的地址
C代码:
void makeArray() { char myString[30]; for(int i=0;i<30;i++) myString[i]='*'; }
等效的汇编代码:
makeArray PROC push ebp mov ebp,esp sub esp,32 ;mystring位于EBP-30的位置 lea esi,[ebp-30] ;加载mystring的地址 mov ecx,30 ;设置循环计数器 L1: mov BYTE PTR [esi],'*' ;填充一个位置为'*' inc esi ;指向下一个元素 loop L1 ;循环30次,直到ecx是0 add esp,32 ;恢复esp pop ebp ret makeArray ENDP
ENTER指令
为被调用过程自动创建堆栈帧
包含三个操作
- 把EBP入栈 push ebp
- 把ebp设置为堆栈帧的基址 mov ebp,esp
- 为局部变量保留空间 sub esp,numbytes
ENTER有两个操作数,一个是常数,是局部变量保存的堆栈空间(字节),第二个参数定义了过程的词法嵌套级
ENTER numbytes,nestinglevel
MySub PROC enter 0,0
等效于:
MySub PROC push ebp mov ebp,esp
MySub PROC enter 8,0
等效于:
MySub PROC push ebp mov ebp,esp sub esp,8
LEAVE指令
结束一个过程的堆栈帧,与ENTER是相对应的操作
直接上代码理解
MySub PROC enter 8,0 . . leave MySub ENDP
等效于:
MySub PROC push ebp mov ebp,esp sub esp,8 . . mov esp,ebp pop ebp ret MySub ENDP
前一篇文章汇编语言学习笔记 上
已将基本的汇编语言语法总结了一遍
暂时不更新
编辑
未完待续