第10章 CALL 和 RET指令
10.1 ret和retf
执行ret指令步骤:
1.取栈最顶部的内容作为IP
2.sp=sp+2
retf指令步骤:
1.取栈最顶部内容为下一步指令的IP,且sp=sp+2
2.取下一个值为下一步指令的CS,且sp=sp+2
由此归纳:
指令名称 生效距离 特点
jxcz指令 短转移 当cx=0时生效
ret指令 近转移 取栈顶部值为IP
retf指令 远转移 取栈值为IP,再取栈值为CS
检测点10.1
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,_____ ;根据栈先进后出的特点,此时应该先把cs的值放进去
push ax
mov ax,____ ;再把IP的值放进去,应该为0
push ax
retf
code ends
栈先进后出,执行retf指令的时候先去IP,那么先放CS的值,再放IP的,故前面的空答案是1000H,后面的是0.
10.2 call指令
10.3 依据位移进行转移的call指令
call指令实现的是近转移,执行call 标号指令会有以下步骤:
1. push IP
2. sp=sp+2
3. 转移至标号处(一个jmp指令,jmp near ptr 标号)
检测点10.2
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0 ;ax=0
1000:3 e8 01 00 call s ;将IP=6压入栈中,并转移到s处运行
1000:6 40 inc ax ;不执行部分
1000:7 58 s:pop ax ;转移到此处将栈中的值赋给ax,则ax=6
值得注意的是,第二行call s指令压入栈中的IP值不是3而是下一条将要执行指令的IP, 我个人理解是进行push IP操作的时候计算机以及准备好下一条将要执行的指令,所以压入的是6。
当然,这意味着和ret指令搭配使用的话将会更加方便!
10.4 转移的目的地址在指令中的call指令
相对于前面所讲的call指令来说,call far ptr 标号则是将IP和CS都压入栈中,并实现段间转移,有以下步骤:
1.sp=sp-2 ;入栈先让sp向上移动也就是减2再push
2.push CS ;出栈pop之后sp+2
3.sp=sp-2
4.push IP
5.转移到标号处
检测点10.3
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 9A 09 00 00 10 call far ptr s ;将目前的CS和IP压入栈中,IP=8,CS=1000H,并跳转到标号s处
1000:8 40 inc ax
1000:9 58 s:pop ax ;跳转到此处将8赋给ax,ax=8
add ax,ax ;ax=8+8=16 = 10H
pop bx ;bx=1000H
add ax,bx ;ax=ax+bx=10H+1000H=1010H
答案及解析如附注所言
10.5 转移地址在寄存器中的call指令
和前面的差不多,只不过把标号换成了寄存器,如:
mov ax,6
call ax
含义便是将IP压入栈中之后转移到偏移地址为6的地方就可以了
检测点10.4
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,6 ;ax=6
1000:2 ff d0 call ax ;将IP=5压入栈中,转移到倒数第二条指令
1000:5 40 inc ax
1000:6 mov bp,sp ;bp=sp
add ax,[bp] ;[sp]=[bp]=5,就是之前压入的IP值,故ax=ax+5=11=0BH
ax=0BH,解析如上图
10.6 转移地址在内存中的call指令
1.call word ptr 标号 相当于前面讲的 call 标号指令
2.call dword ptr 标号 相当于前面所讲的 call far ptr 标号指令
检测点10.5
assume cs:code
stack segment
dw 8 dup (0) ;如果不清楚dup的作用可以看我发的第八章内容有介绍
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ds,ax ;让ds同样指向栈
mov ax,0
call word ptr ds:[0EH] ;将所指IP压入栈中,sp=sp-2,故栈指针所在处为0EH,偏移到0EH处,还是原来的地方
inc ax ;ax=1
inc ax ;ax=2
inc ax ;ax=3
code ends
故ax=3,解析如上图
assume cs:code
data segment
dw 8 dup (0)
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,16
mov word ptr ss:[0], offset s ;将标号s处的地址存入ss:[0]处
mov ss:[2],cs
call dword ptr ss:[0] ;远转移,先将目前CS压入栈中,再压入IP
;转移到的CS:IP值为:ss:[2] : ss:[0],就是标号s处
nop ;nop指令占一个字节,什么都不会做,此处不执行
s:
mov ax,offset s ;将s的偏移地址赋给ax
sub ax,ss:[0cH] ;之前压入cs和ip,栈指针指向0CH处
;所指内容为nop指令,故ax=ax-[0CH]=标号s处地址-nop指令所在地址=1
mov bx,cs
sub bx,ss:[0eH] ;之前压入的CS值便在[0EH]处
;这些指令都在同一个段中故CS相等,所以bx=0
code ends
end start
ax=1,bx=0 解析如上,这题看不懂我觉得最大的原因可能是栈的相关知识不太熟,可以先复习一下
10.7 call和ret的配合使用
call指令会存CS和IP入栈,ret指令会读取CS和IP出栈,这意味着这两者功能互补,我们可以用call指令留下坐标去执行别的程序然后用ret指令返回
codesg segment
start:
mov ax,datasg
mov ds,ax
mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,0
call s
mov ax,4c00h
int 21h
s:inc ax
ret
codesg ends
end start
这是我写的一个简单的示例,初始ax=0,call s指令留下坐标之后转移到标号s处,另 ax自加1,再执行ret指令取回坐标回到原来的地方结束程序,此时ax由0变1.
不光如此,我们可以在子程序中使用循环,并且加入栈来存储cx的值,类似于有一个函数专门实现循环功能的C语言程序。
10.8 mul指令
我们前面所学的div除法指令允许一个数是8位另一个数是16位,而mul乘法指令只允许两个数都是8位或者都是16位
位数 两个数存放的位置 结果
8位 一个数存放在AL中另一个在8位reg或内存中 AX
16位 一个数存放在AX中另一个在16位reg或内存中 高位DX 低位AX
我将上面的例子修改了一下,变成了一个计算100立方的程序:
codesg segment
start:
mov ax,datasg
mov ds,ax
mov ax,stacksg
mov ss,ax
mov sp,16
mov al,100
call s
mov ax,4c00h
int 21h
s:mov bl,al
mul bl
mul bl
ret
codesg ends
end start
10.9模块化程序设计
10.10 参数与结果传递的问题
用书上的两个例子加深理解:
10.12 寄存器冲突的问题
实验10
1.显示字符串,我觉得这个问题的亮点有两处:用行号和列号用mul指令计算偏移地址,以及用datasg段末尾的0利用jcxz指令结束程序。
关于问题描述就不过多赘述了,直接附上答案:
assume cs:codesg,ds:datasg
datasg segment
db 'welcome to masm!',0
datasg ends
codesg segment
start:mov dh,8
mov dl,3
mov cl,2
mov ax,datasg
mov ds,ax
mov si,0
mov di,0
mov ax,0B800H ;跟实验9一样,用80×25彩色字符模式显示缓冲区,地址从这里开始
mov es,ax
mov al,160
mul dh ;这里开始做乘法,dh*al设置成第八行的地址,结果默认在ax中
mov bx,ax ;将ax的值放到bx中,我们待会还会用到ax
mov al,2 ;2是2字节的意思
mul dl ;这个介绍列的偏移地址,相乘的结果还是放到ax中
add bx,ax ;将列和行的偏移地址相加就是8行3列
mov dh,cl ;cl只能为0,因为需要用cx来判断jcxz指令是否跳转,所以我觉得题目让cl=2多此一举,这个颜色数据肯定要赋给别的寄存器的
mov ch,0 ;我在这里将ch=0,那么我们将字符赋给cl
;如果到最后面的0赋给了cl,那么cx=0,jcxz指令就会跳转结束程序!
show_str:mov cl,ds:[si]
jcxz ok ;如果cx=0,那么跳到标号ok处,如果不为0则执行下面的
mov dl,cl ;cl不是0那就是0前面的字符内容,我们将这些字符赋到dl中,高位dh已经在前面赋了颜色属性数据
mov es:[di+bx],dx ;将整个dx值打包放到显示地址中,bx就是我们前面计算的偏移地址
inc si
add di,2
jmp short show_str ;弄完下一个字符重新跳回到show_str处搞下一个
ok:mov ax,4c00h ;如果轮到最后面是0jxcz指令就会跳到这里,完美符合题目要求!
int 21h
codesg ends
end start
附上结果图: