第十章
call和ret都是转移指令,都修改IP或同时修改CS和IP,用于子程序的设计
10.1 ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移
retf指令用栈中的数据,修改CS和IP的内容,实现远转移
执行ret时:
1、(IP)=((ss)*16+(sp))
2、(sp)=(sp)+2
用汇编语法解释,相当于pop IP
执行retf时:
1、(IP)=((ss)*16+(sp))
2、(sp)=(sp)+2
3、(CS)=((ss)*16+(sp))
2、(sp)=(sp)+2
用汇编语法解释,相当于pop IP、pop CS
下面的程序中,ret指令执行后,(IP)=0,CS:IP指向代码段的第一条指令
assume cs:code
stack segment
db 16 dup(0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
下面的程序中,retf指令执行后,CS:IP指向代码段的第一条
assume cs:code
stack segment
db 16 dup(0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start
10.2 call指令
CPU指向call指令时,分两步
1、将当前的IP或CS和IP压入栈中
2、转移
call指令不能实现短转移,除此之外,call指令转移的方法与jmp指令原理相同
10.3 依据位移进行转移的call指令
call 标号:将当前IP压入栈后,转移到标号处执行指令
执行过程:
1、(sp)=(sp)-2
((ss)*16+(sp))=(IP)
2、(IP)=(IP)+16位位移
16位位移由编译器算出=标号处地址-call指令后的第一个字节的地址
范围-32768~32767
用汇编语言解释push IP
jmp near ptr 标号
下面程序执行后,ax中的数值为多少?
内存地址 机器码 汇编指令
1000:0 b8 00 00 mov ax,0
1000:3 e8 01 00 call s
1000:6 40 inc ax
1000:7 58 s:pop ax
CPU从内存中取第二条指令以后,IP指向下一条指令,(IP)=6,随后执行call指令,IP的值入栈,IP执行标号处的指令,执行后(ax)=6,由于还没遇到ret指令,所以ax中最终的数值是6
10.4 转移的目的地址在指令中的call指令
call far ptr 标号 实现的是段间转移
用汇编语言解释push CS
push IP
jmp far ptr 标号
10.5 转移地址在寄存器中的call指令
指令格式 call 16位reg
用汇编语法解释push IP
jmp 16位reg
下面的程序执行后,ax中的数值为多少
内存地址 机器码 汇编指令
1000:0 b8 06 00 mov ax,6
1000:3 ff d0 call ax
1000:5 40 inc ax
1000:6 mov bp,sp
add ax,[bp]
CPU从内存中取完call指令后,IP指向下一条指令,(IP)=5,执行call指令,(IP)的值入栈,跳转,在执行add指令时,因为用bp表示偏移地址时,默认段寄存器为ss,因此就是(ax)+之前压入栈中IP的值,最终(ax)=0bH
10.6 转移地址在内存中的call指令
有两种格式
第一种:call word ptr 内存单元地址
用汇编语言解释push IP
jmp word ptr 内存单元地址
执行下面指令后,(IP)=0123H,(sp)=0EH
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
第二种:call dword ptr 内存单元地址
用汇编语言解释push CS
push IP
jmp dword ptr 内存单元地址
10.7 call和ret的配合使用
一个具有一定功能的程序段,我们称为子程序,在需要的时候,用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
10.8 mul指令
mul是乘法指令,使用的注意点
1、两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中
2、结果如果是8位乘法,结果默认放在AX中,如果是16位乘法,结果高位默认在DX中存放,低位在AX中放
格式:mul reg/内存单元
内存单元可以用不同的寻址方式给出,如
mul byte ptr ds:[0]
mul word ptr [bx+si+8]
计算100*10000,由于10000大于255,所以必须做16位乘法
mov ax,100
mov bx,10000
mul bx
10.9 模块化程序设计
利用call和ret实现多个相互联系、功能独立的子程序解决一个复杂的问题
10.10 参数和结果的传递问题
用寄存器来存储参数和结构是最常用的方法
编程,计算data段中第一组数据的三次方,结果保留在后面一组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 [di].2.dx ; 存储高位数据
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
cube: mov ax,bx
mul bx
mul bx
code ends
end start
10.11 批量数据的传递
如果要传入多个参数,寄存器的数量是不足的,这种时候,我们将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序,对于批量返回的结果,用同样的方法
编程,将data段中的字符串转化为大写
assume cs:code
data segment
db 'conversation'
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0 ; ds:si指向批量数据所在空间的首地址
mov cx,12 ; cx存放字符串的长度
call captial
mov ax,4c00h
int 21h
capital: and byte ptr [si],11011111b ; 表明参与运算的数据大小
inc si
loop capital
ret
code ends
end start
注意,除了用寄存器传递参数外,还可以用栈来传递参数
10.12 寄存器冲突的问题
编程,将一个全是字母,以0结尾的字符串转化为大写
因为0标志着字符串结束,所以子程序不需要字符串的长度作为参数,用jcxz来检测0
但是这会出现一个问题:因为可能有不止一个字符串,因此需要循环将字符串变为大写,而在子程序中,jczx是根据cx的值进行跳转,因此子程序会改变cx的值,影响程序 的正确运行
在编写子程序时,注意子程序不要使用会和主程序产生冲突的寄存器,尽量使用其他寄存器代替
如果都必须用到同一个寄存器,则在子程序开始前将所用到的寄存器的内容先保存起来,可以用栈来保存,还要注意出栈的顺序
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: push cx
push si
change: mov cl,[si]
mov ch,0
jczx ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
code ends
end start
编写子程序的标准框架
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回