RET指令

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

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

a)         (1)(IP)=((ss)*16+(sp))

b)        (2)(sp)=(sp)+2

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

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

a)         (1)(IP)=((ss)*16+(sp))

b)        (2)(sp)=(sp)+2

c)         (3)(CS)=((ss)*16+(sp))

d)        (4)(sp)=(sp)+2

三.可以看出,如果我们用汇编语法来解释ret和retf指令,则:

a)         CPU执行ret指令时,相当于进行:

       pop IP

b).CPU执行retf指令时,相当于进行:

       pop IP

       pop CS

四.call 指令

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

a)         (1)将当前的 IP 或 CS和IP 压入栈中;

b)        (2)转移。

call 指令不能实现短转移,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。

解释:

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

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

n   (1) (sp) = (sp) – 2

                   ((ss)*16+(sp)) = (IP)

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

    call 标号

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

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

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

五.综合实例演示:

看下面一段简单的代码:

Mov ax,0

Call s

Mov bx,0

S:add ax,1

Ret

解释如下:

Call s:实际上就是调用s处的子程序。并将Mov bx,0这条指令所对应的偏移地址入栈,此处为什么要入栈呢?实际上就是为了方便ret指令取出ip。

Ret指令实际上就是返回s,能使程序从mov bx,0处执行。

 

六.call 和 ret 的配合使用

1.例子1:

assume cs:code

code segment

start:       mov ax,1

           mov cx,3

          call s

           mov bx,ax      ;(bx) = ?

          mov ax,4c00h

          int 21h

     s:  add ax,ax

          loop s

           ret

code ends

end start

 

我们来看一下 CPU 执行这个程序的主要过程:

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

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

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

n       (4)CPU从 mov bx,ax 开始执行指令,直至完成。

 

   2.例子2

10-1.jpg

n       我们看一下程序的主要执行过程:

n       (1)前三条指令执行后,栈的情况如下:

10-2.jpg

 

n       2)call 指令读入后,(IP) =000EH,CPU指令缓冲器中的代码为 B8 05 00;

   CPU执行B8 05 00,首先,栈中的情况变为:

 

   然后,(IP)=(IP)+0005=0013H。

n       (3)CPU从cs:0013H处(即标号s处)开始执行。

n       (4)ret指令读入后:(IP)=0016H,CPU指令缓冲器中的代码为 C3;CPU执行C3,相当于进行pop IP,执行后,栈中的情况为:

 

  (IP)=000EH;

n       (5)CPU回到 cs:000EH处(即call指令后面的指令处)继续执行。

3.  从上面的讨论中我们发现,可以写一个具有一定功能的程序段,我们称其为子程序,在需要的时候,用call指令转去执行。

4.  可是执行完子程序后,如何让CPU接着call指令向下执行?

5.  call指令转去执行子程序之前,call指令后面的指令的地址将存储在栈中,所以可以在子程序的后面使用 ret 指令,用栈中的数据设置IP的值,从而转到 call 指令后面的代码处继续执行。

 

七.mul 指令

n       因下面要用到,我们介绍一下mul指令,mul是乘法指令,使用 mul 做乘法的时候:

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

     8 位: AL中和 8位寄存器或内存字节单元中;

   16 位: AX中和 16 位寄存器或内存字单元中。

n       (2)结果

     8位:AX中;

   16位:DX(高位)和AX(低位)中。

 

n       格式如下:

    mul reg

mul 内存单元

 

例子一:

n       例如:

n       (1)计算100*10

   100和10小于255,可以做8位乘法,程序如下:

        mov al,100

         mov bl,10

         mul bl

      结果: (ax)=1000(03E8H)

例子二:

n       (1)计算100*10000

   100小于255,可10000大于255,所以必须做16位乘法,程序如下:

        mov ax,100

         mov bx,10000

         mul bx

      结果: (ax)=4240H,(dx)=000FH

               (F4240H=1000000)

 

8.参数和结果传递的问题

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

n       这里有两个问题:

n       (1)我们将参数N存储在什么地方?

n       (2)计算得到的数值,我们存储在什么地方?

 

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

n       子程序:

n       说明:计算N的3次方

n       参数: (bx)=N

n       结果: (dx:ax)=N∧3

  cube:mov ax,bx

   mul bx

   mul bx

   ret

n       用寄存器来存储参数和结果是最常使用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:

n       调用者将参数送入参数寄存器,从结果寄存器中取到返回值;

n       子程序从参数寄存器中取到参数,将返回值送入结果寄存器。

9.批量数据的传递

n       前面的例程中,子程序 cube 只有一个参数,放在bx中。如果有两个参数,那么可以用两个寄存器来放,可是如果需要传递的数据有3个、4个或更多直至 N个,我们怎样存放呢?

n       寄存器的数量终究有限,我们不可能简单地用寄存器来存放多个需要传递的数据。对于返回值,也有同样的问题。

n       在这种时候,我们将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。

n       对于具有批量数据的返回结果,也可用同样的方法。

如下代码:

10-3.jpg

 

10.寄存器冲突的问题

n       设计一个子程序:

n       功能:将一个全是字母,以0结尾的字符串,转化为大写。

n       分析

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

   由于可通过检测0而知道是否己经处理完整个字符串 ,所以子程序可以不需要字符串的长度作为参数。我们可以用jcxz来检测0。

10-4.jpg

添加主框架

assume cs:code

            data segment

                db 'conversation',0

            data ends

n       代码段中相关程序段如下:

            mov ax,data

            mov ds,ax

            mov si,0

            call capital

其中si运用了多次,怎么避免呢?

   从上而的问题中,实际上引出了个一般化的问题:子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。

那么我们如何来避免这种冲突呢 ?粗略地看,我们可以有两个方案:

n       (1)在编写调用子程序的程序时 ,注意看看子程序中有没有用到会产生冲突的寄存器,如果有,调用者使用别的寄存器;

n       (2)在编写子程序的时候,不要使用会产生冲突的寄存器。

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

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

                    子程序内容

                     子程序使用的寄存器出栈

                     返回(ret、retf)

如下代码:

n       capital: push cx

                  push si

      change: mov cl,[si]

                  mov ch,0

                      jcxz ok

                     and byte ptr [si],11011111b

                      inc si

                      jmp short change

             ok: pop si

                  pop cx

                  ret

n       要注意寄存器入栈和出栈的顺序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值