call和ret指令
call 和ret 指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。
1 ret和retf
ret 指令用栈中的数据,修改IP 的内容,从而实现近转移:
retf 指令用栈中的数据,修改CS 和IP 的内容,从而实现远转移。
CPU 执行ret 指令时,进行下面两步操作:
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
CPU 执行retf 指令时,进行下面4 步操作:
- (IP)=((ss)16+(sp))
- (sp)=(sp)+2
- (CS)=((ss)*16+(sp))
- (sp)=(sp)+2
CPU 执行ret 指令时,相当于进行:pop IP
CPU 执行retf 指令时,相当于进行:pop IP pop CS
2 call指令
CPU 执行call 指令时,进行两步操作:
- 将当前的IP或CS 和IP 压入栈中:
- 转移。
2.1 根据位移进行转移的call指令
call 标号(将当前的IP 压栈后,转到标号处执行指令)
CPU 执行此种格式的call 指令时,进行如下的操作:
-
(sp)=(sp)-2
((ss)* l 6+(sp ))=(IP)
-
(IP)=(IP)+16 位位移
-
16 位位移=标号处的地址-call 指令后的第一个字节的地址
-
16位位移的范围为-32768~32767 ,用补码表示
-
16位位移由编译程序在编译时算出。
CPU 执行“ call 标号”时,相当于进行:
- push IP IP是指call下一条的指令IP 而不是要跳转的IP
- jmp near ptr 标号
2.2 转移目的地址在指令中的call指令
“ call far ptr 标号”实现的是段间转移。
CPU 执行此种格式的call 指令时,进行如下的操作。
- (sp)=(sp)-2
((ss)* 16+(sp))=(CS)
(sp)=(sp)-2
((ss)* l 6+(sp ))=(IP) - (CS)=标号所在段的段地址
(IP)=标号在段中的偏移地址
相当于进行:
push CS
push IP
jmp far ptr 标号
2.3 转移地址在内存单元的call指令
- call word ptr 内存单元地址
CPU 执行“ call word ptr 内存单元地址”时,相当于进行:
push IP
jmp word ptr 内存单元地址
- call dword ptr 内存单元地址
CPU 执行“ call dword ptr 内存单元地址”时,相当于进行:
push CS
push IP
jmp dword ptr 内存单元地址
3 构造子程序
使用call和ret指令实现执行子程序,子程序执行结束在继续主程序
assume cs:code
code segment
main:.
.
.
call sub1
.
.
.
mov ax,4c00h
int 21h
sub1:.
.
.
call sub2
.
.
.
ret
sub2:.
.
.
ret
code ends
end main
assume cs : code
stack segment
db 8 dup (0)
db 8 dup (0)
stack ends
code segment
start : mov ax,stack
mov ss,ax
mov sp, 16
mov ax,1000
call s
mov ax,4c00h
int 21h
s :add ax,ax
ret
code ends
end start
4 mul指令
mul 是乘法指令,使用mul做乘法的时候,注意以下两点。
两个相乘的数:两个相乘的数,要么都是8 位,要么都是16 位。
- 如果是8 位,一个默认放在AL 中,另一个放在8 位reg 或内存字节单元中
- 如果是16 位,一个默认在AX 中,另一个放在16 位reg 或内存字单元中。
结果:如果是8 位乘法,结果默认放在AX 中;如果是16 位乘法,结果高位默认在DX 中存放,低位在AX 中放。
- 计算100*0
100 和10 小于255 ,可以做8 位乘法,程序如下。
mov al,100
mov bl,10
mul bl
结果:(ax)=l000(03E8H)
- 计算100*10000
100 小于255 ,可10000 大于255 ,所以必须做16 位乘法,程序如下。
mov a x ,100
mov bx,10000
mul bx
结果:(ax)=4240H, (dx)=OOOFH (F4240H=IOOOOOO)
5 寄存器冲突
将data 段中的字符串全部转化为大写。
assume cs:code
data segment
db ’ word' , 0
db ’ unix' ,0
db ’ wind' ,0
db ’ good’,o
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov cx ,4
s: mov si,bx
call capital
add bx,5
loop s
mov ax,4c00h
int 2lh
capital:mov cl, [si]
mov ch , O
jcxz ok
and byte ptr [s 工J , llOlllllb
inc si
jmp short capital
ok:ret
code ends
end start
问题在于cx 的使用,主程序要使用cx 记录循环次数,可是子程序中也使用了, 在执行子程序的时候, cx 中保存的循环计数值被改变,使得主程序的循环出错。从上面的问题中,实际上引出了一个一般化的问题:子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。
解决这个问题的简捷方法是,在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。可以用找来保存寄存器中的内容。
capital: push ex
push si
change : mov cl , [ si]
mov ch,0
jcxz ok
and byte ptr [sl], llOlllllb
inc s
jmp short change
ok: pop si
pop cx
ret