ret
和call
都是转移指令,都是修改IP的值,或同时修改CS和IP。它们经常被一起用来实现子程序的设计。
ret和retf
ret
指令用栈中的数据修改IP,实现的是近转移;retf
指令用栈中的数据修改CS和IP的值,实现远转移。
格式:直接用 ret
。
ret
指令执行时,cpu进行2步操作:
1. ip = ss * 16 + sp
2. sp = sp + 2
retf
指令执行时,cpu进行4步操作:
1. ip = ss * 16 + sp
2. sp = sp + 2
3. cs = ss * 16 + sp
4. sp = sp + 2
可以看出,ret
相当于汇编指令的
pop IP
retf
相当于
pop IP
pop CS
call指令
call
指令也是一个转移指令,执行格式:call 目标
(具体使用接下来说明),call的执行步骤:
- 将当前的IP或CS和IP入栈
- 转移
call不能实现短转移,但它实现转移的原理和jmp相同。
根据位移转移:call 标号
,近转移,16位转移范围,也是使用相对的转移地址。
执行步骤:
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (IP)=(IP)+16
所以执行这条命令相当于执行
push ip
jmp near ptr 标号
直接使用地址进行(远)转移:call far ptr 标号
,执行步骤:
- (SP)=(SP)-2
- ((SS)*16+(SP))=(CS)
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (CS)=标号所在的段的段地址
- (IP)=标号的偏移地址
所以执行call far ptr 标号
相当于执行
push cs
push ip
jmp far ptr 标号
使用寄存器的值作为call的跳转地址:call 16位reg
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (IP)=(16为reg)
相当于执行
push ip
jmp 16位reg
使用内存中的值作为call的跳转地址:call word ptr 内存单元地址
,当然还有call dword ptr 内存单元地址
,这样进行的就是远转移。
联合使用ret和call
联合使用ret和call实现子程序的框架:
assume cs:code
code segment
main:
···
call sub1
···
mov ax,4c00h
int 21h
sub1:
···
call sub2
···
ret
sub2:
···
ret
code ends
end main
mul指令
mul是乘法指令,使用时应注意,两个相乘的数,要么都是8位,要么都是16位,如果是8位,那么其中一个默认放在al中,另一个在一个8位reg或字节内存单元中;若是16位,则一个默认在ax中,另一个在16位reg或字内存单元中。如果是8位乘法, 则结果放在ax中,结果是16位;若是16位乘法,结果默认在ax和dx中,dx高位,ax低位,共32位。
格式:mul reg
或 mul 内存单元
,支持内存单元的各种寻址方式。
如mul word ptr [bx+si+8]
代表:
(ax)=(ax)*((ds)*16+(bx)+(si)+8)低16位
(dx)=(ax)*((ds)*16+(bx)+(si)+8)高16位
例:计算100*10
mov al,100
mov bl,10
mul bl
参数的传递和模块化编程
看下面一段程序:计算data中第一行的数的立方存在第二行
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cs,8
s:mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
cube:mov ax,bx
mul bx
mul bx
ret
code ends
end start
寄存器冲突
观察下面将data中的数据全转化为大写的代码:
assume cs:code
data segment
db 'word',0
db 'unix',0
db 'wind',0
db 'good',0
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 21h
capital:mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short capital
ok:ret
code ends
end start
这段代码有一个问题出在,主函数部分使用cx设置循环次数4次,在循环中调用了子函数,而子函数中有一个判断语句jcxz也是用了cx,并且在之前修改了cx的值,造成逻辑错误。虽然修改的方法有很多,但我们应遵循以下的标准:
- 编写调用子程序的程序不必关心子程序使用了什么寄存器
- 编写子程序不用关心调用子程序的程序使用了什么寄存器
- 不会发生寄存器冲突
针对这三点,我们可以如下修改代码:
···
capital:push cx
push si
change:mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok:pop si
pop cx
ret
···
虽然和上面的程序中没有冲突的是si,但我们保险起见,在子程序开始时将子程序用到的所有的寄存器的内容存入栈中,在返回之前在出栈回到相应寄存器中。这样无论调用子程序的程序使用了什么寄存器,都不会产生寄存器冲突。