## 9.8 異常條件(Exception Conditions)
以下幾節詳細的討論可能的異常條件。每個類型都將分為錯誤(fault)、陷阱(trap)、或中止(abort)來講述。這種分類有助于系統程序員重想引起異常的子程序。
錯誤:?? 當發生錯誤時,保存的CS和EIP將指向了引起錯誤異常的指令。
陷阱:?? 當發生陷阱時,保存的CS和EIP將指向引起陷阱的指令的下一條指令,且這下一條指令是動態的一下條。如果陷阱是在一條改變程序控制流的指令期間發生時,保存的CS和EIP則指向了控制改變后的程序的指令。例如,如果在執行一條JMP指令期間發生一個陷阱,壓入堆棧的CS和EIP將是JMP的目的地指令,而不是在JMP下面的指令。
中止:?? 中止是不能精確定位引起異常的指令或不能重起指令的異常。中止用來報告嚴重的錯誤,如硬件出錯或系統表的不一致性或錯誤。
### 9.8.1 中斷0——除法錯(Divide Error)
除法錯誤發生在當DIV或IDIV指令的除數為0時。
### 9.8.2 中斷1—— 調試異常(Debug Exceptions)
處理器在很多情況下都會引發這個中斷。這個異常是錯誤還是陷阱取決于以下條件:
+ 指令地址斷點錯誤(Intruction address breakpoint fault)
+ 數據地址斷點陷阱(Data address breakpoint trap)
+ 通用錯誤(General detect fault)
+ 單步陷阱(Single-step trap)
+ 任務切換斷點陷阱(Task-switch breakpoint trap)
處理器在這種情況下不會壓入出錯碼。異常處理程序可以檢察調試寄存器來決定是什么條件引起的異常。關于調試和調試寄存器,參看第12章。
### 9.8.3 中斷3——斷點(Breakpoint)
INT 3指令引發這個陷阱。INT3是一條一字節長的指令,這樣就可以很容易地用斷點操作碼來替代代碼段中操作碼。操作系統或調試系統可以用一個數據段的別名來指向代碼段,這樣可以很方便地在任何地方放入INT3指令,來引起異常,從而進行一些進一步的操作。調試器經常在一個任務的關鍵地方,使用斷點來顯示寄存器、變量。
保存的CS:EIP值,指向了斷點異常指令的下一個字節。如果一個調試器用調試操作碼來替代了正常的指令,它必須要把保存的EIP減去1個字節的值。關于調試的信息,請參看第12章。
### 9.8.4 中斷4——溢出(Overflow)
只有當處理器遇到INTO指令且OF(溢出位)標志設置時,處理器才引發這個異常。因為有符號數和無符號數使用同樣的算數指令,處理器不能確定到底是哪一種操作,所以當發生溢出時,處理器并不自動引發異常。取而代之的是,如果被作為符號數處理的話,且有可能超出表示范圍的話,處理器設置OF位。當對符號數操作時,仔細的程序員和編譯器將自已測試OF位或使用INTO指令。
### 9.8.5 中斷5——越界(Bounds Check)
當處理器執行BOUND指令時,且操作數超出了界限時,處理器將引發這個錯誤。程序可以使用BOUND指令來檢察一個有符號的到指定內存區域的數組索引。
### 9.8.6 中斷6——非法操作碼(Invalid Opcode)
當執行部件遇到一條非法操作碼的指令時,引發這個錯誤。(只有當執行到時,才會引發該異常,也就是說指令預取時取到一條非法操作數的指令時,并不會引發異常)。處理器不會壓入出錯碼。異常可以在同一個任務中處理。
當指定類型的操作碼的操作數非法時,也會引起該異常。例如,一條段間JMP去訪問一個寄存器操作數、或LES指令訪問一個寄存器源操作數。
### 9.8.7 中斷7——協處理器不可用(Coprocessor Not Available)
當以下兩種情況其中之一發生時,引發這個異常:
+ 處理器則到一條ESC(escape)指令,且CR0(control register zero)中的EM(emulate)位設置時。
+ 處理器遇到一條WAIT指令或和條ESC指令,且CR0中的MP(monitor coprocessor)位和TS(task switched)位都設置時。
關于協處理器,請檢看第11章。
### 9.8.8 中斷8——雙重錯(Double Fault)
通常,當處理器剛要調用一個先前的異常的異常處理程序時又檢測到一個異常,兩個異常可以被連續地處理。但是,如果處理器不能串行的處理他們,處理器引發一個雙重錯異常。當兩個錯誤被聲明為一個雙重錯誤時,80386把異常分為3類:良性異常(benign exceptions)、貢獻異常(contributory exceptions)、和頁錯誤(page faults),圖9-3顯示了這種分類。
表9-4顯示了引起雙重錯誤的異常組合。
處理器總是壓入一個出錯碼到雙重出錯的異常處理程序。但是,出錯碼總是0。出錯的指令是不可重起的。如果在將要調用雙重異常處理程序時,又發生了一個異常,處理器將停機。

### 9.8.9 中斷9——協處理器段超出(Coprocessor Segment Overrun)
處理器在保護模式執行時,當向協的中間部分傳送一個協處理器的操作數到NPX時,如果檢測到一個頁錯誤或段錯誤時,處理器引發這個異常。
### 9.8.10 中斷10——非法TSS(Invalid TSS)
當在任務切換時,發現新的TSS非法時,處理器發生中斷10。圖9-5顯示了非法的TSS的情況。出錯碼被壓入堆棧,以幫助檢察錯誤引發的原因。EXT位指示了當前的異常是否是由程序外界引起的。也就是通過一個任務門引起的外部中斷切換到這個非法的TSS。
這個錯誤可能發生原先的任務中或發生在新的任務的上下文中。直到處理器完全檢測了新TSS的存在后,異常發生在原先任務的上下文中。當新任務的TSS存在性被檢測后,任務切換就算完成了。也就是說更新了TR。如果任務切換是由于CALL指令或中斷,新任務的TSS中的返回鏈將被設置為當前的TSS(舊的TSS)。任一個在這時之后發生的錯誤將在新的任務中處理。
為了能在一個任務中正確的處理這個異常,異常10應該是通過任務門在一個新的任務中處理的。

### 9.8.11 中斷11——段不存在(Segment Not Prosent)
當處理器發現一個描述符的存在位為0時,處理器引發異常11。處理器可能在以下情況下引發該錯誤:
+ 當加載CS,DS,ES,FS,GS,寄存器時,但加載SS寄存器引發堆棧錯誤。
+ 當用LLDT指令加載LDT寄存器時。在任務切換時加載LDT寄存器,則引發非法
TSS異常。
+ 當使用一個不存在的門描述符時。
這樣的錯誤是可以重起的。如果異常處理程序把段的存在位設置且返回后,被中斷的程序將繼續執行。
如果段不存在異常發生在任務切換過程中,任務切換的步驟沒有完全完成。在任務切換的過程中,處理器首先加載所有的段寄存器,然后檢察它們內容的有效性。如果一個段不存在異常被檢測到,那么剩下的段寄存器的值將是未經過檢測的,所以可能是不可用來訪問內存的。異常處理程序不應該在沒有引起另一個異常前依賴于此時的CS,SS,DS,ES,FS和GS。異常處理程序應該在恢復新任務之前首先檢測所在段寄存器。否則,通用保護異常可能會隨后發生,以致錯誤檢測將更加困難。有3種方法來處理這種情況:
1、? 在一個任務中處理段不存在異常。當切換回被中斷的任務時,處理器將從TSS加載時檢測寄存器的有效性。
2、? 壓入和彈出所有段寄存器。每一條POP指令將引起處理器檢測段寄存器的新內容。
3、? 細查TSS段中存儲的每一個段寄存器映象,模擬處理器在加載段寄存器時的檢測。
這個異常壓入一個出錯碼到堆棧上。EXT位指出是否是外部事件引起的段不存在的異常。如果出錯碼訪問的是IDT的項,I位將被設置,也就是說一條INT指令訪問了一個不存在的門。
操作系統通常使用“段不存在”異常來實現基于段的虛擬內存。但是,一個門描述中的不存位,通常不是指段的不存在(因為門不一定要對應著一個段)。門描述符的不存在可以被操作系用來引發一個特別重要的異常。
### 9.8.12 中斷12——堆棧異常(Stack Exception)
堆棧異常通常發生在以下兩種情況下:
+ 在使用SS寄存器來訪問內存時,如果發生了任何的界限違例。這包括了基于堆棧的指令,如POP,PUSH,ENTER,還有LEAVE,當然還有其它的一些隱式使用SS的內存訪問(例如,MOV AX, [BP+6])。當堆棧太小而不能容納指定的局部變量時,ENTER指令將引起這個異常。
+ 當加載一個選擇子到SS寄存器時,且該選擇子指向一個標識為不存在但有效的描述符。這種情況可能發生在任務切換中、段間CALL指令、段間返回、LSS指令、或者一條向SS加載的MOV或POP指令。
當處理器發現堆棧異常時,它會壓入一個出錯碼到異常處理程序的堆棧上。如果異常是由堆棧段不存在或在段間CALL指令間時的新堆棧溢出的話,出錯碼包含了出問題的段的選擇子(異常處理程序可以測試描述符的存在位來確定是哪個異常發生的)。否則出錯碼為0。
造成這種異常的指令在所有情況下都是可重起的。被壓入入異常處理程序堆棧的返回地址指向了一條需要重起的指令處,一般來說就是引起異常的指令。但是,在一個任務切換過程中,加載不存在的堆棧段寄存器時,它指向了新任務的第一條指令。
當堆棧錯誤在任務切換時發生的話,段寄存器不能再用來內存訪問了。在任務切換中,選擇子是在描述符被檢察之前加載到寄存器里的,所以可能并不能用來作內存訪問。堆棧異常處理程序不應在未引起另一個異常之前依賴于CS,SS,DS,ES,FS和GS中的值。異常處理程序應該要在重起任務前檢測所有的段寄存器的值。否則,通用保護異常錯誤將會使以后的錯誤調試更加困難。
### 9.8.13 中斷13——通用保護異常(General Protection Exception)
所有保護模范規則的違例,如果沒有引起另一個異常,將引起一個通用保護異常。這些包括(但不局限于):
1、? 當使用CS,DS,ES,FS,或GS,做內存訪問時的段界限超出。
2、? 訪問描述符表時的界限超出。
3、? 向一個不可執行的段作控制轉移。
4、? 向一個只讀段或一個代碼段寫入數據。
5、? 從只執行的代碼段內讀取數據。
6、? 用一個指向只讀描述符的選擇子加載SS寄存器(除非選擇子來自于任務切換過程時的TSS段中,這種情況將引發一個TSS異常)
7、? 把系統段描述符加載到SS,DS,ES,FS,或GS。
8、? 把一個不可讀的執行代碼段描述符加載到DS,ES,FS或GS中。
9、? 將代碼段描述符加載到SS寄存器。
10、????? DS,ES,FS,和GS中包含一個空選擇子(null selector)來訪問內存時。
11、????? 向一個正忙的任務切換。
12、????? 違反特權級規則。
13、????? 把一個PG=1而PE=0的值加載到CR0內。
14、????? 通過中斷門或陷阱門從V86模式轉移到不是特權級0時。
15、????? 執行一個長度大于15個字節的指令(只可能發生在達多的前綴用于一條指令前時)。
通用保護異常是一個錯誤。在響應這個異常時,處理器壓入一個出錯碼到異常處理程序的堆棧上。如果在加載一個描述符時,發生異常,出錯碼包含了指向此描述符的選擇子。否則,出錯碼為空。出錯碼中的選擇子可能來自以下:
1、? 指令操作數。
2、? 一個指令操作數中的門,選擇子在門中。
3、? 在任務切換過程中,在TSS中的選擇子。??????
### 9.8.14 中斷14——缺頁異常(Page Fault)
當啟用了分頁后(PG=1),當處理器將線性地址轉換到物理地址時,檢測到以下情況中的一個將引發一個異常:
+ 所需要的頁目錄或頁表項的存在位為0時。
+ 當前子程序沒有足夠的權限來訪問指定的頁面。
處理器為缺頁異常處理程序提供以下兩種信息以便異常的診斷和從異常中恢復:
+ 一個在堆棧上的出錯碼。為缺頁異常提供的出錯碼和一般的異常的出錯碼格式有所不同(見圖9-8)。出錯碼告訴異常處理程序以下三件事:
1、引起異常的原因是由于頁不存在還是沒有足夠的權限來訪問指定的頁。
2、異常發生時,處理器是處于用戶模式還是處于超級用戶模式。
3、在訪問內存時,是讀操作還是寫操作。
+ CR2 (控制寄存器2)。處理器把引起異常的線性地址放在CR2中(如圖9-9)。異常處理程序可以使用這個線性地址來定位頁目錄項和頁表項。如果在這個異常處理過程中,允許另一個缺頁異常產生的話,異常處理程序應該負責把CR2壓入到堆棧中。

9.8.14.1 在任務切換中的缺頁異常(Page Fault During Task Switch)
在任務切換時,處理器可能訪問以下4個段:
1、? 把當前的任務狀態寫入到它的任務狀態段中。
2、? 讀取GDT來定位新任務的TSS描述符。
3、? 讀取新任務的TSS,以便檢測段描述符的類型。
4、? 可能會讀取新任務的LDT,以便來檢測存儲在新任務TSS中段寄存器。
當訪問他們中的任意一個段時,都可能出現缺頁異常。在后兩種情況下,異常算發生在新任務的上下文里。保存的指針指向新任務的下一條指令,而不是引起任務切換的指令。如果操作系統的設計允許在任務切換時發生缺頁異常,缺頁異常錯誤應該通過一個任務門來處理。

9.8.14.2 缺頁錯誤內的不一致堆棧指針(Page Fault with Inconsistent Stack)
為了保證在缺頁異常中不會讓處理器使用非法的堆棧指針(SS:ESP),應該特別注意。在80386早期寫的軟件常常使用一對指令還改變堆棧,如:
MOV SS,AX
MOV SP,StackTop
在80386下,第二條指令要訪問內存,可能會在SS改變后而在SP改變前發生缺頁異常。這時,堆棧的兩部分SS:SP(或,對于32位程序來說,SS:ESP)將不一致。
如果在處理缺頁異常時,發生了堆棧切換到一個定義好的堆棧(也就是說處理程序是一個任務或是一個特權級更高的子程序)的話,處理器就不會使用不一致的堆棧指針。即使這樣,如果缺頁異常是在一個陷阱門或中斷門時處理的,且缺頁異常處理程序和發生缺頁異常的程序是在同一特權級的話,處理器會使用當前的(非法的)堆棧指針。
在實現分頁和缺頁異常處理程序在同一任務內(用陷阱門或中斷門)的系統里,同特權級的軟件應該用新的LSS指令,而不要使用一對上面那樣的指令,來初始化堆棧。當缺頁異常處理程序在特權級0(正常情況下應該是)執行時,問題則只局限在特權級0的代碼,一般說來是操作系統內核。
### 9.8.15 中斷16——協處理器錯(Coprocessor Error)
當處理器從ERROR#引腳發現一個80287或80387發送的一個報告時,處理器引發這個異常。80386只在一定情況下的ESC指令或者當遇到WAIT指令且MSW中的EM位為0時(沒有摸擬)執行前才檢測這個引腳。關于協處理器的信息,請參看第11章。
- 第一章 80386介紹
- 1.1 該手冊的組織結構
- 1.2 其他文獻
- 第二章 編程基本模型
- 2.1 存儲器組織和段
- 2.2 數據類型
- 2.3 寄存器
- 2.4 指令格式
- 2.5 操作數選擇
- 2.6 中斷和異常
- 第4章 系統寄存器
- 4.1 系統寄存器 (System Registers)
- 4.2 系統指令 (System Instructions)
- 第五章 內存管理
- 5.1 分段地址轉換(Segment Translation)
- 5.2 分頁地址轉換(Page Translation)
- 5.3 混合分段和分頁地址轉換(Combining Segment and Page Translation)
- 第六章 內存管理
- 6.1 為什么要保護(Why Protection?)
- 6.2 80386保護機制概述(Overview of 80386 Protection Mechnaisms)
- 6.3 段級保護(Segment-Level Protection)
- 6.4 頁級保護(Page-Level Protection)
- 6.5 混合分頁和分段保護(Combining Page and Segment Protection)
- 第7章 多任務(Multitasking)
- 8.1 I/O 尋址(I/O Addressing)
- 7.1 任務狀態段(Task State Segment)
- 7.3 任務寄存器(Task Register)
- 7.4 任務門描述符(Task Gate Descriptor)
- 7.5 任務切換(Task Switching)
- 7.6 任務鏈(Task Linking)
- 7.7 任務尋址空間(Task Address Space)
- 第8章 輸入 輸出
- 8.2 I/O 指令(I/O Instructions)
- 8.3 保護和I/O(Protection and I/O)
- 第9章 異常和中斷(Exceptions and Interrupts)
- 9.1 識別中斷(Identifying Interrupts)
- 9.2 允許和禁止中斷(Enabling and Disabling Interrupts)
- 9.3 同時發生的中斷和異常的優先級(Priority Among Simultaneous Interrupts and Exceptions)
- 9.4 中斷描述符表(Interrupt Descriptor Table)
- 9.5 IDT 描述符(IDT Descriptors)
- 9.6 中斷任務和中斷子程序(Interrupt Tasks and Interrupt Procedures)
- 9.7 出錯碼(Error Code)
- 9.8 異常條件(Exception Conditions)
- 9.9 異常總結(Exception Summary)
- 9.10 出錯碼總結(Error Code Summary)
- 第10章 初始化(Initialization)
- 10.1 復位后處理器狀態(Processor State After Reset)
- 10.2 實模式初始化(Software Initialization for Real-Address Mode)
- 10.3 切換到保護模式(Switching to Protected Mode)
- 10.4 保護模式初始化(Software Initialization for Protected Mode)
- 10.5 初始化示例
- 10.6 TLB測試
- 第十四章 80386實地址模式
- 14.1 物理地址構成
- 14.2 寄存器和指令
- 14.3 中斷和異常處理
- 14.4 進入和離開實地址模式
- 14.6 實地址模式異常
- 14.7 與8086的不同
- 14.8 與80286實地址模式的不同