[汇编]《汇编语⾔》第10章CALL和RET指令王爽《汇编语⾔》第四版超级笔记
⽬录
第10章 CALL和RET指令
call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。
它们经常被共同⽤来实现⼦程序的设计。这⼀章,我们讲解call和ret指令的原理。
10.1 ret和retf、call指令
ret指令⽤栈中的数据,修改IP的内容,从⽽实现近转移;
retf指令⽤栈中的数据,修改CS和IP的内容,从⽽实现远转移。
CPU执⾏ret指令时,进⾏下⾯两步操作:
(IP)=((ss)x16+(sp))
(sp)=(sp)+2
CPU执⾏retf指令时,进⾏下⾯4步操作:
(IP)=((ss)x16+(sp))
(sp)=(sp)+2
(CS)=((ss)x16+(sp))
(sp)=(sp)+2
可以看出,如果我们⽤汇编语法来解释ret和retf指令,则:
CPU执⾏ret指令时,相当于进⾏:
pop IP
CPU执⾏retf指令时,相当于进⾏:
pop IP
pop CS
下⾯的程序中,ret指令执⾏后,(IP)=0,CS:IP指向代码段的第⼀条指令。
assume cs:code
stack segment
db 15 dup (0)
stack ends
code segment
mov ax,4c00h
int 21h
start:  mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
下⾯的程序中,retf指令执⾏后,CS:IP指向代码段的第⼀条指令。
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00h
inc 21h
start:  mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start
CPU执⾏call指令时,进⾏两步操作:
(1)将当前的IP或CS和IP压⼊栈中;
(2)转移。
call指令不能实现短转移,除此之外,call指令实现转移的⽅法和jmp指令的原理相同,下⾯的⼏个⼩节中,我们以给出转移⽬的地址的不同⽅法为主线,讲解call指令的主要应⽤格式。
10.2 call指令应⽤场景
依据位移进⾏转移的call指令
call 标号(将当前的IP压栈后,转到标号处执⾏指令)
CPU执⾏此种格式的call指令时,进⾏如下的操作:
(1) (sp)=(sp)-2
((ss)x16+(sp))=(IP)
(2) (IP)=(IP)+16位位移。
16位位移=标号处的地址-call指令后的第⼀个字节的地址;
16位位移的范围为-32768~32767,⽤补码表⽰;
16位位移由编译程序在编译时算岀。
从上⾯的描述中,可以看出,如果我们⽤汇编语法来解释此种格式的call指令,则:
CPU执⾏“call 标号”时,相当于进⾏:
push IP
jmp near ptr 标号
汇编语言结束指令
转移的⽬的地址在指令中的call指令
"call far ptr 标号”实现的是段间转移。
CPU执⾏此种格式的call指令时,进⾏如下的操作。
(1 )(sp)=(sp)-2
((ss)x16+(sp))=(CS)
(sp)=(sp)-2
((ss)x16+(sp))=(IP)
(2) (CS)=标号所在段的段地址
(IP)=标号在段中的偏移地址
从上⾯的描述中可以看出,如果我们⽤汇编语法来解释此种格式的call指令,则:
CPU执⾏"call far ptr 标号”时,相当于进⾏:
push CS
push IP
jmp far ptr 标号
转移地址在寄存器中的call指令
指令格式:call 16位 reg
功能:
(sp)=(sp)-2
((ss)x16+(sp))=(IP)
(IP)=(16位reg)
⽤汇编语法来解释此种格式的call指令,CPU执⾏“call 16位 reg”时,相当于进⾏:push IP
jmp 16位 reg
转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式。
(1)call word ptr 内存单元地址
⽤汇编语法来解释此种格式的call指令,则:
CPU执⾏“call word ptr 内存单元地址”时,相当于进⾏:
push IP
jmp word ptr 内存单元地址
⽐如,下⾯的指令:
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
执⾏后,(IP)=0123H,(sp)=0EH。
(2)call dword ptr 内存单元地址
⽤汇编语法来解释此种格式的call指令,则:
CPU执⾏“call dword ptr 内存单元地址”时,相当于进⾏:
push CS
push IP
jmp dword ptr 内存单元地址
⽐如,下⾯的指令:
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
执⾏后,(CS)=0, (IP)=0123H,(sp)=0CH。
10.3 call和ret的配合使⽤、mul指令
我们⼰经分别学习了ret和call指令的原理。
现在来看⼀下,如何将它们配合使⽤来实现⼦程序的机制。
问题10.1
下⾯程序返回前,bx中的值是多少?
assume cs:code
code segment
start:  mov ax,1
mov ex,3
call s
mov bx,ax      ;(bx)=?
mov ax,4c00h
int 21h
s:  add ax,ax
loop s
ret
code ends
end start
思考后看分析。
分析:
我们来看⼀下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。可以看出,从标号s到ret的程序段的作⽤是计算2的N次⽅,计算前,N的值由cx提供。
我们再来看下⾯的程序:
看⼀下程序的主要执⾏过程。
(1) 前3条指令执⾏后,栈的情况如下:
(2) call指令读⼊后,(IP)=000EH,CPU指令缓冲器中的代码为:E8 05 00;
CPU执⾏E8 05 00,⾸先,栈中的情况变为:
然后,(IP)=(IP)+0005=0013H。
(3) CPU从cs:0013H处(即标号s处)开始执⾏。
(4) ret指令读⼊后:
(IP)=0016H,CPU指令缓冲器中的代码为:C3
CPU执⾏C3,相当于进⾏pop IP,执⾏后,栈中的情况为:
(5) CPU回到cs:000EH处(即call指令后⾯的指令处)继续执⾏。
从上⾯的讨论中我们发现,可以写⼀个具有⼀定功能的程序段,我们称其为⼦程序,在需要的时候,⽤call指令转去执⾏。
可是执⾏完⼦程序后,如何让CPU接着call指令向下执⾏?
call指令转去执⾏⼦程序之前,call指令后⾯的指令的地址将存储在栈中,所以可在⼦程序的后⾯使⽤ret指令,⽤栈中的数据设置IP的值,从⽽转到call指令后⾯的代码处继续执⾏。这样,我们可以利⽤call和ret来实现⼦程序的机制。⼦程序的框架如下。
标号:
指令
ret
具有⼦程序的源程序的框架如下。
assume cs:code
code segment
main:
...
...
...
call sub1      ;调⽤⼦程序sub1
...
...
...
mov ax,4c00h
int 21h
sub1:                  ;⼦程序sub1开始
...
...
...
call sub2      ;调⽤⼦程序sub2
...
...
...
ret            ;⼦程序返回
sub2:                  ;⼦程序sub2开始
...
.
..
...
ret            ;⼦程序返回
code ends
end main
现在,可以从⼦程序的⾓度,回过头来再看⼀下本节中的两个程序。
这⾥介绍⼀下mul指令,mul是乘法指令,使⽤mul做乘法的时候,注意以下两点。
(1)两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,⼀个默认放在AL中,另⼀个放在8位reg或内存字节单元中;如果是16位,⼀个默认在AX中,另⼀个放在16位reg或内存字单元中。
(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果⾼位默认在DX中存放,低位在AX中放。
格式如下:
mul reg
mul 内存单元
内存单元可以⽤不同的寻址⽅式给出,⽐如:
mul byte ptr ds:[0]
含义:(ax)=(al)x((ds)x16+0);
mul word ptr [bx+si+8]
含义:(ax)=(ax)x((ds)x16+(bx)+(si)+8)结果的低16位。
(dx)=(ax)x((ds)x16+(bx)+(si)+8)结果的⾼16 位。
例:
(1) 计算100x10
100和10⼩于255,可以做8位乘法,程序如下。
mov al,100
mov bl,10
mul bl
结果:(ax)=1000(03E8H)
(2) 计算100x10000
100⼩于255,可10000⼤于255,所以必须做16位乘法,程序如下。
mov ax,100
mov bx,10000
mul bx
结果:(ax)=4240H,(dx)=000FH
(F4240H=1000000)
10.4 参数和结果传递的问题、批量数据的传递
call与ret指令共同⽀持了汇编语⾔编程中的模块化设计。
在实际编程中,程序的模块化是必不可少的。因为现实的问题⽐较复杂,对现实问题进⾏分析时,把它转化成为相互联系、不同层次的⼦问题,是必须的解决⽅法。
⽽call与ret指令对这种分析⽅法提供了程序实现上的⽀持。利⽤call和ret指令,我们可以⽤简捷的⽅法,实现多个相互联系、功能独⽴的⼦程序来解决⼀个复杂的问题。
下⾯的内容中,我们来看⼀下⼦程序设计中的相关问题和解决⽅法。