控制轉移基本上可分為兩大類:同一任務內的控制轉移和任務間的控制轉移(任務切換)。同一任務內的控制轉移又可分為:段內轉移、特權級不變的段間轉移和特權級變換的段間轉移。段內轉移與實模式下相似,不涉及特權級變換和任務切換。只有段間轉移才涉及特權級變換和任務切換。本文介紹保護方式下的控制轉移,重點是任務內的特權級變換和任務間的切換。
<一>任務內無特權級變換的轉移各種段內轉移與實模式下相似,當然不涉及特權級變換和任務切換。只有各種形式的段間轉移才涉及特權級變換和任務切換。
1.段間轉移指令與實模式下相同,指令JMP、CALL和RET都具有段間轉移的功能,指令INT和IRET總是段間轉移。此外,中斷/異常也將引起段間轉移。有時把這些具有段間轉移功能的指令統稱為段間轉移指令。
在保護模式下,段間轉移的目標位置由選擇子和偏移構成的地址表示,常把它稱為目標地址指針。在32位代碼段中,上述指針內的偏移使用32位表示,這樣的指針也稱為48位全指針。在實例二的32位代碼段內就使用了48位全指針。在16位代碼段中,上述指針內的偏移只使用16位表示。
與實模式下相似,段間轉移指令JMP和段間調用指令CALL還可分為段間直接轉移和段間間接轉移兩類。如果指令JMP和CALL在指令中直接含有目標地址指針,那麼就是段間直接轉移;如果指令中含有指向包含目標地址指針的門描述符或TSS描述符的指針,那麼就是段間間接轉移,這種指針只有選擇子部分有效,指示調用門、任務門或 TSS描述符,而偏移部分不起作用。實際上,當段間轉移指令JMP和段間調用指令CALL所含指針的選擇子部分指示代碼段描述符,那麼就是段間直接轉移,偏移部分表示目標代碼段的入口點;當選擇子部分指示門描述符或TSS描述符時,就是段間間接轉移。
2.向目標代碼段轉移的步驟處理器在執行上述段間轉移指令向目標代碼段實施轉移的過程中,一般至少要經過如下步驟:
(1)判斷目標地址指針內的選擇子指示的描述符是否為空描述符。空描述符是GDT中的第0個描述符,是一個特殊的描述符。目標代碼段描述符不能為空描述符,也即選擇子的高14位不能為0。
(2)從全局或局部描述符表內讀出目標代碼段描述符。由選擇子內的TI位,確定使用全局描述符表還是局部描述符表。
(3)根據情況,檢測描述符類型是否正確;調整RPL。
(4)把目標代碼段描述符內的有關內容裝載到CS高速緩沖寄存器。
(5)判斷目標地址指針內的偏移是否越出代碼段的界限。目標地址指針內的偏移必須不超過目標代碼段界限。
(6)裝載CS段寄存器和指令指針寄存器EIP;CPL存入CS內選擇子的RPL字段。
上述步驟只是對轉移過程的簡單說明,實際的動作還要復雜。在把目標代碼段描述符內的有關內容轉載到CS高速緩沖寄存器時,還要進行如下保護檢測,其中的DPL表示目標代碼段描述符的特權級:
(1)對於非一致代碼段,要求CPL=DPL,RPL<=DPL;對於一致代碼段,要求CPL>=DPL。
(2)代碼段必須存在,即描述符中的P位必須是1。
通常描述符特權級DPL規定了對應段的特權級。如果描述符描述的是數據段,那麼DPL就規定了訪問該數據段的最外層特權級;如果描述符描述的是代碼段,那麼DPL就規定了執行該代碼段所需要的CPL。但從上述裝載CS高速緩沖寄存器時進行的保護檢測可見,對於一致代碼段,卻要求CPL>=DPL,也就是說,一致代碼段描述符中的DPL規定了可以轉移到一致代碼段的最內層特權級。於是,3級的程序可以轉移到任何一致的代碼段,而0級的程序只允許轉移到DPL等於0的一致代碼段。一致代碼段描述符內DPL的這種解釋,正好與正常的DPL的解釋相反。
一致的可執行段是一種特別的段。這種存儲段,為在多個特權級執行的程序,提供對子例程的共享支持,而不要求改變特權級。例如,通過把數值庫例程放在一致的代碼段中,可以使不同級執行的程序共享數值庫例程。這樣,任何特權級的程序可以使用段間調用指令,調用庫中的例程,並在調用者所具有的特權級執行該例程。
3.任務內無特權級變換的轉移所謂任務內無特權級變換的轉移指:在轉移到新的代碼段時,當前特權級CPL保持不變。利用段間轉移指令JMP、段間調用指令CALL和段間返回指令RET可實現任務內無特權級變換的轉移。利用INT指令和IRET指令也可實現任務內無特權級變換的轉移。
(1)利用段間直接轉移指令JMP或CALL在執行段間轉移指令JMP時,如果指令內所含指針指示一個代碼段,那麼就直接開始上述向目標代碼段轉移的步驟;在執行段間調用指令CALL時,如果指令內所含指針指針指示一個代碼段,那麼就把返回地址指針壓棧,然後就直接開始上述向目標代碼段轉移的步驟。順利通過這幾步(不調整RPL)後,就完成了任務內無特權級變換的轉移。
由此可見,利用段間直接轉移指令JMP或調用指令CALL可方便地進行任務內無特權級變換的轉移,但不能進行任務內特權級變換的轉移。
(2)利用段間返回指令RET在執行段間返回指令RET時,如果從堆棧中彈出的目標地址指針指示一個代碼段,並且選擇子符合RPL=CPL的條件,那麼就開始上述向目標代碼段的轉移步驟。順利通過這幾步後,就完成了任務內無特權級變換的轉移。
通常情況下,段間返回指令RET與段間調用指令CALL對應。在利用段間調用指令CALL以任務內無特權級變換的方式轉移到某個子程序後,在子程序內利用段間返回指令RET以任務內無特權級變換的方式返回主程序。由於調用時無特權級變換,所以返回時也無特權級變換,如果真是如此,那麼必須能夠滿足條件RPL=CPL。
(3)利用調用門和其它途徑如何利用調用門實行和其它方法實現任務內無特權級變換的轉移將在後面的文章中介紹。
4.裝載數據段和堆棧段寄存器時的特權檢測
上面簡單地說明了把選擇子裝入代碼段寄存器CS時為實現保護而進行的檢測,下面也簡單地說明在把選擇子裝入數據段寄存器和堆棧段寄存器時要進行的檢測。
在把選擇子裝入數據段寄存器DS、ES、FS或GS時,要進行如下檢測:
(1)選擇子不能為空;
(2)選擇子指定的描述符必須是數據段描述符、可讀可執行的代碼段或一致可讀的可執行代碼段的描述符;
(3)對於數據段和可讀可執行代碼段,要求CPL<=DPL,RPL<=DPL;
(4)對應的段必須存在。
若裝入的選擇子不滿足上述要求,則會產生異常。
在把選擇子裝入堆棧段寄存器SS時要進行如下檢測:
(1)選擇子不能為空;
(2)選擇子指定的描述符必須是可讀寫的數據段描述符;
(3)要求CPL=DPL=RPL;
(4)對應段必須存在。
若裝入的選擇子不滿足上述條件,則在裝入SS時就會引起異常。
<二>演示任務內無特權級變換轉移的實例(實例三)在實例二中,32位代碼段到16位代碼段的轉移就是任務內無特權級轉移的例子。
下面再給出一個用於演示任務內無特權級變換轉移的實例。該實例使用了段間轉移指令JMP、段間調用指令CALL和段間返回指令RET實現同一任務內相同特權級的轉移。該實例還建立並使用了局部描述符表LDT。
1.實現步驟和源程序實現步驟如下:(1)實模式下的初始化,包括對GDT和演示任務LDT的初始化,裝載GDTR;(2)從實模式切換到保護模式,處於0特權級;(3)裝載LDTR,並設置堆棧;(4)利用段間轉移指令JMP實現從代碼段K到同級代碼段L的轉移;(5)利用段間調用指令CALL調用同級代碼段C中的子程序D顯示字符串信息;(6)利用段間調用指令CALL調用同級代碼段C中的子程序H把十六進制數轉換成對應的ASCII碼;(7)再利用段間調用指令CALL調用同級代碼段C中的子程序D顯示字符串信息;(8)利用段間轉移指令JMP實現從代碼段L到代碼段K的轉移;(9)從保護模式切換到實模式;(10)在實模式下結束程序。
該實例的邏輯功能是用十六進制數的形式顯示代碼段L的段界限的值。源程序如下:;名稱:ASM3.ASM
;功能:演示任務內無特權級變換的轉移
;編譯:TASM ASM3.ASM
;連接:TLINK ASM3.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 'GDT' ;全局描述符表數據段(16位)
;----------------------------------------------------------------------------
GDT LABEL BYTE ;全局描述符表
DUMMY Desc <> ;空描述符
Normal Desc <0ffffh,,,ATDW,,> ;規范段描述符
CodeK Desc <0ffffh,,,ATCE,,> ;代碼段K的描述符
LDTable Desc <LDTLen-1,,,ATLDT,,> ;局部描述符表段的描述符
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表長度
;----------------------------------------------------------------------------
Normal_Sel = Normal-GDT ;規范段描述符選擇子
CodeK_Sel = CodeK-GDT ;代碼段K的選擇子
LDT_Sel = LDTable-GDT ;局部描述符表段的選擇子
;----------------------------------------------------------------------------
GDTSeg ENDS ;全局描述符表段定義結束
;----------------------------------------------------------------------------
LDTSeg SEGMENT PARA USE16 'LDT' ;局部描述符表數據段(16位)
LDT LABEL BYTE ;局部描述符表
;代碼段L的描述符
CodeL Desc <CodeLLen-1,CodeLSeg,,ATCE,,>
;代碼段C的描述符
CodeC Desc <CodeCLen-1,CodeCSeg,,ATCE,,>
;顯示緩沖區段描述符
VideoBuf Desc <0ffffh,0b800h,,ATDW,,>
;LDT別名段描述符(DPL=3)
ToLDT Desc <LDTLen-1,LDTSEG,,ATDR+DPL3,,>
;顯示信息緩沖區數據段描述符(DPL=3)
MData Desc <MDataLen-1,MDataSeg,,ATDW+DPL3,,>
;堆棧段描述符
StackS Desc <TopOfS-1,StackSeg,,ATDWA,,>
;----------------------------------------------------------------------------
LDTLen = $-LDT ;LDT所占字節數
LDNum = ($-LDT)/(SIZE Desc) ;LDT含描述符項數
;----------------------------------------------------------------------------
CodeL_Sel = CodeL-LDT+TIL ;代碼段L的選擇子
CodeC_Sel = CodeC-LDT+TIL ;代碼段C的選擇子
Video_Sel = VideoBuf-LDT+TIL ;顯示緩沖區選擇子
ToLDT_Sel = ToLDT-LDT+TIL ;LDT別名段選擇子
MData_Sel = MData-LDT+TIL+RPL3 ;顯示信息數據段選擇子
Stack_Sel = StackS-LDT+TIL ;堆棧段選擇子
;----------------------------------------------------------------------------
LDTSeg ENDS ;局部描述符表段定義結束
;----------------------------------------------------------------------------
MDataSeg SEGMENT PARA USE16 'MDATA' ;顯示信息緩沖區數據段
;----------------------------------------------------------------------------
Message DB 'Value=',0
Buffer DB 80 DUP(0)
MDataLen = $
;----------------------------------------------------------------------------
MDataSeg ENDS ;顯示緩沖區數據段結束
;----------------------------------------------------------------------------
StackSeg SEGMENT DWORD USE16 'STACK' ;堆棧段
;----------------------------------------------------------------------------
DW 512 DUP(?)
TopOfS = $
;----------------------------------------------------------------------------
StackSeg ENDS ;堆棧段結束
;----------------------------------------------------------------------------
CodeCSeg SEGMENT PARA USE16 'CODEC' ;任務代碼段C
ASSUME CS:CodeCSeg
;----------------------------------------------------------------------------
;顯示信息子程序
;入口參數:fs:si指向要顯示的以0結尾的字符串,es:di指向顯示緩沖區
;----------------------------------------------------------------------------
DispMsg PROC FAR
mov ah,01001110b
Disp1: mov al,BYTE PTR fs:[si]
inc si
or al,al
jz Disp2
mov WORD PTR es:[di],ax
inc di
inc di
jmp Disp1
Disp2: ret
DispMsg ENDP
;----------------------------------------------------------------------------
;把AL寄存器低4位二進制數(一位16進制數)轉換成ASCII碼
;----------------------------------------------------------------------------
HToASCII PROC FAR
and al,00001111b
add al,90h
daa
adc al,40h
daa
ret
HToASCII ENDP
;----------------------------------------------------------------------------
CodeCLen = $
;----------------------------------------------------------------------------
CodeCSeg ENDS ;代碼段C定義結束
;----------------------------------------------------------------------------
CodeLSeg SEGMENT PARA USE16 'CODEL'
ASSUME CS:CodeLSeg
;----------------------------------------------------------------------------
Virtual2 PROC FAR
mov ax,Video_Sel ;設置顯示緩沖區指針
mov es,ax
mov di,1986
mov ax,MData_Sel ;設置提示信息緩沖區指針
mov fs,ax
mov si,OFFSET Message
CALL16 CodeC_Sel,DispMsg ;顯示提示信息
mov ax,ToLDT_Sel ;把演示任務的LDT的別名
mov gs,ax ;段的描述符選擇子裝入GS
mov dx,WORD PTR gs:CodeL.LimitL
mov si,OFFSET Buffer ;取代碼段L的段界限值
mov cx,4 ;並轉成對應可顯示字符串
Vir: rol dx,4
mov al,dl
CALL16 CodeC_Sel,HToASCII
mov BYTE PTR fs:[si],al
inc si
loop Vir
mov WORD PTR fs:[si],'H'
mov si,OFFSET Buffer
CALL16 CodeC_Sel,DispMsg
JUMP16 CodeK_Sel,Virtual3
CodeLLen = $
Virtual2 ENDP
;----------------------------------------------------------------------------
CodeLSeg ENDS
;----------------------------------------------------------------------------
CodeKSeg SEGMENT PARA USE16 'CODEK'
ASSUME CS:CodeKSeg
;----------------------------------------------------------------------------
Virtual1 PROC FAR
mov ax,LDT_Sel
LLDT ax ;加載局部描述符表寄存器LDTR
mov ax,Stack_Sel
mov ss,ax ;建立演示任務堆棧
mov sp,OFFSET TopOfS
JUMP16 CodeL_Sel,Virtual2
Virtual3: mov ax,Normal_Sel
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
CodeKLen = $
Virtual1 ENDP
;----------------------------------------------------------------------------
CodeKSeg ENDS
;============================================================================
RDataSeg SEGMENT PARA USE16 ;實方式數據段
VGDTR PDesc <GDTLen-1,> ;GDT偽描述符
SPVar DW ? ;用於保存實方式下的SP
SSVar DW ? ;用於保存實方式下的SS
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16
ASSUME CS:RCodeSeg
;----------------------------------------------------------------------------
Start PROC
ASSUME DS:GDTSeg
;-----------------
mov ax,GDTSeg
mov ds,ax
;初始化全局描述符表
mov bx,16
mov ax,CodeKSeg
mul bx
mov CodeK.BaseL,ax
mov CodeK.BaseM,dl
mov CodeK.BaseH,dh
mov ax,LDTSeg
mul bx
mov LDTable.BaseL,ax
mov LDTable.BaseM,dl
mov LDTable.BaseH,dh
;設置GDT偽描述符
ASSUME DS:RDataSeg
mov ax,RDataSeg
mov ds,ax
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
;初始化演示任務LDT
cld
call Init_MLDT
;保存實方式堆棧指針
mov SSVar,ss
mov SPVar,sp
;裝載GDTR
lgdt QWORD PTR VGDTR
cli
;切換到保護方式
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <CodeK_Sel>,<OFFSET Virtual1>
Real: ;又回到實方式
mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
Init_MLDT PROC
push ds
mov ax,LDTSeg
mov ds,ax
mov cx,LDNum
mov si,OFFSET LDT
InitL: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov [si].BaseL,ax
mov [si].BaseM,dl
mov [si].BaseH,dh
add si,SIZE Desc
loop InitL
pop ds
ret
Init_MLDT ENDP
;----------------------------------------------------------------------------
RCodeSeg ENDS
END Start