ITEEDU

程序中多出的CALL指令就是用于调用子程序的。

助记符:CALL
用 途:调用指定地址开始的一段子程序
格 式:CALL 入口地址
执 行:CPU将返回地址存至堆栈并转至给出的地址处继续执行指令

从形式上看CALL指令与JMP指令有些类似,但实际上CPU执行CALL指令的动作和JMP指令完全不同。JMP指令仅仅是使IP寄存器中装入一个新地址,而CALL指令不仅可以完成这个工作,同时CPU在执行CALL指令时还会自动把下一条指令的偏移地址压入堆栈。我们可以实际跟踪一下这个程序,看看是不是这样:(返回DOS了吗?)

C:\ASM\>DEBUG PROG4-A.COM[Enter] 
-t
AX=0100 BX=0000 CX=0029 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=0A3E ES=0A3E SS=0A3E CS=0A3E IP=0102 NV UP EI PL NZ NA PO NC
0A3E:0102 CD21 INT 21

请注意指令INT的意义,毫无疑问这条指令实际代表着一段程序。我们并不知道21H中断服务程序究竟有多长,而且跟踪系统提供的中断服务程序也没有什么意义,因此有必要对INT指令进行适当的处理。

-p   注意不要使用"t"命令
a   输入小写字母a
AX=0100 BX=0000 CX=0029 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=0A3E ES=0A3E SS=0A3E CS=0A3E IP=0102 NV UP EI PL NZ NA PO NC
0A3E:0102 CD21 INT 21

"P"是一个新的命令,作用与"T"相似。但这两者又有不同:由于INT指令的实质是调用了操作系统为我们提供的一个子程序,所以若用T命令跟踪INT指令,则就会跟踪到操作系统内部。这是没有必要的同时搞不好也是危险的。P命令则不然,它比T命令要聪明,当它发现被跟踪的指令是INT(或CALL、LOOP等),它会一次将被调用的中断(或子程序、循环)全部执行完,也就是将这些指令"一步"跨过,这样就可以避免T命令的冗长。

-t 不要敲入"P"
AX=0161 BX=0000 CX=0029 DX=0000 SP=FFFC BP=0000 SI=0000 DI=0000
DS=0A3E ES=0A3E SS=0A3E CS=0A3E IP=010D NV UP EI PL NZ NA PO NC
0A3E:010D 50 PUSH AX

并非是所有的CALL、INT指令都需要使用"P"命令跳过,如果确实需要仔细分析一个子程序或检查操作系统中是否有"异形"(病毒)程序侵入,那么"T"命令的缺点也就成了优势了。这就是为什么我们在跟踪CALL 010D指令时用了T命令,因为我们想看到CALL指令的实际动作。

不难看出,CALL指令执行完后,CPU确实转移到10D处执行指令,同时我们也看到,SP寄存器被减了2,从FFFEH变成FFFCH,也就是说,有一个16位数据进入了堆栈。这个数据是什么呢?

-dfffc    返回地址
0A3E:FFF0  07 01 00 00  .>~...p.......p.

用D命令显示出堆栈的内容,可以看到这个数据是0107,它恰恰是指令"CMP AL,1B"的偏移地址。