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

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

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

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

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

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

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

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

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

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

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

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

       pop IP

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

       pop IP

       pop CS

四.call 指令

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

a)         1)将当前的 IP CSIP 压入栈中;

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

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

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

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

 

   2.例子2

10-1.jpg

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

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

10-2.jpg

 

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

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

 

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

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

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

 

  (IP)=000EH

n       5CPU回到 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

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

        mov al,100

         mov bl,10

         mul bl

      结果: (ax)=100003E8H

例子二:

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,来计算N3次方。

n       这里有两个问题:

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

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

 

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

n       子程序:

n       说明:计算N3次方

n       参数: (bx)=N

n       结果: (dx:ax)=N3

  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)在编写子程序的时候,不要使用会产生冲突的寄存器。

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

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

                    子程序内容

                     子程序使用的寄存器出栈

                     返回(retretf

如下代码:

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       要注意寄存器入栈和出栈的顺序。

 

 

 

 

 

 

posted on 2009-03-29 10:44  jasonM  阅读(11722)  评论(3编辑  收藏  举报