call 和 ret 指令

Posted 夏天的技术博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了call 和 ret 指令相关的知识,希望对你有一定的参考价值。

基于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位)





以上是关于call 和 ret 指令的主要内容,如果未能解决你的问题,请参考以下文章

[汇编]《汇编语言》第10章 CALL和RET指令

汇编10:CALL和RET指令

汇编语言——call 和 ret 指令

第10章 CALL和RET指令

汇编-10.0-CALL和RET指令

使用 Intel Pin 时跟踪不匹配的 CALL 和 RET 指令数