一. call指令
1. call 标号
call 标号 是根据位移进行进转移的call指令,实现的是段内转移,只修改IP的值,操作原理:
(1)(sp) = (sp) - 2 ;栈顶开辟2字节空间
((ss)*16 + (sp)) = (IP) ;栈顶的值 = (IP)
(2)(IP) = (IP) + 16位位移 ;修改IP的值
CPU执行 call 标号 指令,相当于执行:
(1)push IP
(2)jmp near ptr 标号
2. call far ptr 标号
call far ptr 标号 是转移的目的地址在指令中的call指令,实现的是段间转移,同时修改CS和IP的值,操作原理:
(1)(sp) = (sp) - 2 ;栈顶开辟2字节空间
((ss)*16+(sp)) = (CS) ;当前栈顶的值 = (CS)
(sp) = (sp) - 2 ;栈顶再开辟2字节空间
((ss)*16 + (sp)) = (IP) ;当前栈顶的值 = (IP)
(2)(CS) = 标号所在段的段地址
(IP) = 标号在段中的偏移地址
CPU执行 call far ptr 标号 指令,相当于执行:
(1)push CS
push IP
(2)jmp far ptr 标号
3. call 寄存器
call 16位寄存器 是转移的目的地址在寄存器中的call指令,实现的是段内转移,只修改IP的值,操作原理:
(1)(sp) = (sp) - 2 ;栈顶开辟2个字节的空间
((ss)*16+(sp)) = (IP) ;栈顶的值 = (IP)
(2)(IP) = (16位寄存器) ;修改IP值
CPU执行call 16位寄存器指令,相当于执行:
(1)push IP
(2)jmp 16位寄存器
4. call word ptr 内存单元地址
call word ptr 内存单元地址 是转移的目的地址在内存中的call指令,实现的是段内转移,只修改IP,基本原理:
(1)(sp) = (sp) - 2 ;栈顶开辟2个字节的空间
((ss)*16+(sp)) = (IP) ;栈顶的值 = (IP)
(2)(IP) = (内存单元地址) ;修改IP值
CPU执行 call word ptr 内存单元地址 指令时,相当于执行:
(1)push IP
(2)jmp word ptr 内存单元地址
5. call dword ptr 内存单元地址
call dword ptr 内存单元地址 是转移的目的地址在内存中的call指令,实现的是段间转移,在指明的内存单元地址中,高字单元内存中存放的是目的段地址(CS),低字单元内存中存放的是目的偏移地址(IP)。基本原理:
(1)(sp) = (sp) - 2 ;栈顶开辟2个字节的空间
((ss)*16+(sp)) = (CS) ;当前栈顶的值 = (CS)
(sp) = (sp) - 2 ;栈顶再开辟2个字节的空间
((ss)*16+(sp)) = (IP) ;当前栈顶的值 = (IP)
(2)(CS) = 内存单元地址[2]
(IP) = 内存单元地址[0]
CPU执行call dword ptr 内存单元地址指令,相当于执行:
(1)push CS
push IP
(2)jmp dword ptr 内存单元地址
二. ret 和 retf指令
1. ret 指令
ret指令用栈顶中的数据,修改IP的内容,从而实现近转移。执行ret指令的基本步骤原理如下:
(1)(IP) = ((ss)*16 + (sp))
(sp) = (sp) + 2
CPU执行 ret 指令,相当于执行
pop IP
tips:
ret n 指令,相当于
pop IP
add sp, n
2. retf 指令
retf指令用栈顶中的数据,同时修改IP和CS,从而实现远转移。执行retf指令的基本步骤原理如下:
(1)(IP) = ((ss)*16 + (sp))
(sp) = (sp) + 2
(2)(CS) = ((ss)*16 + (sp))
(sp) = (sp) + 2
CPU执行ret指令时,相当于执行:
(1)pop IP
(2)pop CS
三. 模块化程序设计
1. 用栈传递参数
用栈传递参数的原理是由调用者通过push指令将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。
2. 函数调用栈
用栈传递参数这种技术和高级语言编译器的工作原理密切相关。bp(base pointer,基址指针)寄存器和 sp(stack pointer,栈顶指针)共同管理函数调用栈。bp 寄存器用于保存进入函数时的栈顶基址,用于作为正在运行的函数的栈帧的栈底,每一个函数都有自己的栈帧,函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的。而 sp 寄存器永远指向栈顶位置。
IP (Instruction Pointer,指令指针寄存器),其内存放着一个指针,该指针指向下一条等待执行的指令地址。
理论看起来是枯燥,似懂非懂的样子,我们来看下面的例子。
示例:写一个求一个数立方的函数,在主程序中,我们调用这个函数,求:
assume cs:code, ss:stack
stack segment
db 32 dup (0)
stack ends
code segment
start:
;初始化栈
mov ax, stack
mov ss, ax
mov sp, 32
mov bp, sp
mov ax, 2
push ax ;调用者将传递给被调用者的参数压入栈
call cube
mov ax, 4c00h
int 21h
;函数功能:计算a³,a为字型数据
;参数:进入子程序时,栈顶存放IP,后面存放参数a
;结果:ax存放结果的低16位,dx存放结果的高16位
cube:
push bp ;保存原栈顶基址
mov bp, sp ;设置现栈顶基址
mov ax, [bp+4] ;取到参数,因为call会push IP,又执行了push bp,所以函数调用栈中,传递给该函数参数在 [bp+4]处
mul word ptr [bp+4] ;16位乘法,一个数默认在ax寄存器,结果的低16位存放ax寄存器中,高16位存在dx寄存器中
mul word ptr [bp+4] ;
pop bp ;恢复原栈顶基址
ret 2 ;执行ret 2,相当于 pop IP; add sp, 2
code ends
end start
(1)各寄存器和栈段原始状态如下:
(2)初始化栈
(3)主程序将执行子程序需要的参数压入栈
(4)主程序调用子程序
(5)进入子程序
a)push bp ; 保存原栈顶基址
b)mov bp, sp ;设置现栈顶基址,bp = 001Ah
mov ax, [bp+4] ;取到参数,因为call会push IP,又执行了push bp,所以函数调用栈中,传递给该函数参数在 [bp+4]处
mul word ptr [bp+4] ;16位乘法,一个数默认在ax寄存器,结果的低16位存放ax寄存器中,高16位存在dx寄存器中
mul word ptr [bp+4] ;执行该指令后,一个数的立方计算完成,结果的低16位存放ax寄存器中,高16位存在dx寄存器中
此时,所以,ax = 0008h,dx = 0000h
c)pop bp ;恢复原栈顶基址,bp = 0020h
(6)子程序返回:ret 2 ;执行ret 2,相当于 pop IP; add sp, 2
至此,cube子程序执行完毕,栈又回到了未调用cube 子程序时的状态,CS:IP指向了代码段中:mov ax, 4c00h 这条指令。