RET指令

一.ret指令用棧中的數據,修改IP的內容,從而實現近轉移;api

 CPU執行ret指令時,進行下面兩步操做:框架

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

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

二.retf指令用棧中的數據,修改CS和IP的內容,從而實現遠轉移;設計

   CPU執行retf指令時,進行下面兩步操做:code

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

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

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

相關文章
相關標籤/搜索