基于8086cpu,虽然有点老,用来学习还是可以的。
目录
介绍
一.call 和 ret(我认为比较着来看好些)
三.ret 和 call 的结合
介绍
ret 和 call 都是转移指令,它们都修改IP 或者 CS:IP, ret 和 call 经常被用来设计子程序,也就是所谓的模块化设计
书上是先讲的ret 接着讲的 call, 我觉得这两者如果大家先能把他们联系起来理解可能会更好,不然单独说ret或者call的作用,可能一时间不能理解这么做是为什么。
无论什么高级语言它都避免不了下面的过程,用c举例子吧。
hello.c
看看编译链接的过程(为linux下gcc, windows下会有少许不同)
hello.c -> 预处理 hello.i -> 编译阶段 hello.s -> 汇编阶段 hello.o -> 链接阶段 ->hello
从上面过程来看,经过编译阶段生成汇编是必须的。
那么任何一个小程序相信在汇编中都少不了ret 和 call 指令。
来看一个简单的hello.c
#include <stdio.h> int main() { printf("hello, world\n"); return 0; }
下面是它的汇编
由于8086太老了,前面也说了,所以这里的汇编是32位的(gcc -m32 hello.c),语法是AT&T的,和8086语法有一定的区别,具体了解请百度or google
其他的不管,看main函数
在.c代码里我们只调用了一个库函数就是printf
所以代码里会有callq和retq(也就是call 和 ret ).看看下面几行
400520: e8 bb fe ff ff callq 4003e0 <puts@plt>
400525: b8 00 00 00 00 mov $0x0,%eax
40052a: c9 leaveq
40052b: c3 retq
最前面是内存地址(虚拟地址),接着是地址内存的机器指令,接下来是汇编代码
call指令调用printf函数时,先把下一条指令 mov %0x0, %eax 压栈,然后跳转到printf函数执行printf函数。
执行完了执行ret指令, ret指令是从栈中弹出刚才压栈的指令,继续执行此指令和后面的指令。栈段也就是
起了一个临时保存的作用。
所以ret 和 call 是配合着使用的。
到这里我又想起来了一些问题,关于操作系统为每一个程序分配的空间,就在这里也记录一下
比如创建一个.c程序,执行的时候,操作系统会为该 程序分配5个区。
1. 栈区(stack):存放函数的参数, 局部变量等, 由系统申请释放。
2. 堆,堆栈区(heap):根据程序中的指令申请释放,也就是由人决定的,手动申请和释放,c也就是malloc和free。
3. 全局区或静态区(static):存放全局变量和静态变量, 已初始化的二者放在一起,未初始化的二者放在一起。(静态变量和全局静态变量的区别就是作用域不同,全局静态变量可以在其他文件中访问, 而静态变量static 只能在本文件中访问)。
4. 常量区:存放一些常量,如字符串常量
5. 代码区:存放二进制代码
那么为什么调用一个函数,它的形式参数和被调用函数内部定义的变量(非静态和全局变量)会在运行结束后释放, 这些都是在栈里保存。
call调用时保存IP寄存器所指的下一条指令,然后IP寄存器就跳转到call 后面所指的地方,比如printf函数, 执行完在跳转回来,那么所谓的释放栈其实就是ret, 寄存器改变回了原先的位置, 那么以前的数据当然不“存在”了。
一.call 和 ret
call: 将当前的IP 或者 CS:IP 压入栈中
跳转到指定位置
ret : 用栈中所保存的数据赋值给IP的, 跳转回来。
用通俗的话来描述吧
call name
将当前IP寄存器所指向的下一条指令压栈。
执行jmp name, 跳转到name 处
ret
返回来刚才压栈保存的位置,继续。
代码描述
sp = sp -2
ss* 16 + sp = ip (将IP的值压栈)
IP = IP +16位位移 (就是jmp了)
ret
从栈中pop出一条指令, 然后让IP指向这条指令, 那么就接着这条指令继续执行下去。
IP = ss*16 + sp (保存的值出栈, 复制给IP)
sp = sp +2
ret 还有另外一种就是retf , f的意思也就是far 跨段转移。同理
二.call 和 ret 结合
也就是实现模块化, 其实就是一段代码框架
assume cs:code
code segment
main: .....
......
call sub1 ;调用子程序1
......
mov ax, 4c00h
int 21h
sub 1: ;子程序1
call sub2 ;调用子程序2
......
ret
sub 2:
......
ret ;子程序返回
code ends
end main
寄存器冲突问题
cpu中的寄存器数量有限, 8086貌似只有14个, 主程序和子程序寄存器的使用可能冲突,那么我们有一种好的解决方法就是
在子程序使用寄存器前, 将寄存器的东西全部入栈, 执行子程序, 执行完毕后在出栈 返回ret.
补充mul指令
乘法指令
8 位 AL * 8位reg(寄存器) = AX
16位 AX * 16位reg = DX(高16位) AX(低16位)