[匯編]《匯編語言》第9章 轉移指令的原理


王爽《匯編語言》第四版 超級筆記

第9章 轉移指令的原理

可以修改IP,或同時修改CS和IP的指令統稱為轉移指令。

概括地講,轉移指令就是可以控制CPU執行內存中某處代碼的指令。

8086CPU的轉移行為有以下幾類。

  • 只修改IP時,稱為段內轉移,比如:jmp ax。
  • 同時修改CS和IP時,稱為段間轉移,比如:jmp 1000:0。
    由於轉移指令對IP的修改范圍不同,段內轉移又分為:短轉移和近轉移。
  • 短轉移IP的修改范圍為-128〜127。
  • 近轉移IP的修改范圍為-32768〜32767。

8086CPU的轉移指令分為以下幾類。

  • 無條件轉移指令(如:jmp)
  • 條件轉移指令
  • 循環指令(如:loop)
  • 過程
  • 中斷

這些轉移指令轉移的前提條件可能不同,但轉移的基本原理是相同的。我們在這一章主要通過深入學習無條件轉移指令jmp來理解CPU執行轉移指令的基本原理。

9.1 操作符offset、jmp指令

操作符offset在匯編語言中是由編譯器處理的符號,它的功能是取得標號的偏移地址。比如下面的程序:

assume cs:codesg

codesg segment

    start: mov ax,offset start     ;相當於mov ax,0
        s: mov ax,offset s         ;相當於mov ax,3

codesg ends

end start

在上面的程序中,offset操作符取得了標號start和s的偏移地址0和3,所以指令:mov ax,offset start相當於指令mov ax,0,因為start是代碼段中的標號,它所標記的指令是代碼段中的第一條指令,偏移地址為0。

mov ax,offset s相當於指令mov ax,3,因為s是代碼段中的標號,它所標記的指令是代碼段中的第二條指令,第一條指令長度為3個字節,則s的偏移地址為3。

問題9.1

image

思考后看分析。

分析:

(1)s和s0處的指令所在的內存單元的地址是多少?cs:offset s和cs:offset s0。
(2)將s處的指令復制到s0處,就是將cs:offset s處的數據復制到cs:offset s0處。
(3)段地址己知在cs中,偏移地址offset s和offset s0己經送入si和di中。
(4)要復制的數據有多長?mov ax,bx指令的長度為兩個字節,即1個字。

程序如下。

assume cs:codesg

codesg segment

    s:  mov ax,bx       ;mov ax,bx 的機器碼占兩個字節
        mov si,offset s
        mov di,offset s0
        mov ax,cs:[si]
        mov cs:[di],ax

    s0: nop     ;nop的機器碼占一個字節
        nop

codesg ends

end s

jmp為無條件轉移指令,可以只修改IP,也可以同時修改CS和IP。

jmp指令要給出兩種信息:

(1)轉移的目的地址
(2)轉移的距離(段間轉移、段內短轉移、段內近轉移)

不同的給岀目的地址的方法,和不同的轉移位置,對應有不同格式的jmp指令。

下面的幾節內容中,我們以給出目的地址的不同方法為主線,講解jmp指令的主要應用格式和CPU執行轉移指令的基本原理。

9.2 jmp指令應用場景

依據位移進行轉移的jmp指令

jmp short標號(轉到標號處執行指令)

這種格式的jmp指令實現的是段內短轉移,它對IP的修改范圍為-128~127,也就是說,它向前轉移時可以最多越過128個字節,向后轉移可以最多越過127個字節。

jmp指令中的“short”符號,說明指令進行的是短轉移。jmp指令中的“標號”是代碼段中的標號,指明了指令要轉移的目的地,轉移指令結束后,CS:IP應該指向標號處的指令。

程序9.1

assume cs:codesg

codesg segment

start:	mov ax,0
        jmp short s
        add ax,1
    s:  inc ax

codesg ends

end start

上面的程序執行后,ax中的值為1,因為執行jmp short s后,越過了add ax,1,IP指向了標號s處的inc ax。也就是說,程序只進行了一次ax加1操作。

匯編指令jmp short s對應的機器指令應該是什么樣的呢?

我們先看一下別的匯編指令和其相對應的機器指令。

匯編指令                    機器指令
mov ax,0123h               B8 23 01
mov ax,ds:[0123h]          A1 23 01
push ds:[0123h]            FF 36 23 01

可以看到,在一般的匯編指令中,匯編指令中的idata(立即數),不論它是表示一個數據還是內存單元的偏移地址,都會在對應的機器指令中岀現,因為CPU執行的是機器指令,它必須要處理這些數據或地址。

現在我們在Debug中將程序9.1翻譯成為機器碼,看到的結果如圖9.1所示。

image

對照匯編源程序,我們可以看到,Debug將jmp short s中的s表示為inc ax指令的偏移地址8,並將jmp short s表示為jmp 0008,表示轉移到cs:0008處。

jmp 0008(Debug中的表示)或jmp short s(匯編語言中的表示)所對應的機器碼為EB03,注意,這個機器碼中竟不包含轉移的目的地址,這意味着CPU在執行EB03時,並不知道轉移的目的地址。

那么,CPU根據什么進行轉移呢?它知道轉移到哪里呢?

令人奇怪的是,匯編指令jmp short s中,明明是帶有轉移的目的地址(由標號s表示)的,可翻譯成機器指令后,怎么目的地址就沒了呢?

沒有了目的地址,CPU如何知道轉移到哪里呢?

我們把程序9.1改寫一下,變成下面這樣:

程序9.2

assume cs:codesg

codesg segment

start:  mov ax,0
        mov bx,0
        jmp short s
        add ax,1

    s:  inc ax

codesg ends

end start

我們在Debug中將程序9.2翻譯成為機器碼,看到的結果如圖9.2所示。

image

這說明CPU在執行jmp指令的時候並不需要轉移的目的地址。

CPU不是神仙,它只能處理你提供給它的東西,jmp指令的機器碼中不包含轉移的目的地址,那么,CPU如何知道將IP改為多少呢?

所以,在jmp指令的機器碼中,一定包含了某種信息,使得CPU可以將它當做修改IP的依據。這種信息是什么呢?我們一步步地分析。

我們先簡單回憶一下CPU執行指令的過程。

(1)從CS:IP指向內存單元讀取指令,讀取的指令進入指令緩沖器;
(2)(IP)=(IP)+所讀取指令的長度,從而指向下一條指令;
(3)執行指令。轉到1,重復這個過程。

按照這個步驟,我們參照圖9.2看一下,程序9.2中jmp short s指令的讀取和執行過程:

(1)(CS)=0BBDH,(IP)=0006H,CS:IP指向EB 03(jmp short s 的機器碼);
(2)讀取指令碼EB 03進入指令緩沖器;
(3)(IP)=(IP)+所讀取指令的長度=(IP)+2=0008H,CS:IP指向add ax,1;
(4)CPU執行指令緩沖器中的指令EB 03;
(5)指令EB 03執行后,(IP)=000BH,CS:IP指向inc ax。

從上面的過程中我們看到,CPU將指令EB 03讀入后,IP指向了下一條指令,即CS:0008處的add ax,1,接着執行EB 03。

如果EB 03沒有對IP進行修改的話,那么,接下來CPU將執行add ax,1,可是,CPU執行的EB 03卻是一條修改IP的轉移指令,執行后(IP)=000BH,CS:IP指向inc ax,CS:0008處的add ax,1沒有被執行。

CPU在執行EB 03的時候是根據什么修改的IP,使其指向目標指令呢?

就是根據指令碼中的03。注意,要轉移的目的地址是CS:000B,而CPU執行EB 03時,當前的(IP)=0008H,如果將當前的IP值加3,使(IP)=000BH,CS:IP就可指向目標指令。在轉移指令EB 03中並沒有告訴CPU要轉移的目的地址,卻告訴了CPU要轉移的位移,即將當前的IP向后移動3個字節。因為程序1、2中的jmp指令轉移的位移相同,都是向后3個字節,所以它們的機器碼都是EB 03。

原來如此,在“jmp short標號”指令所對應的機器碼中,並不包含轉移的目的地址,而包含的是轉移的位移。

這個位移,是編譯器根據匯編指令中的“標號”計算出來的,具體的計算方法如圖9.3所示。

image

實際上,“jmp short 標號“的功能為:(IP)=(IP)+8位位移。

(1) 8位位移=標號處的地址-jmp指令后的第一個字節的地址;
(2) short指明此處的位移為8位位移;
(3) 8位位移的范圍為-128-127,用補碼表示;
(4) 8位位移由編譯程序在編譯時算出。

還有一種和"jmp short 標號”功能相近的指令格式,jmp near ptr 標號,它實現的是段內近轉移。

“jmp near ptr 標號"的功能為:(IP)=(IP)+16位位移。

(1) 16位位移=標號處的地址-jmp指令后的第一個字節的地址;
(2) near ptr指明此處的位移為16位位移,進行的是段內近轉移;
(3) 16位位移的范圍為-32768〜32767,用補碼表示;
(4) 16位位移由編譯程序在編譯時算出。

轉移的目的地址在指令中的jmp指令

"jmp far ptr 標號”實現的是段間轉移,又稱為遠轉移。功能如下:

  • (CS)=標號所在段的段地址;
  • (IP)=標號在段中的偏移地址。

far ptr 指明了指令用標號的段地址和偏移地址修改CS和IP。

看下面的程序:

程序9.3

assume cs:codesg

codesg segment

start:  mov ax,0
        mov bx,0
        jmp far ptr s
        db 256 dup (0)
    s:  add ax,1
        inc ax

codesg ends

end start

在Debug中將程序9.3翻譯成為機器碼,看到的結果如圖9.4所示。

image

如圖9.4中所示,源程序中的db 256 dup (0),被Debug解釋為相應的若干條匯編指令。

這不是關鍵,關鍵是,我們要注意一下jmp far ptr s所對應的機器碼:EA 0B 01 BD 0B,其中包含轉移的目的地址。“0B 01 BD 0B ”是目的地址在指令中的存儲順序,高地址的“BD 0B”是轉移的段地址:0BBDH,低地址的“0B 01”是偏移地址:010BH。

轉移地址在寄存器中的jmp指令

指令格式:jmp 16位reg
功能:(IP)=(16位reg)

這種指令我們在前面的內容(參見第2章寄存器修改CS、IP的指令小節)中己經講過,這里就不再詳述。

轉移地址在內存中的jmp指令

轉移地址在內存中的jmp指令有兩種格式:

(1)jmp word ptr 內存單元地址(段內轉移)

功能:從內存單元地址處開始存放着一個字,是轉移的目的偏移地址。

內存單元地址可用尋址方式的任一格式給出。

比如,下面的指令:

mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]

執行后, (IP)=0123H

又比如,下面的指令:

mov ax,0123H
mov [bx],ax
jmp word ptr [bx]

執行后, (IP)=0123H

(2)jmp dword ptr 內存單元地址(段間轉移)

功能:從內存單元地址處開始存放着兩個字,高地址處的字是轉移的目的段地址,低地址處是轉移的目的偏移地址。

(CS)=(內存單元地址+2)
(IP)=(內存單元地址)

內存單元地址可用尋址方式的任一格式給出。

比如,下面的指令:

mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]

執行后,(CS)=0,(IP)=0123H,CS:IP指向0000:0123。

又比如,下面的指令:

mov ax,0123H
mov [bx],ax
mov word ptr [bx+2],0
jmp dword ptr [bx]

執行后,(CS)=0,(IP)=0123H,CS:IP指向0000:0123。

9.3 jcxz指令、loop指令

JCXZ指令為有條件轉移指令,所有的有條件轉移指令都是短轉移,在對應的機器碼中包含轉移的位移,而不是目的地址。

對IP的修改范圍都為:-128~127。

指令格式:jcxz 標號(如果(cx)=0,轉移到標號處執行)。

操作:
當(cx)=0時,(IP)=(IP)+8位位移;
8位位移=標號處的地址-jcxz指令后的第一個字節的地址;
8位位移的范圍為-128~127,用補碼表示;
8位位移由編譯程序在編譯時算出。

當(cx)不等於0時,什么也不做(程序向下執行)。

我們從jcxz的功能中可以看岀,“jcxz 標號”的功能相當於:

if ((cx)==0) jmp short 標號;

(這種用C語言和匯編語言進行的綜合描述,或許能使你對有條件轉移指令理解得更加清楚。)


loop指令為循環指令,所有的循環指令都是短轉移,在對應的機器碼中包含轉移的位移,而不是目的地址。

對IP的修改范圍都為:-128~127。

指令格式:loop 標號((cx)=(cx)-1,如果(cx)不等於0,轉移到標號處執行。

操作:
(1) (cx)=(cx)-1;
(2) 如果(cx)不等於0,(IP)=(IP)+8位位移。

8位位移=標號處的地址-loop指令后的第一個字節的地址;
8位位移的范圍為-128~127,用補碼表示;
8位位移由編譯程序在編譯時算出。

如果(cx)=0,什么也不做(程序向下執行)。

我們從loop的功能中可以看出,“loop標號”的功能相當於:

(cx)--;
if ((cx)!=0) jmp short 標號;

9.4 位移轉移的意義、編譯器對轉移位移超界的檢測

前面我們講到:

jmp short 標號
jmp near ptr 標號
jcxz 標號
loop 標號

等幾種匯編指令,它們對IP的修改是根據轉移目的地址和轉移起始地址之間的位移來進行的。

在它們對應的機器碼中不包含轉移的目的地址,而包含的是到目的地址的位移。

這種設計,方便了程序段在內存中的浮動裝配。

例如:

匯編指令                    機器代碼
mov cx,6                   B9 06 00
mov ax,10h                 B8 10 00
s:  add ax,ax              01 C0
    loop s                 E2 FC

這段程序裝在內存中的不同位置都可正確執行,因為loop s在執行時只涉及s的位移(-4,前移4個字節,補碼表示為FCH),而不是s的地址。

如果loop s的機器碼中包含的是s的地址,則就對程序段在內存中的偏移地址有了嚴格的限制,因為機器碼中包含的是s的地址,如果s處的指令不在目的地址處,程序的執行就會出錯。

而loop s的機器碼中包含的是轉移的位移,就不存在這個問題了,因為,無論s處的指令的實際地址是多少,loop指令的轉移位移是不變的。


注意,根據位移進行轉移的指令,它們的轉移范圍受到轉移位移的限制,如果在源程序中出現了轉移范圍超界的問題,在編譯的時候,編譯器將報錯。

比如,下面的程序將引起編譯錯誤:

assume cs:code

code segment

start:  jmp short s
        db 128 dup (0)
    s:  mov ax,0ffffh

code ends

end start

jmp short s 的轉移范圍是-128~127,IP最多向后移動127個字節。

注意,我們在第2章中講到的形如“jmp 2000:0100"的轉移指令,是在Debug中使用的匯編指令,匯編編譯器並不認識。如果在源程序中使用,編譯時也會報錯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM