7 call和ret指令

call和ret指令

call 和ret 指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。

1 ret和retf

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

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

  1. (IP)=((ss)*16+(sp))
  2. (sp)=(sp)+2

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

  1. (IP)=((ss)16+(sp))
  2. (sp)=(sp)+2
  3. (CS)=((ss)*16+(sp))
  4. (sp)=(sp)+2

CPU 执行ret 指令时,相当于进行:pop IP

CPU 执行retf 指令时,相当于进行:pop IP pop CS

2 call指令

CPU 执行call 指令时,进行两步操作:

  1. 将当前的IP或CS 和IP 压入栈中:
  2. 转移。

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

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

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

  1. (sp)=(sp)-2

    ((ss)* l 6+(sp ))=(IP)

  2. (IP)=(IP)+16 位位移

  • 16 位位移=标号处的地址-call 指令后的第一个字节的地址

  • 16位位移的范围为-32768~32767 ,用补码表示

  • 16位位移由编译程序在编译时算出。

CPU 执行“ call 标号”时,相当于进行:

  • push IP IP是指call下一条的指令IP 而不是要跳转的IP
  • jmp near ptr 标号

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

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

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

  1. (sp)=(sp)-2
    ((ss)* 16+(sp))=(CS)
    (sp)=(sp)-2
    ((ss)* l 6+(sp ))=(IP)
  2. (CS)=标号所在段的段地址
    (IP)=标号在段中的偏移地址

相当于进行:

push CS
push IP
jmp far ptr 标号

2.3 转移地址在内存单元的call指令

  1. call word ptr 内存单元地址

CPU 执行“ call word ptr 内存单元地址”时,相当于进行:

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

CPU 执行“ call dword ptr 内存单元地址”时,相当于进行:

push CS
push IP
jmp dword ptr 内存单元地址

3 构造子程序

使用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
assume cs : code
stack segment
	db 8 dup (0)
	db 8 dup (0)
stack ends
code segment
	start : mov ax,stack
		    mov ss,ax
			mov sp, 16
            mov ax,1000
            call s
            mov ax,4c00h
            int 21h
            s :add ax,ax
            ret
code ends
end start

4 mul指令

mul 是乘法指令,使用mul做乘法的时候,注意以下两点。

两个相乘的数:两个相乘的数,要么都是8 位,要么都是16 位。

  • 如果是8 位,一个默认放在AL 中,另一个放在8 位reg 或内存字节单元中
  • 如果是16 位,一个默认在AX 中,另一个放在16 位reg 或内存字单元中。

结果:如果是8 位乘法,结果默认放在AX 中;如果是16 位乘法,结果高位默认在DX 中存放,低位在AX 中放。

  1. 计算100*0

100 和10 小于255 ,可以做8 位乘法,程序如下。
mov al,100
mov bl,10
mul bl
结果:(ax)=l000(03E8H)

  1. 计算100*10000

100 小于255 ,可10000 大于255 ,所以必须做16 位乘法,程序如下。
mov a x ,100
mov bx,10000
mul bx
结果:(ax)=4240H, (dx)=OOOFH (F4240H=IOOOOOO)

5 寄存器冲突

将data 段中的字符串全部转化为大写。

assume cs:code
data segment
    db ’ word' , 0
    db ’ unix' ,0
    db ’ wind' ,0
    db ’ good’,o
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 2lh
    capital:mov cl, [si]
    mov ch , O
    jcxz ok
    and byte ptr [s 工J , llOlllllb
    inc si
    jmp short capital
    ok:ret
code ends
end start

问题在于cx 的使用,主程序要使用cx 记录循环次数,可是子程序中也使用了, 在执行子程序的时候, cx 中保存的循环计数值被改变,使得主程序的循环出错。从上面的问题中,实际上引出了一个一般化的问题:子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。

解决这个问题的简捷方法是,在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。可以用找来保存寄存器中的内容。

capital: push ex
        push si
        
change : mov cl , [ si]
        mov ch,0
        jcxz ok
        and byte ptr [sl], llOlllllb
        inc s 
        jmp short change
ok: pop si
    pop cx
    ret
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值