寄存器
8086/8088 CPU的寄存器共有14个,都是16位的寄存器,根据用途分为数据寄存器、段寄存器、地址寄存器和控制寄存器4种类型
8086上一代CPU中的寄存器都是8位的;为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
分别为AH、AL、BH、BL、CH、CL、DH、DL,H表示 高字节寄存器(高8位),L表示低字节寄存器(低8位)
-
数据寄存器(通用寄存器)
- ax:累加器寄存器(Accumulator Register),主要作为累加器用,是算术运算的主要寄存器
- bx:基址寄存器(Base Register)
- cx:计数器寄存器(Count Register)
- dx:数据寄存器(Data Register)
-
地址寄存器
- si:源变址寄存器(Source Index Register),可用于存放源缓冲区的偏移地址
- di:目的变址寄存器(Destination Index Register),可用于存放目的缓冲区的偏移地 址
- bp:基址指针寄存器(Base Pointer Register),用于指出堆栈区基地址
- sp:栈指针寄存器(Stack Pointer Register),用于指出堆栈区栈顶的偏移地址
-
段寄存器
- cs
- ds
- es
- ss
-
控制寄存器
- IP:指令指针寄存器(Instruction Pointer Register),指出当前正在执行指令的下一条指令所在单元的偏移地址
- FR:标志寄存器(Flags Register),注意是否溢出的判断位即可
函数调用过程中栈帧的变化
- 调用函数前
- 参数先入栈(注意顺序是相反的,先入 b,再入 a,从右向左)
- 返回地址入栈(前一个函数调用完当前函数后应该返回的地址),即将eip压入栈
- 进入被调用函数
- ebp(的值)入栈,即被调用函数保存上一层函数的栈帧基地址,同时将当前esp下移
- 将当前栈指针(esp)赋值给栈帧基地址寄存器(ebp)
- 分配被调用函数需要的局部变量空间
- 函数执行
- 函数返回
- 清理寄存器和栈空间
- 将当前栈帧的基地址(ebp)复制给栈顶指针(esp),恢复上一层函数的栈顶位置
- 将栈顶的值弹出,并存储到 ebp 寄存器,恢复上一层函数的栈帧基地址
- 弹出返回地址,返回值(如果有的话)放在 eax 寄存器
那么问题来了, 一整个函数调用栈只有一个 esp 和一个 ebp,却有很多不同函数的栈帧,那么怎么分出各个函数的栈帧范围呢?这就需要 ebp。 当父函数执行时,ebp 指向父函数的栈底,此时父函数调用子函数,ebp 需要更新指向子函数的栈底(ebp 寄存器的值改成子函数栈底地址),但是父函数的栈底同时需要被保存,这个地址就被保存在更新后的 ebp 所指向的内存空间,也就是每个函数的栈底空间存储的都是上一个函数的栈底空间的地址,当函数调用结束需要回退 ebp 时,使用这个值去更新 ebp 寄存器的值即可
对应的汇编代码为
1 | push ebp |
下面以一次完整的main函数调用sum函数做说明
1 | int sum(int _a,int _b) |
在Compiler Explorer 得到对应的汇编代码如下:
1 | sum(int, int): |
main函数的栈在调用之前如图
-
函数参数从右至左入栈
1
2mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4] -
返回地址入栈
call sum(int, int)
指令实际上分两步:①push EIP
将下一条指令入栈保存起来;②esp-4
esp指针下移 -
main函数基指针入栈保存
1
push ebp
mov ebp esp
将esp的值存入ebp也就等于将ebp指向espsub esp 44H
将esp下移动一段空间创建sum函数的栈栈帧 -
将没有显式地初始化的内存用
CCCCCCCC
进行填充,将ebx
、esi
、edi
压入栈中 -
复制传入的参数的值,将sum的局部变量c放入[ebp-4]的空间内
1
2
3mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
mov DWORD PTR [rbp-4], 0 -
执行函数操作
-
将返回值(变量c所在的地址的内容)放入eax寄存器保存住;将ebp的值赋给esp,也就等于将esp指向ebp,销毁sum函数栈帧
1
2mov eax,dword ptr [ebp-4]
mov esp ebp -
ebp出栈,将栈中保存的main函数的基址赋值给ebp
1
pop ebp
注:
leave
指令相当于mov esp ebp
加上pop ebp
,有时被调用函数没有局部变量,ebp始终等于esp,就不用mov esp ebp
ret
指令相当于pop eip
-
ret
相当于pop eip
就是把之前保存的函数返回地址(也就是main函数中下一条该执行的指令的地址)出栈1
ret
-
add esp,8
,此时若传入sum函数的参数已经不需要了,我们将esp指针上移此时函数整个调用过程就结束了,main函数栈恢复到了调用之前的状态
寻址方式举例
参考下面的汇编代码
1 | int main() { |
dword ptr
是汇编指令中的一个操作数大小说明符,用于表示操作数的大小为双字(32位)
分支循环汇编示例
1 | __asm { |
其中,elseif
、elsee
、fun_end
等是汇编代码中的标签(label),用于标记代码中的特定位置,以便于在程序执行过程中进行跳转
本着互联网开源的性质,欢迎分享这篇文章,以帮助到更多的人,谢谢!