[汇编语言]CALL和RET指令


一、ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移
ref指令用栈中的数据,修改CS和IP的内容,从而实现远转移

CPU执行ret指令时,进行下面两步操作

(IP)=((SS)*16+SP)
(SP)=(SP)+ 2

CPU执行retf指令时,进行下面4步操作:

(IP)=((SS)*16+SP)
(SP)=(SP)+ 2
(CS)=((SS)*16+SP)
(SP)=(SP)+ 2

可以看出来,如果我们用汇编语法来解释ret和ref指令,则:
CPU执行ret指令时,相当于进行pop IP
CPU执行ref指令时,相当于进行:pop IP,pop CS

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

上面程序,ret执行后,(IP) = 0,CS:IP指向代码段的第一条指令mov ax,4c00h


二、call指令

CPU执行call指令时,进行两步操作:
(1)将当前的IP或CS和IP压入栈中
(2)转移

2.1 依据位移进行转移的call指令

call 标号(将当前的IP压入栈,转到标号处执行指令)

CPU执行此种格式的call指令,进行如下操作:
(1)(sp)=(sp)- 2
((ss)*16+(sp))=(IP)
(2)(IP)= (IP)+16位位移
可以看出CPU要执行call 标号的时候,相当于是进行:
push IP
jmp near ptr 标号

2.2 转移的目的地址在指令中的call指令

前面讲到的call指令,其对应的机器指令中并没有转移的目的地址,而是相当于当前IP的转移位移。

call far ptr 标号实现的是段间转移

CPU执行此种格式的call指令时,进行如下的操作:

(1)
(sp)=(sp)- 2
((ss)*16+(sp))=(CS)
(sp)=(sp)- 2
((ss)*16+(sp))=(IP)

(2)
(IP)= 标号所在段的偏移地址
(CS)= 标号所在段的段地址

2.3 转移地址在寄存器中的call指令

指令格式:call 16位reg

相当于是进行:
push IP
jmp 16位reg

2.4 转移地址在内存中的call指令

转移地址在内存中的call指令有两种格式:

  1. call word ptr 内存单元地址
    CPU执行call word ptr 内存单元地址时,相当于是进行:
push IP
jmp word ptr 内存单元地址

比如下面指令:

mov sp,10h
mov ax,0123h
mov dx:[0],ax
call word ptr ds:[0]

执行后,(IP)=0123H,(SP)=0EH

  1. call dword ptr内存单元地址
push IP
push CS
jmp word ptr 内存单元地址

三、call和ret的配合使用

下面程序返回前,bx中的值是多少?

assume cs:code
code segment

start:
	mov ax,1
	mov cx,3
	call segment
	mov bx,ax ;(bx)=?
	mov ax,4c00h
	int 21h
	
s:	add ax,ax
	loop segment
	ret

code ends
end
  1. CPU将call s指令的机器码读入,IP指向了call s后的指令mov bx,ax 然后CPU执行call s的指令,将当前IP值(mov bx,ax的偏移地址)压入栈,并将IP的值改变为标号s处的偏移地址

  2. CPU从标号s处开始执行指令,loop循环完毕后,(ax)= 8

  3. CPU将ret指令的机器码读入,IP指向ret指令后面的内存单元,然后CPU执行ret指令,从栈中弹出一个值(call s先前压入的mov bx,ax指令的偏移地址)送入IP中。则CS:IP指向指令mov bx,ax

  4. CPU从mov bx,ax开始执行指令,直到完成

所以程序执行完后,(bx)=8,计算2的N次方,N由cx提供

我们可以使用call和ret来实现子程序(写一个具有一定功能的程序段),子程序的框架如下:

assume cs:code
code segment

main:
 	
	...
	
	call subl ;调用子程序
	
	...
	
	mov ax,4c00h
	int 21h
	
sub1:	

	...
	call sub2 ; 调用子程序sub2
	...
	ret
	
sub2:

	...
	ret
	
code ends
end main

四、mul指令

mul是乘法指令,使用mul乘法的时候应该注意:

  1. 两个相乘的数:两个相乘的数位数应该一样(要么都是8位、要么都是16位),如果是8位,一个默认在AL,另外一个放在8位reg或是内存字节单元中,如果是16位,一个默认在AX,另外一个放在16位reg或是内存字节单元中
  2. 结果:如果是8位乘法,结果放在AX中,如果是16位,高位放入DX,低位放入AX

格式如下:

mul reg
mul 内存单元

可以用不同寻址方式给出:
mul byte ptr ds:[0]
含义:(ax) = (al) * ((ds)*16+0)

计算100*10

mov al,100
mov bl,10
mul bl

五、模块化程序设计

5.1 参数和结果传递的问题

子程序中一般都要根据参数处理一定的事物、处理后,将结果(返回值)提供给调用者,我们实际上就是在探讨,如何存储子程序需要的参数和产生的返回值

比如设置一个子程序,可以根据提供的N,计算N的3次方

这里就有两个问题,N存储在什么地方,计算得到的数值存储在什么地方?

很显然,可以用寄存器来进行存储,将参数放到bx中;因为子程序要计算NNN,可以使用多个mul指令,为了方便,将结果放在dx和ax中,子程序如下:

;说明:计算N的3次方
;参数:(bx)=N
;结果:(dx:ax)=N^3
cube:
mov ax,bx
mul bx
mul bx
ret

编程计算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

程序如下:

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	;ds:si指向下一个word单元
		add di,4	;ds:di指向下一个dword单元
		loop s
		
		mov ax,4c00h
		int 21h
		
cube:	mov ax,bx
		mul bx
		mul bx
		ret
		
code ends
end start

5.2 寄存器冲突问题

设计一个子程序,功能:将一个全是字母,以0结尾的字符串,转化为大写

程序要处理的字符串以0作为结尾符,这个字符串可以如下定义:
db ‘conversation’,0

这个子程序,字符串后面一定要有一个0,标记字符串的结束,字符串可以依次读取每个字符来进行检测,如果不是0,就进行大写的转化,是0就结束,可以用jcxz来检测0

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值