call 和 ret 指令
call 和 ret 指令都是转移指令,它们都修改 IP,或同时修改 CS 和 IP。它们经常被用来实现子程序的设计。
10.1 ret 和 retf
ret 指令用栈中的数据,修改 IP 的内容,从而实现近转移。
retf 指令用栈中的数据,修改 CS 和 IP 的内容,从而实现近远转移。
CPU 执行 ret 指令时,进行下面两步操作:
(1)(IP) = ((ss)*16 + (sp))
(2)(sp) = (sp) + 2
CPU 执行 retf 指令时,进行下面 4 步操作:
(1)(IP) = ((ss)*16 + (sp))
(2)(sp) = (sp) + 2
(3)(CS) = ((ss)*16 + (sp))
(4)(sp) = (sp) + 2
可以看出,如果我们用汇编语法来解释 ret 和 retf 指令,则:
CPU 执行 ret 指令时,相当于进行:
pop IP
CPU 执行 retf 指令时,相当于进行:
pop IP
pop CS
10.2 call 指令
CPU 执行 call 指令时,进行两步操作:
(1)将当前的 IP 或 CS 和 IP 压入栈中;
(2)转移。
call 指令不能实现短转移,除此之外,call 指令实现转移的方法和 jmp 指令的原理相同。
10.3 依据位移进行转移的 call 指令
call 标号(将当前的 IP 压栈后,转到标号处执行指令)
CPU 执行此种格式的 call 指令时,进行如下操作:
(1)(sp) = (sp) - 2
((ss)*16+(sp)) = (IP)
(2)(IP) = (IP) + 16位位移
16位位移=标号处的地址 - call 指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出。
从上面的描述中,可以看出,如果我们用汇编语法来解释此种格式的 call 指令,则:
push IP
jmp near ptr 标号
10.4 转移的目的地址在指令中的 call 指令
前面讲的 call 指令,其对应的机器指令中并没有转移的目的地址,而是相对于当前 IP 的转移位移。
call far ptr 标号,实现的是段间转移。
CPU 执行此种格式的 call 指令时,进行如下操作:
(1)(sp) = (sp) - 2
((ss)*16 + (sp)) = (IP)
(sp) = (sp) - 2
((ss)*16 + (sp)) = (IP)
(2)(CS) = 标号所在的段的段地址
(IP)=标号在段中的偏移地址
用汇编语法描述如下:
push CS
push IP
jmp far ptr 标号
10.5 转移地址在寄存器中的 call 指令
指令格式: call 16位reg
功能:
(sp) = (sp) - 2
((ss)*16 + (sp)) = (IP)
(IP) = (16位reg)
用汇编语法描述如下:
push IP
jmp 16位reg
10.6 转移地址在内存中的 call 指令
转移地址在内存中的 call 指令有两种格式。
(1) call word ptr 内存单元地址
用汇编语法解释如下:
push IP
jmp word ptr 内存单元地址
(2)call dword ptr 内存单元地址
CPU 执行“call dword ptr 内存单元地址”时,相当于进行:
push CS
push IP
jmp dword ptr 内存单元地址
按书上的说法,其实(1)中的IP就是内存单元地址,(2)类似。
10.7 call 和 ret 的配合使用
我们可以写一个具有一定功能的程序段,我们称其为 子程序,在需要的时候,用 call 指令转去执行。执行完之后,使用 ret 指令,从而转到 call 指令后面的代码处继续执行。
10.8 mul 指令
mul 是乘法指令,我们用 mul 做乘法的时候,要注意以下两点:
(1)两个相乘的数:两个相乘的数,要么都是 8 位,要么都是 16 位。如果是 8 位,一个默认放在 AL中,另一个放在 8 位 reg 或内存字节单元中;如果是 16 位,一个默认在 AX 中,另一个放在 16 位 reg 或内存字单元中。
(2)结果:如果是 8 位乘法,结果默认放在 AX 中;如果是 16 位乘法,结果高位默认放在 DX 中,低位在 AX 中存放。