汇编语言笔记(10)CALL和RET指令

  • Post author:
  • Post category:其他


call和ret指令都是转移指令,它们都修改IP,或者同时修改CS和IP

ret和reft

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

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

ret指令执行步骤:

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

2(SP)= (SP)+2

retf指令执行步骤:

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

2(SP)= (SP)+2

3(CS) = ((SS)* 16 + (SP))

4(SP)= (SP)+2

ret执行相当于汇编指令

pop IP

retf指令相当于汇编指令

pop IP

pop CS

call指令

call指令执行步骤

1 将当前的IP或者CS和IP压入栈中;

2 转移

call不能实现短转移,但指令实现的转移方法和jmp指令的原理相同;

依据位移转移:

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

近转移,16位转移范围,也是使用相对的转移地址;

执行步骤

1(SP)=(SP)- 2;

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

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

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

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

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

相当于汇编指令

push IP

imp near ptr 标号

直接使用地址进行转移

(远转移):call far ptr 标号 实现段间转移

执行步骤:

1(SP)=(SP)- 2;

((SS)* 16 +(SP)) =(CS);

(SP)=(SP)- 2;

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

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

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

相当于汇编指令

push CS

push IP

jmp far ptr

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

call 16位reg(寄存器)

执行步骤:

(SP)=(SP)- 2

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

(IP)= (16位reg)

相当于汇编

push IP

jmp 16位reg

转移指令在内存中的call指令

1 call word ptr 内存单元地址

相当于汇编

push IP

jmp word ptr 内存单元地址

2 call dword ptr 内存单元地址

相当于汇编

push CS

push IP

jmp dword ptr 内存单元地址

call和ret的配合使用

可以写一段具有一定功能的程序段,我们称为子程序,在需要的时候,用call指令去执行。可是执行完子程序后,如何让CPU接着call指令向下执行?call指令转去执行子程序之前,call指令后面的指令地址存储在栈中,所以可以在子程序的后面使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行。

具有子程序的源程序的框架如下

assume cs:code
code segment
    main:    :
             :
             call sub1                      ;调用子程序sub1
             :
             :
             move ax, 4c00h
             int 21
    sub1:    :                               ;子程序sub1开始
             :
             call sub2                       ;调用子程序sub2    
             :
             :
             ret                              ;子程序返回
    sub2:    :                                ;子程序sub2开始
             :
             :
             ret                              ;子程序返回
code ends
end main

mul指令

mul是乘法指令

注意:1,两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果都是8位,一个默认放在 AL中,另一个放在8位reg或者内存字节单元中;如果是19位,一个默认在AX中,另一个 放在16位reg或者内存子单元中。

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

格式如下,

mul reg

mul 内存单元

内存单元可以用不同的寻址方式,比如:

mul byte ptr ds : [0]

含义:(ax) = (al) * ((ds)*16 +0);

mul word ptr [bx + si + 8]

含义:(ax) =(ax)*((ds)* 16 +(bx)+(si) + 8) 结果的低16位

(dx) =(ax)*((ds)* 16 +(bx)+(si) + 8) 结果的高16位

参数和结果传递的问题

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

这里面就有两个问题

(1)将参数N存放在什么地方?

(2)计算得到的数值,存放在什么地方?

很显然,可以用寄存器来存储,可以将参数存放到bx中,因为子程序中要计算N*N*N,可以使用多个mul指令,为了方便,可以将结果放到dx和ax中。

对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取到返回值:子程序从参数寄存器中取到参数,将返回值送入结果寄存器。

编程,计算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
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 d[i].2,dx
          add si,2             ;ds:si指向下一个word单元
          add di,4             ;ds:di指向下一个dword单元
          loop s 
    cube: move ax,bx
          mul bx
          mul bx
          ret
code ends
end start

批量数据传递

前面的例程,子程序cube只有一个参数,放在bx中。如果有两个参数,那么可以放在两个寄存器中,如果参数过多显然是不可以的。

在这种时候,我们将批量数据存放到内存中,然后将他们所在的内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可以采用同样的方法。

设计一个程序,功能:将一个全是字母的字符串转换为大写。

这个子程序需要知道两件事,字符串的内容和字符串的长度。因为字符串中的字母可能很多,所以不便将整个字符串中的所有字母都直接传递给子程序。可以将字符串在内存中的首地址放在寄存器中传递给子程序。子程序需要循环,所有我们要将字符串长度传递给cx。

assume cs:code
data segment
    db 'conversation'
data ends
code ends
code segment
    start: mov ax,data
           mov ds,ax
           mov si,0
           mov cx,12
           call capital
           mov ax,4c00h
           int 21h
   capital:and byte ptr [si], 11011111b
           inc si
           loop capital
           ret
code ends
end start

寄存器的冲突问题

设计一个子程序,功能:将一个全是字母,以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

问题在于cx的使用,主程序要使用cx记录循环次数,课时子程序中也使用了cx,在执行子程序的时候,cx中保存的循环计数值被改变,使得主程序的循环出错。

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

我们编写子程序的标准框架如下

子程序开始:子程序中使用的寄存器入栈

子程序内容

子程序中使用的寄存器出栈

返回(ret,retf)

我们改进一下子程序capital的设计:

capital: push cx

push si

change: mov cl, [si]

mov ch, o

jcxz ok

and byte ptr [si], 11011111b

inc si

jmp short change

ok: pop si

pop cx

ret

注意出栈入栈的顺序



版权声明:本文为m0_62750704原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。