下面給出一個用於模擬異常和演示異常處理的實例。該實例的邏輯功能是,在屏幕上顯示一條提示用戶以按鍵方式選擇異常類型的字符,然後模擬指定的異常。該實例演示內容包括:除法出錯故障處理、溢出陷阱處理、段不存在故障處理、堆棧段出錯處理和通用保護故障處理;還有作為一個獨立任務方式出現的陷阱處理程序。
1.源程序組織和清單
為了演示以獨立任務方式出現的陷阱處理程序,實例含有兩個任務:演示任務和讀鍵盤任務。實例由如下幾部分組成:
(1)全局描述符表GDT和中斷描述符表IDT;
(2)讀鍵盤任務局部描述符表、任務狀態段、堆棧段和代碼段等;
(3)演示任務的局部描述符表、任務狀態段、堆棧段、代碼段和數據段等;
(4)作為演示任務一部分的有關陷阱處理和故障處理程序的代碼段;
(5)作為演示任務一部分的顯示出錯碼過程的代碼段;
(6)實模式下執行的啟動和結束程序代碼段和數據段。
在切換到保護模式後,就進入臨時代碼段。為了簡單,演示任務不發生特權級變換。演示步驟如下:
(1)從臨時代碼段轉移到演示代碼段。
(2)做演示准備。把演示任務的LDT選擇子裝入LDTR,並填入TSS,裝載任務寄存器TR,建立演示任務堆棧,設置其它數據段寄存器。
(3)接收要模擬的異常類型號。通過軟中斷指令INT調用讀鍵盤任務完成該步驟。讀鍵盤任務只有在接收到指定的字符後才結束。接收的字符是0、4、B、C和D。
按接收的字符模擬異常。即根據鍵入的字符,執行有關程序片段。在這些片段中,有意安排了能引起有關故障或陷阱的指令。
結束演示,轉臨時代碼段,返回DOS。
程序清單如下:
名稱:ASM7.ASM
功能:模擬異常和演示異常處理
編譯:TASM ASM7.ASM
連接:TLINK ASM7.OBJ
----------------------------------------------------------------------------
INCLUDE 386SCD.INC
----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 全局描述符表數據段(16位)
----------------------------------------------------------------------------
全局描述符表GDT
GDT LABEL BYTE
空描述符
DUMMY Desc <>
規范段描述符及選擇子
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
視頻緩沖區段描述符(DPL=3)及選擇子
VideoBuf Desc <0ffffh,8000h,0bh,ATDW,,>
VideoBuf_Sel = VideoBuf-GDT
----------------------------------------------------------------------------
EFFGDT LABEL BYTE
臨時代碼段描述符及選擇子
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel = TempCode-GDT
演示代碼段描述符及選擇子
DemoCode Desc
DemoCode_Sel = DemoCode-GDT
演示任務局部描述符表段描述符及選擇子
DemoLDT Desc
DemoLDT_Sel = DemoLDT-GDT
演示任務TSS段描述符及選擇子
DemoTSS Desc
DemoTSS_Sel = DemoTSS-GDT
緩沖數據段描述符及選擇子
XBuffer Desc
XBuffer_Sel = XBuffer-GDT
讀鍵盤任務局部描述符表段描述符及選擇子
GKeyLDT Desc
GKeyLDT_Sel = GKeyLDT-GDT
讀鍵盤任務TSS段描述符及選擇子
GKeyTSS Desc
GKeyTSS_Sel = GKeyTSS-GDT
顯示陷阱處理程序代碼段描述符及選擇子
EchoCode Desc
EchoCode_Sel = EchoCode-GDT
顯示出錯碼過程代碼段描述符及選擇子
SubCode Desc
SubCode_Sel = SubCode-GDT
其它中斷或異常處理程序代碼段描述符及選擇子
Other Desc
Other_Sel = Other-GDT
----------------------------------------------------------------------------
GDTLen = $-GDT 全局描述符表長度
GDNum = ($-EFFGDT)/(SIZE Desc) 需處理基地址的描述符個數
----------------------------------------------------------------------------
GDTSeg ENDS 全局描述符表段定義結束
----------------------------------------------------------------------------
IDTSeg SEGMENT PARA USE16 中斷描述符表數據段(16位)
----------------------------------------------------------------------------
IDT LABEL BYTE 中斷描述符表
0號陷阱門描述符(對應除法出錯故障)
Gate
從1--3的3個陷阱門描述符
REPT 3
Gate
ENDM
4號陷阱門描述符(對應溢出陷阱)
Gate
從5--0ah的的6個陷阱門描述符
REPT 6
Gate
ENDM
0bh號陷阱門描述符(對應段不存在故障)
Gate
0ch號陷阱門描述符(對應堆棧段故障)
Gate
0dh號陷阱門描述符(對應通用保護故障)
Gate
從0eh--0edh的240個陷阱門描述符
REPT 240
Gate
ENDM
對應0feh號陷阱門描述符(對應顯示中斷處理程序)
Gate
0ffh號任務門描述符(對應讀鍵盤中斷處理任務)
Gate <,GKeyTSS_Sel,,ATTaskGate,>
----------------------------------------------------------------------------
IDTLen = $-IDT
----------------------------------------------------------------------------
IDTSeg ENDS 中斷描述符表段定義結束
----------------------------------------------------------------------------
讀鍵盤任務局部描述符表段
----------------------------------------------------------------------------
GKeyLDTSeg SEGMENT PARA USE16
----------------------------------------------------------------------------
GLDT LABEL BYTE
代碼段描述符及選擇子
GKeyCode Desc <0ffffh,GKeyCodeSeg,,ATCE,,>
GKeyCode_Sel = GKeyCode-GLDT+TIL
堆棧段描述符及選擇子
GKeyStack Desc
GKeyStack_Sel = GKeyStack-GLDT+TIL
----------------------------------------------------------------------------
GKeyLDNum = ($-GLDT)/(SIZE Desc) 需初始化基地址的描述符個數
GKeyLDTLen = $ 局部描述符表段長度
----------------------------------------------------------------------------
GKeyLDTSeg ENDS
----------------------------------------------------------------------------
讀鍵盤任務TSS段
----------------------------------------------------------------------------
GKeyTSSSeg SEGMENT PARA USE16
DD 0 鏈接字
DD ? 0級堆棧指針
DW ?,?
DD ? 1級堆棧指針
DW ?,?
DD ? 2級堆棧指針
DW ?,?
DD 0 CR3
DW GKeyBegin,0 EIP
DD 0 EFLAGS
DD 0 EAX
DD 0 ECX
DD 0 EDX
DD 0 EBX
DW GKeyStackLen,0 ESP
DD 0 EBP
DD 0 ESI
DD 0 EDI
DW Normal_Sel,0 ES
DW GKeyCode_Sel,0 CS
DW GKeyStack_Sel,0 SS
DW Normal_Sel,0 DS
DW Normal_Sel,0 FS
DW Normal_Sel,0 GS
DW GKeyLDT_Sel,0 LDTR
DW 0 調試陷阱標志
DW $+2 指向I/O許可位圖的偏移
DB 0ffh I/O許可位圖結束字節
GKeyTSSLen = $
GKeyTSSSeg ENDS
----------------------------------------------------------------------------
讀鍵盤任務堆棧段
----------------------------------------------------------------------------
GKeyStackSeg SEGMENT PARA USE16
GKeyStackLen = 1024
DB GKeyStackLen DUP(0)
GKeyStackSeg ENDS
----------------------------------------------------------------------------
讀鍵盤任務代碼段
----------------------------------------------------------------------------
GKeyCodeSeg SEGMENT PARA USE16
ASSUME CS:GKeyCodeSeg,DS:RDataSeg,ES:BufferSeg
----------------------------------------------------------------------------
GKeyBegin PROC FAR
push ds
push es
push fs
push gs
mov ax,Normal_Sel
mov ss,ax 准備轉實方式
mov eax,cr0
and al,11111110b
mov cr0,eax 轉實方式
JUMP16 ,
GetKey: mov ax,RDataSeg 實方式
mov ds,ax
mov ebp,esp 恢復實方式部分現場
lss sp,DWORD PTR SPVar
lidt QWORD PTR NORVIDTR
sti
mov dx,OFFSET Mess
mov ah,9
int 21h 顯示提示信息
GetKey1: mov ah,0
int 16h 讀鍵盤
cmp al,'0'
jz GetKey2
cmp al,'4'
jz GetKey2
and al,11011111b 小寫轉大寫
cmp al,'B'
jb GetKey1
cmp al,'D'
ja GetKey1 只有[0,4,b,c,d]有效
GetKey2: mov dl,al
mov ah,2
int 21h 顯示所按字符
mov ax,BufferSeg
mov es,ax
mov BYTE PTR es:KeyASCII,dl 保存到緩沖數據段
cli 准備返回保護方式
lidt QWORD PTR VIDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 ,
GetKeyV: mov ax,GKeyStack_Sel 又進入保護方式
mov ss,ax
mov esp,ebp
pop gs
pop fs
pop es
pop ds
iretd
jmp GKeyBegin
GKeyBegin ENDP
----------------------------------------------------------------------------
GKeyCodeLen = $
GKeyCodeSeg ENDS
----------------------------------------------------------------------------
其它中斷或異常處理程序的代碼段
----------------------------------------------------------------------------
OtherCodeSeg SEGMENT PARA USE16
ASSUME CS:OtherCodeSeg
----------------------------------------------------------------------------
OtherBegin PROC FAR
mov si,OFFSET MessOther
int 0feh 顯示提示信息
mov WORD PTR es:[0],ax
jmp $ 進入無限循環
OtherBegin ENDP
----------------------------------------------------------------------------
OtherCodeLen = $
OtherCodeSeg ENDS
----------------------------------------------------------------------------
除法出錯故障處理程序代碼段
----------------------------------------------------------------------------
DivCodeSeg SEGMENT PARA USE16
ASSUME CS:DivCodeSeg
----------------------------------------------------------------------------
DivBegin PROC FAR
mov si,OFFSET Mess0
mov di,0
int 0feh 顯示提示信息
shr ax,1 處理模擬的除法錯誤
iretd 返回
DivBegin ENDP
----------------------------------------------------------------------------
DivCodeLen = $
DivCodeSeg ENDS
----------------------------------------------------------------------------
溢出陷阱處理程序代碼段
----------------------------------------------------------------------------
OFCodeSeg SEGMENT PARA USE16
ASSUME CS:OFCodeSeg
----------------------------------------------------------------------------
OFBegin PROC FAR
mov si,OFFSET Mess4
mov di,0
int 0feh 顯示提示信息
iretd 返回
OFBegin ENDP
----------------------------------------------------------------------------
OFCodeLen = $
OFCodeSeg ENDS
----------------------------------------------------------------------------
段不存在故障處理程序代碼段
----------------------------------------------------------------------------
SNPCodeSeg SEGMENT PARA USE16
ASSUME CS:SNPCodeSeg
----------------------------------------------------------------------------
SNPBegin PROC FAR
mov si,OFFSET MessB
mov di,0
int 0feh 顯示提示信息
pop eax 彈出出錯代碼
CALL16 SubCode_Sel,SubBegin 顯示出錯代碼
pop eax
add eax,2 按模擬的引起段不存在指令
push eax 調整返回地址
iretd
SNPBegin ENDP
----------------------------------------------------------------------------
SNPCodeLen = $
SNPCodeSeg ENDS
----------------------------------------------------------------------------
堆棧段故障處理程序代碼段
----------------------------------------------------------------------------
SSECodeSeg SEGMENT PARA USE16
ASSUME CS:SSECodeSeg
----------------------------------------------------------------------------
SSEBegin PROC FAR
mov si,OFFSET MessC
mov di,0
int 0feh 顯示提示信息
pop eax 彈出出錯代碼
CALL16 SubCode_Sel,SubBegin 顯示出錯代碼
pop eax
add eax,4 按模擬的引起堆棧段錯誤的
push eax 指令調整返回地址
iretd
SSEBegin ENDP
----------------------------------------------------------------------------
SSECodeLen = $
SSECodeSeg ENDS
2.關於實例七的說明
上述模擬與演示程序的許多內容與實例六相同,下面就各異常處理程序和讀鍵盤任務的實現作些說明:
(1)除法出錯故障處理程序的實現
從源程序可見,除法出錯是在執行故意安排的被除數為2000,而除數為2的無符號除指令時引起的。作為演示,除法出錯故障處理程序先顯示一條提示信息,然後把存放被除數AX的內容右移一位,然後就返回。由於除法出錯為故障類異常,所以在故障處理結束後,仍執行該無符號除指令。顯然將再次引起同樣的故障,仍把被除數右移一位。由於每次處理時都把被除數減半,所以幾次故障後就不發生該故障了。
(2)溢出陷阱處理程序的實現
作為演示的溢出陷阱處理程序較簡單。先顯示一條提示信息,然後就返回。因為溢出異常為陷阱類異常,所以在陷阱處理結束後,就直接返回到引起陷阱指令的下一條指令。
(3)段不存在故障處理程序的實現
從源程序可見,段不存在故障是在執行故意安排的把一個選擇子送段寄存器GS的指令時引起的。該選擇子索引的描述符中的存在位P被置為0,表示對應段不在內存。在正常情況下,段不存在故障處理程序要把對應的段裝入內存,再把描述符內的P位修改為1,於是,在故障處理結束後,引起故障的指令可得到順序執行。為了簡單,這裡安排的故障處理程序先顯示一條提示信息,然後顯示出錯碼,最後調整堆棧中的返回地址並返回。段不存在故障提供一個出錯碼,該故障處理程序利用POP指令把它用堆棧中彈出,這樣堆棧指針就指向返回地址。由於段不存在異常屬於故障類異常,所以返回點仍是引起故障的指令。因此,演示程序調整了堆棧中的返回地址,使其返回到引起故障的指令的下一條指令。
(4)堆棧段出錯故障處理程序的實現
引起堆棧出錯故障的原因有多種,實例通過執行故意安排的偏移超過段界限的堆棧段訪問指令來模擬堆棧段出錯故障的產生。作為演示的堆棧出錯故障處理程序比較簡單,先顯示一條信息,然後顯示出錯碼,最後調整堆棧中的返回地址並返回。
(5)通用保護障處理程序的實現
引起通用保護故障處理程序的原因有多種,實例通過把一個指向系統段描述符的選擇子裝入數據段寄存器GS來模擬通用保護故障的產生。作為演示的通用保護故障處理程序,象上述兩個故障處理程序一樣比較簡單,先顯示一條提示信息,然後顯示出錯碼,最後調整堆棧中的返回地址並返回,但在廢除堆棧中的出錯碼和調整堆棧中的返回地址時采用了其它方法。
(6)異常處理程序的一般說明
在實例中,通向上述各種異常處理程序的門都是陷阱門。所以,在發生異常而轉入這些異常處理程序時,都不發生任務切換。於是,這些異常處理仍作為演示任務的一部分。
正常情況下,異常處理程序應該注意現場的保護和恢復,但為了簡單,作為演示的異常處理程序沒有能夠切實地保護現場。注意,這些異常處理程序所采用的處理方法與所模擬的指令有關,不適用於一般情況。
(7)顯示出錯代碼的過程
實例采用一個過程用於顯示出錯代碼,該過程的入口參數是AX含出錯碼。利用該過程不僅縮短程序,而且也用於表現異常處理程序的實現。
(8)讀鍵盤任務的實現
在實例的IDT中,0FFH號門描述符是任務門,指向一個獨立的任務。該任務的功能是讀鍵盤,接收一個指定范圍內的字符。演示任務通過指令“INT 0FFH”來調用它,接收一個代表需要模擬異常的字符。
為了簡單,該任務在實模式下讀鍵盤,接收指定范圍內的字符。為此,該任務每次經歷如下步驟:(1)轉到實模式。此前要作必要的准備,轉到實模式後,要恢復必須的實模式下的部分現場。(2)接收指定的字符。調用DOS功能顯示提示信息,調用BIOS中斷讀鍵盤,如果在指定范圍內,那麼就顯示,並保存在約定的數據段中。(3)轉回到保護模式,此前也要作必要的准備。
盡管在任務切換時,自動利用TSS保護和恢復現場,但由於該任務相當於一個讀鍵盤的過程,所以在開始任務時,還通過堆棧保護必要的現場,在結束任務時恢復現場。請特別注意,安排在該任務代碼段中的IRETD指令之後的轉移指令的作用。當執行IRETD指令時,由於NT位為1,所以按反向鏈進行任務切換,同時保存各寄存器到當前的TSS,為了下次進入時仍能從頭開始執行此任務,所以在IRETD後加一條轉移到該任務開頭的指令。