ret指令与call指令的深入理解
背景
1、代码
2、编译
1 | gcc Rj45.c -o Rj45 -m32 |
3、反编译
1 | objdump -d Rj45 -M intel |
1 | 0804840b <main>: |
4、问题
在反汇编的过程中,有两个比较关键的地方:call
和ret
。
在《汇编语言(第3版)》中对这两个指令的概述为:
1 | call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计,也即是调用和返回。 |
其中ret指令用栈中的数据,修改IP的内容,实现近转移。
而call指令将IP或者CS和IP压入栈中,实现转移。
还有retf指令,用栈中的数据,修改CS和IP的内容,实现远转移。
那么,如何理解这个概述,以及如何理解从概述延申出来的概念性的知识
什么是转移指令?
1、概念
转移指令是可以控制CPU执行内存中某处代码的指令,或者说,转移指令是可以修改IP,或同时修改CS和IP的指令。
根据转移行为可分为段内转移和段间转移,其中只修改IP的叫段内转移;同时修改CS和IP的,叫段间转移
。
由于转移指令对IP或CS和IP的修改范围的不同,又分为短转移、近转移和远转移。
2、分类
1 | 无条件转移指令,如jmp |
什么是CS和IP?
CS是代码段寄存器、IP是指令指针寄存器。
(8086CPU有四个段寄存器,CS、DS、SS、ES,当CPU要访问内存是由这四个段寄存器提供内存单元的段地址。)
这两个寄存器是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
CS和IP如何指示CPU读取指令的地址?
8086机器中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
意思是,任意时刻,设CS中的内容是M,IP中的内容为N,8086CPU将从内存M*16+N单元开始,读取一条指令并执行。
解释:
CPU当前的状态是CS内数据为2000H,IP内为0000H
内存20000到20009H单元存放着可执行的机器码
1、CPU将从内存2000H*16+0000H出读取指令执行,其中将CS和IP内容送入地址加法器,地址加法器完成后,得到物理地址=段地址*16+偏移地址
。
2、地址加法器将物理地址送人输入输出控制电路,输入输出控制电路将物理地址20000H送上地址总线。
3、从内存20000H单元开始存放的机器指令B8 23 01即mov ax,0123H
通过数据总线被送入CPU。
4、输入输出控制电路将机器指令送入指令缓冲器,接着到执行控制器。执行控制器执行指令后,AX内的内容为0123H。
5、读取一条指令后,IP中的值会自动增加,以使CPU可以读取下一个指令。因当前读取的指令B8 23 01
的长度为3,故IP的值加3,CS:IP指向内存单元2000:0003。
6、以此依次执行。
什么是段地址和偏移地址?
什么是段?段的划分来自于CPU,而不是内存,内存没有分段。
由于8086CPU的内存的物理地址=段地址*16+偏移地址,故使得在管理内存的时候借助了分段的概念,将若干个地址连续的内存单元看作一个段。
什么是段地址和偏移地址?
这是8086CPU对内存读写图
CPU的两个相关部件提供两个16位地址、段地址和偏移地址。
段地址和偏移地址通过内部总线送到地址加法器。
地址加法器通过物理地址=段地址\*16+偏移地址
,合成20位物理地址。
输入输出电路将物理地址送上地址总线。
故段地址和偏移地址也即CS内地址数据和IP内地址数据。
ret指令与call指令实现了什么功能?
1、ret指令用栈中的数据,修改IP的内容,返回代码段的第一条指令。
相当于
1 | pop IP |
2、call指令将IP或者CS和IP压入栈中,实现转移。
相当于
1 | push IP |
3、retf指令,用栈中的数据,修改CS和IP的内容,返回代码段的第一条指令。
相当于
1 | pop IP |
以ret和call实现的子程序源程序的框架
1 | assume cs:code |
解释:
一个写有一定功能的程序段叫子程序。
在子程序需要被执行的时候用call指令去调用。
当子程序执行完后,由于call指令后面的指令的地址已经存储在栈中(push操作
),
使得在子程序后面再使用ret指令时候,栈中的数据会设置IP内的值,
IP内的值的设置实现了CPU继续执行call指令后面的代码指令,达到程序继续执行的目的。
参考
《汇编语言(第3版)》