第十章 call和ret指令
call和ret指令都是转移指令,它们都能修改ip,或同时修改cs和ip
10.1 ret和retf
1.ret指令用栈中的数据,修改ip的内容,从而实现【近转移】
CPU执行ret指令时,进行下面两步操作:
1.(ip)=((ss)*16+(sp)) ;ip的值修改为栈顶的内容
2.(sp)=(sp)+2 ;栈顶移动
2.retf指令用栈中的数据,修改cs和ip的内容,从而实现【远转移】
CPU执行retf指令时,进行下面四步操作
1.(ip)=((ss)*16+(sp)) ;ip的内容修改为栈顶的内容
2.(sp)=(sp)+2 ;栈顶移动
3.(cs)=((ss)*16+(sp)) ;cs的内容修改为栈顶移动之后,栈顶的内容
4.(sp)=(sp)+2 ;栈顶移动
栈顶的两个字,低位字修改为ip,高位字修改为cs
3.可以看出,如果我们用汇编语法来解释ret和retf指令,则
1.CPU执行ret指令,相当于
pop ip
2.执行retf指令时,相当于
pop ip
pop cs
10.2 call指令
1.call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作:
1.将当前的ip或cs和ip压入栈中
2.转移
2.call指令不能实现短转移,除此之外,
call指令实现转移的方法和jmp指令的原理相同
10.3 依据位移进行转移的call指令
1.CPU执行“call 标号”这种格式的call指令时,进行如下操作:
1.(sp)=(sp)-2 ;栈顶移动
2.((ss)*16+(sp))=(ip) ;当前ip内容压栈
3.(ip)=(ip)+16位位移 ;跳转到标号处
2.call指令格式:call 标号
相当于执行:
push ip
jmp near ptr 标号
10.4 转移的目的地址在指令中的call指令
1.指令格式:call far ptr 标号
实现的是段间转移
2.执行这种格式的call指令时CPU的操作
1.(sp)=(sp)-2 ;栈顶移动
2.((ss)×16+(sp))=(cs) ;先把cs压栈
3.(sp)=(sp)-2 ;栈顶移动
4.((ss)×16+(sp))=(ip) ;然后把ss压栈
3.CPU执行“call far ptr 标号”时,相当于进行
push cs
push ip
jmp far ptr 标号
10.5 转移地址在寄存器中的call指令
1.指令格式:call 16位寄存器
2.执行这种指令时,在CPU中的操作
1.(sp)=(sp)-2
2.((ss)×16+(sp))=(ip)
3.(ip)=(16位寄存器)
3.相当于
push ip
jmp 16位寄存器
10.6 转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式:
1.call word ptr 内存单元地址
汇编语法解释
push ip
jmp word ptr 内存单元地址
2.call dword ptr 内存单元地址
汇编语法解释
push cs ;cs存放在高位
push ip ;ip存放在低位
jmp dword ptr 内存单元地址
10.7 call和ret的配合使用(重要)
assume cs:code
code segment
start:
mov ax,1
mov cx,3
call s ; 先push 当前ip到栈,再jmp到s处
mov bx,ax ;(bx)=? 2的3次方 8
mov ax,4c00h
int 21h
s:
add ax,ax
loop s
ret ;pop 之前存入的ip
code ends
end start
10.8 mul指令
相乘的两个数;要么都是8位,要么都是16位
1.8位:AL中和8位寄存器或内存字节单元中
AL中的内容作为被乘数
结果放在AX中
2.16位:AX中和16位寄存器或内存字单元中
AX中的内容作为被乘数
结果放在DX(高位)和AX(低位)中。
3.格式如下:
mul 寄存器
mul 内存单元(byte ptr或 word ptr指明是字还是字节)
10.9 模块化程序设计
从上面我们看到,call与ret指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是必不可少的。因为现实的问题比较复杂,对现实问题进行分析时,把它转化成为相互联系、不同层次的子问题,是必项的解决方法。
而call和ret指令对这种分析方法提供了程序实现上的支持。利用call和ret指令,我们可以用简洁的方法,实现多个互相联系、功能独立的子程序来解决一个复杂的问题。
10.10 参数和结果传递的问题
子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。
【编程】计算data段中第一组数据的3次方,结果保存在后面一组dword单元中
assume cs:code,ds:data
data segment
dw 1,2,3,4,5,6,7,8
dw 0,0,0,0,0,0,0,0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0;
mov cx,8
call cube ; 先push 当前ip到栈,再jmp到cube处
mov ax,4c00h
int 21h
cube: ;参数bx =n,结果 dx:ax = n^3
mov bx,[si ]
mov ax,bx
mul bx
mul bx
mov [si+10h],ax ;下一行的位置,偏移16字节
add si,2
loop cube;
ret ;pop 之前存入的ip
code ends
end start
10.11 批量数据的传递
使用寄存器、内存、栈传递数据
【编程】将一个全是字母,以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
10.12 寄存器冲突的问题
【实验十 编写子程序】
1.显示字符串
子程序描述名称: show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围0-24),(dl)-列号(取值范围0-79),(cI)=颜色,ds:si指向字符串的首地址
返回:无
应用举例:在屏幕的8行3列,用绿色显示data 段中的字符串。
assume cs:code,ds:data
data segment
db 'Welcome to masm!',0
data ends
code segment
start:
mov dh,8 ;dh装行号(范围:1--25)
mov dl,3 ;dl装列号(范围:1--80)[注:每超过80等于行号自动加1]
mov cl,2 ;cl中存放颜色属性(0cah为红底高亮闪烁绿色属性)
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str: ;显示字符串的子程序[定义开始]
mov al,0A0h ;大家还记得吧?每行是有80*2 == 160个字节 == 0A0h个字节内容,则第n行
dec dh ;行号在显存中下标从0开始,所以减1
mul dh ;相当于从第 (n-1)*0A0h 个Byte单元开始……
mov bx,ax ;定位好的位置偏移地址存放在bx里(行)
mov al,2 ;每个字符占两个字节
mul dl ;定位列,结果ax存放的是定位好的列的位置
sub ax,2 ;列号在显存中下标从0开始,又因为偶字节存放字符,所以减2
add bx,ax ;此时bx中存放的是行与列号的偏移地址
mov ax,0B800h ;显存开始的地址
mov es,ax ;es中存放的是显存的第0页(共0--7页)的起始的段地址
mov di,0 ;di指向显存的偏移地址,确定指向下一个要处理的字符的位置
mov al,cl ;cl是存放颜色的参数,这时候al存放颜色了,因为cl下边要用来临时存放要处理的字符
mov ch,0 ;下边cx存放的是每次准备处理的字符
s:
mov cl,ds:[si] ;ds:[si]指向“Welcome to masm!”,0
jcxz ok ;当cl的值为0时候,cx == 0, 则发生跳转,到OK处结束处理!
mov es:[bx+di],cl ;偶地址存放字符
mov es:[bx+di+1],al ;奇地址存放字符的颜色属性
inc si
add di,2 ;指向了下个字符
jmp short s ;无条件跳转,jcxz是离开的关键跳!
ok:
ret ;显示字符串的子程序[定义结束]
code ends
end start
2.解决除法溢出问题
子程序描述名称: divdw
功能:进行不会产生溢出的除法运算,被除数为dword 型,除数为word 型,结果为dword 型。
参数:(ax)=dword型数据的低16位,(dx)=dword型数据的高16位,(cx)=除数
返回:(dx)-结果的高16位,(ax)=结果的低16位,(cx)=余数
应用举例:计算1000000/10(F4240H/OAH)
assume cs:code,ss:stack
stack segment
dw 8 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,10h
mov ax,4240h
mov dx,0fh
mov cx,0ah
call divdw
mov ax,4c00h
int 21h
divdw: ;子程序定义开始
push ax ;低16位先保存
mov ax,dx ;ax这时候的值是高16位了
mov dx,0 ;dx置0是为了不影响下边余数位,使得高16位为0
div cx ;H/N 0fh/0ah dx(余数)=0005h ax(商)=0001h
mov bx,ax ;ax,bx的值为(int)H/N ,dx的值为(rem)H/N
pop ax ;ax的值现在是L,这个时候上次的余数dx变成了高位
;注意,16位除法的时候默认被除数DX为高16位,AX为低16位
div cx ;L/N,54240h/0ah dx(余数)=0h ax(商)=86a0h
mov cx,dx
mov dx,bx
ret ;子程序定义结束
code ends
end start
3.数值显示
【课程设计1】