您的位置:首页 > 其它

call 和 ret 指令

2014-10-28 19:58 169 查看
基于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位)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: