千哥读书笔记:汇编语言(王爽第四版)第10章 call和ret指令

本章是介绍call和ret指令,它们都是转移指令,都修改IP,或者同时修改CS和IP。它们经常被共同用来实现子程序的设计,本章内容属于汇编语言中实现子程序的基础。 由于内容也很复杂,故一步一步进行解析。

一、ret和retf指令

这两个指令相对简单:

1、ret指令用栈中的数据,修改IP的内容,从而实现近转移

CPU执行ret指令时,相当于进行:pop IP。

而栈中的数据,则由((ss)*16+(sp))决定,即:

(IP)=((ss)*16+(sp)),并且指令指针寄存器sp要自加2,即:(sp)=(sp)+2。

当然,这些操作会由CPU在执行ret指令时自动完成。

2、retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移

CPU执行retf指令时,相当于进行:

pop IP

pop CS

同理,在CUP执行reft时,会进行下面4步操作:

1)(IP)=((ss)*16+(sp))

2)(sp)=(sp)+2

3)(CS)=((ss)*16+(sp))

4)(sp)=(sp)+2

也就是说,当IP指向栈底的sp、在CUP执行reft时,会将栈底的第一个字单元的内容,出栈后赋值给IP,然后将栈底的第二个字单元的内容,赋值给CS,从而改变CS和IP的内容。

所谓的“实现近转移”(ret指令)和“实现远转移”(retf指令),区别就在于,ret指令只修改IP,而retf指令既要修改IP,还要修改CS。

二、call指令

call指令相对复杂,从对栈单元内容进行操作(push和pop)的角度来看,实际上ret指令和reft指令是call指令的逆向操作;或者说,在调用子程序的时候,先会用call指令,然后用ret或reft进行返回,如下图:

根据call指令的工作类型,并结合第9章转移指令的原理的思维导图:

同样可以画一个call指令的思维导图进行区分:

1、依据位移进行转移

1)call 标号

是将当前的IP压栈后,转到标号处执行指令,相当于:

push IP

jmp near ptr 标号

由于jmp near ptr 标号称为“段内近转移”,故把call 标号也归为“段内近转移”这种类型,后面的call指令类型也以此类推。

而“jmp near  ptr 标号””的功能:(IP) = (IP) + 16  位位移。16 位位移的范围是 -32768~32767。

可以看出,“call 标号”这一指令,实际上是在指令“jmp near  ptr 标号” 的基础上,多了一个push IP的动作。

这里需要注意的是:push IP

并不是将本条指令所指示的IP压栈,所谓的将当前的IP压栈,实际上是将本条指令的下一条指令的IP压栈。

如原书第193的检测点10.2:

最后,在执行完pop ax 之后,ax为6。

原因就在于,当执行 call s的时候,IP并不是3,而已经指向了6。即:

第一步:CPU读取call s到指令缓冲区,(IP) = (IP) + 所执行指令的长度(call s指令的长度)。此时的IP为3,再加上call s指令的长度3,就是6。相当于 mov ip,6,而不是mov ip,3。

第二步:将当前的IP(值为6)压栈。

第三点:转到标号s处,执行pop ax。

而这一点知识是最容易搞混的,应当认真去领会。其他类型的call指令关于IP的压栈方式,也要参照这个知识点。

2)call far ptr 标号

这种指令实现的是段间转移,换句话说就是转移的过程,会涉及代码段寄存器CS的改变,指令的跳转可以在不同的代码段之间进行。

CUP执行“call far ptr 标号”中,相当于进行:

push CS

push IP

jmp far ptr 标号

需要注意的是,“jmp far ptr 标号”指令所对应的机器码,高地址(16位)是转移的段地址,低地址(16位)是偏移地址。

当然,在执行“call far ptr 标号”指令时,上述细节都是由CPU自动执行的。我们只需要了解这些原理。

2、转移地址在内存中

1)call word ptr 内存单元地址

相当于进行:

push IP

jmp word ptr 内存单元地址

这里不难发现,“call word ptr 内存单元地址”指令与“call 标号”有异曲同工之妙,其实都是在同一个段中执行跳转。

不同之处在于:

“call 标号”指令相当于在执行“push IP”之后,执行“jmp near ptr 标号”指令,而标号所指示的偏移量,是由CPU自动计算得来。偏移量是16 位位移,范围是 -32768~32767。

而“call word ptr 内存单元地址”指令的偏移量,是由内存单元地址的内容提供,也就是说,这个偏移量要事先写入相应的内存单元。

由于有“word”这个关键词,会将2个内存单元地址(每个单元1个字节),也就是1个字用来保存偏移量,这是16 位位移,范围是 -32768~32767。

如果指令是:call word ptr ds:[0],则相当于mov ip ds:[0] 。当然,汇编语言是没有mov ip ds:[0] 这个指令的,这里只是从原理上讲相当于这种表述。

2)call dword ptr 内存单元地址

相当于进行:

push CS

push IP

jmp dword ptr   内存单元地址

同样,不难发现,“call dword ptr 内存单元地址”指令与“call far ptr 标号”有异曲同工之妙,都是在不同段间执行转移。

不同之处在于:

“call far ptr 标号”指令相当于在执行“push IP”之后,执行“jmp far ptr 标号”指令,而标号所指示的目的段地址CS(16位)和目的偏移量IP(16位),是由CPU自动计算得来。

而“call dword ptr 内存单元地址”指令的目的段地址CS(16位)和目的偏移量IP(16位),是由内存单元地址的内容提供,也就是说,这个CS和IP要由事先写入相应的内存单元。

由于有“dword”这个关键词 ,会将4个内存单元地址(每个单元1个字节),也就是2个字来保存转移的目的段地址和转移的目的偏移地址。

而“jmp dword ptr   内存单元地址”指令本身,就是从内存单元地址开始存放2个字,高地址处的字(16位)是转移的目的段地址,低地址处(16位)是转移的目的偏移地址。然后实施目的地址的跳转。

3、转移地址在寄存器中

这种类型的call 指令就相对简单,只有一种指令格式:

call 16位reg

从语法上讲,相当于:

push IP

jmp 16位reg

例如:

mov ax,6

call ax

其中的call ax 相当于:

push IP

jmp ax

而jmp ax则相当于:mov IP,ax

从上述知识内容中不难发现,从jmp指令到call指令,其语法结构都是层层嵌套的,只要搞懂了jmp指令,call指令也迎刃而解。

三、call和ret 的配合使用

前面提到,在调用子程序的时候,先会用call指令,然后用ret或reft进行返回,原书中给出了这样一个示例:

我们来看一下 CPU 执行这个程序的主要过程。

1、CPU 将 call s指令的机器码读入,IP 指向了 call s后的指令 mov bx,ax,然后CPU 执行 call s指令,将当前的IP 值(指令 mov bx,ax 的偏移地址)压栈,并将IP的值改变为标号s处的偏移地址。

2、CPU 从标号s处开始执行指令,loop 循环完毕后,(ax)=8。

3、CPU 将 ret 指令的机器码读入,IP 指向了 ret 指令后的内存单元,然后 CPU 执行ret 指令,从栈中弹出一个值(即 call s先前压入的 mov bx,ax 指令的偏移地址)送入 IP中。则 CS:IP 指向指令 mov bx.ax。

4、CPU 从 mov bx,ax 开始执行指令,直至完成,bx等于8。

理解了call指令与ret指令相配合的原理之后,自然也就理解了call指令与retf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值