call和ret指令都是转移指令,它们都修改IP,或者同时修改CS和IP
ret和reft
ret指令用栈中的数据,修改IP的内容,从而实现近转移;
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移;
ret指令执行步骤:
1(IP) = ((SS)* 16 + (SP))
2(SP)= (SP)+2
retf指令执行步骤:
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指令执行步骤
1 将当前的IP或者CS和IP压入栈中;
2 转移
call不能实现短转移,但指令实现的转移方法和jmp指令的原理相同;
依据位移转移:
call标号(将当前的IP压入栈后,转到标号处执行指令)
近转移,16位转移范围,也是使用相对的转移地址;
执行步骤
1(SP)=(SP)- 2;
((SS)* 16 + (SP)) =(IP)
2 (IP)=(IP)+ 16位移
16 位位移 = 标号处的地址 – call 指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出
相当于汇编指令
push IP
imp near ptr 标号
直接使用地址进行转移
(远转移):call far ptr 标号 实现段间转移
执行步骤:
1(SP)=(SP)- 2;
((SS)* 16 +(SP)) =(CS);
(SP)=(SP)- 2;
((SS)* 16 +(SP)) =(IP);
2(CS)= 标号所在的段地址
(IP)= 标号所在的偏移地址
相当于汇编指令
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指令
1 call word ptr 内存单元地址
相当于汇编
push IP
jmp word ptr 内存单元地址
2 call dword ptr 内存单元地址
相当于汇编
push CS
push IP
jmp dword ptr 内存单元地址
call和ret的配合使用
可以写一段具有一定功能的程序段,我们称为子程序,在需要的时候,用call指令去执行。可是执行完子程序后,如何让CPU接着call指令向下执行?call指令转去执行子程序之前,call指令后面的指令地址存储在栈中,所以可以在子程序的后面使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行。
具有子程序的源程序的框架如下
assume cs:code
code segment
main: :
:
call sub1 ;调用子程序sub1
:
:
move ax, 4c00h
int 21
sub1: : ;子程序sub1开始
:
call sub2 ;调用子程序sub2
:
:
ret ;子程序返回
sub2: : ;子程序sub2开始
:
:
ret ;子程序返回
code ends
end main
mul指令
mul是乘法指令
注意:1,两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果都是8位,一个默认放在 AL中,另一个放在8位reg或者内存字节单元中;如果是19位,一个默认在AX中,另一个 放在16位reg或者内存子单元中。
2,结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果默认高位放在DX中, 低位在AX中。
格式如下,
mul reg
mul 内存单元
内存单元可以用不同的寻址方式,比如:
mul byte ptr ds : [0]
含义:(ax) = (al) * ((ds)*16 +0);
mul word ptr [bx + si + 8]
含义:(ax) =(ax)*((ds)* 16 +(bx)+(si) + 8) 结果的低16位
(dx) =(ax)*((ds)* 16 +(bx)+(si) + 8) 结果的高16位
参数和结果传递的问题
设计一个子程序,可以根据提供的N,来计算N的3次方。
这里面就有两个问题
(1)将参数N存放在什么地方?
(2)计算得到的数值,存放在什么地方?
很显然,可以用寄存器来存储,可以将参数存放到bx中,因为子程序中要计算N*N*N,可以使用多个mul指令,为了方便,可以将结果放到dx和ax中。
对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取到返回值:子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
编程,计算data段中第一组数据的3次方,结果保存在后面一组的dword单元中。
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 ;ds:si 指向第一组word单元
mov di,16 ;ds:di 指向第二组dword单元
mov cx, 8
s:mov bx,[si]
call cube
mov [di],ax
mov d[i].2,dx
add si,2 ;ds:si指向下一个word单元
add di,4 ;ds:di指向下一个dword单元
loop s
cube: move ax,bx
mul bx
mul bx
ret
code ends
end start
批量数据传递
前面的例程,子程序cube只有一个参数,放在bx中。如果有两个参数,那么可以放在两个寄存器中,如果参数过多显然是不可以的。
在这种时候,我们将批量数据存放到内存中,然后将他们所在的内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可以采用同样的方法。
设计一个程序,功能:将一个全是字母的字符串转换为大写。
这个子程序需要知道两件事,字符串的内容和字符串的长度。因为字符串中的字母可能很多,所以不便将整个字符串中的所有字母都直接传递给子程序。可以将字符串在内存中的首地址放在寄存器中传递给子程序。子程序需要循环,所有我们要将字符串长度传递给cx。
assume cs:code
data segment
db 'conversation'
data ends
code ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,12
call capital
mov ax,4c00h
int 21h
capital:and byte ptr [si], 11011111b
inc si
loop capital
ret
code ends
end start
寄存器的冲突问题
设计一个子程序,功能:将一个全是字母,以0结尾的字符串,转化位大写。
观察下面代码
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的使用,主程序要使用cx记录循环次数,课时子程序中也使用了cx,在执行子程序的时候,cx中保存的循环计数值被改变,使得主程序的循环出错。
解决这个问题的简捷方法是,在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前在恢复。可以用栈来保存寄存器中的内容。
我们编写子程序的标准框架如下
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret,retf)
我们改进一下子程序capital的设计:
capital: push cx
push si
change: mov cl, [si]
mov ch, o
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
注意出栈入栈的顺序