王爽《匯編語言》第四版 超級筆記
第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
思考后看分析。
分析:
(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所示。
對照匯編源程序,我們可以看到,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所示。
這說明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所示。
實際上,“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所示。
如圖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中使用的匯編指令,匯編編譯器並不認識。如果在源程序中使用,編譯時也會報錯。