前面,我们已经分别学习了ret和call指令的原理。现在来看一下,如何将它们配合使用来实现子程序的机制。
问题
下面程序返回前,bx中的值是多少?
assume cx:code code segment start: mov ax, 1 mov cx,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执行calls指令,将当前的IP值(指令movbx,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提供
我们再来看下面的程序:
源程序 内存中的情况(假设程序从内存1000:。处装入) assume cs:code stack segment db 8 dup (0) 1000:0000 00 00 00 00 00 00 00 00 db 8 dup (0) 1000:0008 00 00 00 00 00 00 00 00 stack ends code segment start: mov ax,stack 1001:0000 B8 00 10 mov ss,ax 1001:0003 8E D0 mov sp,16 1001:0005 BC 10 00 mov ax,1000 1001:0008 B8 E8 03 call s 1001:000B E8 05 00 mov ax, 4c00h 1001:000E B8 00 4C int 21h 1001:0011 CD 21 s: add ax,ax 1001:0013 03 C0 ret 1001:0015 C3 code ends end start
看一下程序的主要执行过程。
(1)前3条指令执行后,栈的情况如下:
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | SS:Sp
(2) call指令读入后,(IP)=OOOEH } CPU指令缓冲器中的代码为:E8 OS 00
CPU执行E8 OS 00,首先,栈中的情况变为:
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 OE 00 | SS:Sp
然后,(IP)={IP)+0005=0013H
(3) CPU从cs:0013H处(即标号s处)开始执行
(4) ret指令读入后:
(IP)=0016H,CPU指令缓冲器中的代码为: C3
CPU执行C3,相当于进行pop IP,执行后,栈中的情况为:
1000:0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 OE 00 | SS:Sp (IP)=000EH
(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: : : : ret ;子程序返回 code ends end main
现在,可以从子程序的角度,回过头来再看一下本节中的两个程序。