CALL和RET指令---汇编学习笔记
CALL和RET指令
call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。
10.1 ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移;
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
CPU执行ret指令时,进行下面2步操作(相当于pop IP
):
CPU执行retf指令时,进行下面4步操作(相当于pop IP AND pop CS
):
检测点 10.1
补全程序,实现从内存1000:0000处开始执行指令。
assume cs:code
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
;补全下面一条指令
mov ax,1000h
push ax
;补全下面一条指令
mov ax,0h
push ax
retf
code ends
end start
实验结果如下:
10.2 call指令
CPU执行call指令时,进行2步操作:
- 将当前的IP或CS和IP压入栈中
- 转移
10.3 依据位移进行转移的call指令
指令格式:call 标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此种格式的call指令时,进行如下操作(相当于执行了push IP AND jmp near ptr 标号
):
检测点 10.2
下面的程序执行后,ax中的数值为多少?
内存地址 | 机器码 | 汇编指令 | ax |
---|---|---|---|
1000:0 | b8 00 00 | mov ax,0 | ax=0000h |
1000:3 | e8 01 00 | call s | ip=0006h |
1000:6 | 40 | inc ax | 没有执行 |
1000:7 | 58 | s:pop ax | ax=0006h |
发现,push的IP是执行指令后的IP。
10.4 转移的目的地址在指令中的call指令
call far ptr 标号
实现的是段间转移。
CPU执行此种格式的call指令时,进行如下操作(相当于执行push CS AND push IP AND jmp far ptr 标号
):
检测点 10.3
下面的程序执行后,ax中的数值为多少?
内存地址 | 机器码 | 汇编指令 | ax |
---|---|---|---|
1000:0 | b8 00 00 | mov ax,0 | ax=0000h |
1000:3 | 9A 09 00 00 10 | call far ptr s | cs=1000h,ip=0008h |
1000:8 | 40 | inc ax | 没有执行 |
1000:9 | 58 | s: pop ax | ax=0008h |
1000:10 | pop ax | ax=1000h |
10.5 转移地址在寄存器中的call指令
指令格式:call 16为reg
功能:
相当于执行了push IP AND jmp 16位reg
。
检测点 10.4
下面的程序执行后,ax中的数值为多少?
内存地址 | 机器码 | 汇编指令 | ax |
---|---|---|---|
1000:0 | b8 06 00 | mov ax,6 | ax=0006h |
1000:2 | ff d0 | call ax | ip=0005h |
1000:5 | 40 | inc ax | 没有执行 |
1000:6 | 8b ec | mov bp,sp | bp=sp |
1000:8 | 03 36 00 | add ax,[bp] | ax=000Ah |
注意,最后一条指令是add指令。
10.6 转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式。
(1)call word ptr 内存单元地址
,相当于push IP AND jmp word ptr 内存单元地址
。
(2)call dword ptr 内存单元地址
,相当于push CS AND push IP AND jmp dword ptr 内存单元地址
。
检测点 10.5
(1)下面的程序执行后,ax中的数值为多少?(注意:用call指令的原理来分析,不要在Debug中单步跟踪来验证你的结论。对于此程序,在Debug中单步跟踪的结果,不能代表CPU的实际执行结果)
assume cs:code
stack segment
dw 8 dup(0)
stack ends
code segment
start:
mov ax,stack ;ax=stack
mov ss,ax ;ss=ax
mov sp,16 ;sp=16
mov ds,ax ;ds=ax
mov ax,0 ;ax=0
call word ptr ds:[0EH] ;push ip AND jmp ds:[0eh]
inc ax ;上面的应该是这条指令的ip,jmp ds:[0eh]应该
;转跳这条指令,那么ax=1
inc ax ;ax=2
inc ax ;ax=3
mov ax,4c00h
int 21h
code ends
end start
这真的很奇怪的程序,会懵!
(2)下面的程序执行后,ax和bx中的数值为多少?
assume cs:code
data segment
dw 8 dup(0)
data ends
code segment
start:
mov ax,data ;ax=data
mov ss,ax ;ss=ax
mov sp,16 ;sp=16
mov word ptr ss:[0],offset s ;ss:[0]=s的地址
mov ss:[2],cs ;ss:[2]=cs
call dword ptr ss:[0] ;call (cs):(s的地址)
nop ;ss:[0ch]=这条指令的地址
;ss:[0eh]=cs
s:
mov ax,offset s ;ax=s的地址
sub ax,ss:[0ch] ;ax=ax-ss:[0ch] = 1
mov bx,cs ;bx=cs
sub bx,ss:[0eh] ;bx=bx-cs=0
mov ax,4c00h
int 21h
code ends
end start
实验结果如下:
10.7 call和ret的配合使用
现在来看一下,如何将它们配合使用来实现子程序的机制。
问题 10.1
下面程序返回前,bx中的值是多少?
assume cs:code
code segment
start:
mov ax,1 ;1.ax=1
mov cx,3 ;2.cx=3
call s ;3.push 下一条指令的IP,jmp s处
mov bx,ax ;6.(bx)=8
mov ax,4c00h
int 21h
s:
add ax,ax
loop s ;4.ax=2^3次方,后结束这个loop
ret ;5.pop ip,就返回到6处
code ends
end start
具有子程序的源程序框架如下:
assume cs:code
code segment
main:
..
..
call sub1 ;调用子程序sub1
..
mov ax,4c00h
int 21h
sub1:
.. ;子程序sub1开始
call sub2 ;调用子程序sub2
..
ret ;sub1子程序返回
sub2:
.. ;子程序sub2开始
..
ret ;sub2子程序返回
code ends
end main
10.8 mul指令
mul是乘法指令,使用mul做乘法的时候注意以下两点。
(1)两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存单元中。
(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中存放。
格式如下:
mul reg
mul 内存单元
示例程序:计算100*10000
mov ax,100
mov bx,100000
mul bx
结果:。
10.9 模块化程序设计
call与ret指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是必不可少的。因为现实的问题比较复杂,对现实问题进行分析时,把它转化成互相联系、不同层次的子问题,是必须的解决方法,我们可以用简捷的方法,实现多个相互联系、功能独立的子程序来解决一个复杂的问题。
10.10 参数和结果传递的问题
子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。
比如,设计一个子程序,可以根据提供的N,来计算N的3次方。这里面就有两个问题:
- 将参数N存储在什么地方?
- 计算得到的数值,存储在什么地方?
显然,可以用寄存器来存储,可以将参数放在bx中;因为子程序中要计算N^3,可以使用多个mul指令,为了方便,可将结果放在dx和ax中。子程序如下。
;说明:计算N的3次方
;参数:(bx)=N
;结果:(dx:ax)=N^3
cube:
mov ax,bx
mul bx
mul bx
ret
10.11 批量数据的传递
如果有两个参数,那么可以用两个寄存器来存储,可是如果需要传递的数据有3个、4个或更多直至N个,该怎样存储呢?
这种时候,我们将批量数据放在内存中,然后将它们所在的内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的放回结果,也可用同样的方法。
例如:编程,将data段中的字符串转化为大写。
assume cs:code
db 'conversation'
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向字符串所在空间的首地址
mov cx,12 ;cx存放字符串的长度
call capital
mov ax,4c00h
int 21h
capital:
and byte ptr [si],11011111b
inc si
loop capital
ret
code ends
end start
10.12 寄存器冲突的问题
寄存器出现冲突,可以使用栈来解决。这个小节比较细,自己看书!
以后,我们编写子程序的标准框架如下:
子程序开始:
子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret、retf)
实验10 编写子程序
1. 显示字符串
问题:
显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。
子程序描述
名称: show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:
返回:无
应用举例:在屏幕的8行3列,用绿色显示data段中的字符串。
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start:
mov dh,8
mov dl,3
mov cl,2
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str:
push ax
push bx
push cx
push es
push si
;屏幕是B8000~BFFFFF这段
;开始位置应该为dh*80*2+dl*2且MAX=4160<2^15
mov ax,0B800h
mov es,ax
;计算开始位置
;计算行
mov ah,0
mov al,dh
mov bh,160
;乘8位,dx不变
mul bh
;计算具体开始位置
mov bh,0
mov bl,dl
add ax,bx
add ax,bx
;得到开始位置
mov bx,ax
;开始显示
mov ah,cl
color:
mov cl,ds:[si]
mov ch,0
jcxz ok
mov al,ds:[si]
mov es:[bx],al
mov es:[bx+1],ah
add bx,2
inc si
jmp short color
ok:
pop si
pop es
pop cx
pop bx
pop ax
ret
code ends
end start
实验结果:
2. 解决除法溢出的问题
问题:
前面讲过,div指令可以做除法。当进行8位除法的时候,用al存储结果的商,ah存储结果的余数;进行16位除法的时候,用ax存储结果的商,dx存储结果的余数。可是,现在有一个问题,如果结果的商大于al或ax所能存储的最大值,那么将如何?
比如:
mov bh,1
mov ax,1000
div bh
此时,al放不下1000,这就是除法溢出。
子程序描述
**名称:**divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:
返回:
应用举例:计算1000000/10(F4240H/0AH)
assume cs:code,ss:stack
stack segment
dw 8 dup(0)
stack ends
code segment
start:
mov ax,4240h
mov dx,000fh
mov cx,0ah
call divdw
mov ax,4c00h
int 21h
divdw:
push bx
push es
;高
mov es,dx
;低
mov bx,ax
;X/N = int(H/N)*65536+[rem(H/N)*65536+L]/N
;int(H/N)
;rem(H/N)
mov ax,es
mov dx,0
div cx
;int(H/N)*65536,ax存储商,es存储高位结果
mov es,ax
;rem(H/N)*65536+L,dx存储余数,dx存储高位,bx是低位
mov ax,bx
;[rem(H/N)*65536+L]/N,得到低位结果,用ax存储
div cx
;余数是dx
mov cx,dx
;此时得出2个结果了,低位结果在AX中,高位结果在es中
mov dx,es
pop es
pop bx
ret
code ends
end start
实验结果:
3. 数值显示
问题:
编程,将data段中的数据以十进制的形式显示出来。
子程序描述:
名称: dtoc
功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符。
参数:
返回:无
应用举例:编程,将数据12666以十进制的形式在屏幕的8行3列,用绿色显示出来。在显示时,我们调用本次实验中的第一个子程序show_str。
assume cs:code,ds:data
data segment
db 10 dup (0)
data ends
code segment
start:
mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
dtoc:
push ax
push bx
push cx
push dx
push si
push di
;初始化
mov bx,10
mov dx,0
mov di,0
;求余数
s:
mov cx,ax
jcxz rev
div bx
;ax是商,dx是余数
add dl,30h
mov ds:[si],dl
inc si
;dx需要置0
mov dx,0
jmp short s
rev:
push cx
;逆序,循环si/2次结束
mov dx,0
mov ax,si
mov bx,2
div bx
mov cx,ax
;si是长度,下标是从0开始,所以需要-1
dec si
rev_loop:
;前缀
mov al,ds:[di]
;后缀
mov ah,ds:[si]
;交换
mov ds:[si],al
mov ds:[di],ah
;前缀自增
inc di
;后缀自减
dec si
loop rev_loop
pop cx
jmp ok
ok:
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret
show_str:
push ax
push bx
push cx
push es
push si
;屏幕是B8000~BFFFFF这段
;开始位置应该为dh*80*2+dl*2且MAX=4160<2^15
mov ax,0B800h
mov es,ax
;计算开始位置
;计算行
mov ah,0
mov al,dh
mov bh,160
;乘8位,dx不变
mul bh
;计算具体开始位置
mov bh,0
mov bl,dl
add ax,bx
add ax,bx
;得到开始位置
mov bx,ax
;开始显示
mov ah,cl
color:
mov cl,ds:[si]
mov ch,0
jcxz ok1
mov al,ds:[si]
mov es:[bx],al
mov es:[bx+1],ah
add bx,2
inc si
jmp short color
ok1:
pop si
pop es
pop cx
pop bx
pop ax
ret
code ends
end start
这题做了一天,出错地方在div bx那条语句,如果直接执行div bx,会出现死循环,可是debug又不出现死循环。最后将dx置0即可
–
课程设计
改天做,这里放置超链接!
上一篇: 汇编语言实现简单的计算器
下一篇: Hello, Assembly !