1.異常分類
一般來說,我們把Exception分為2類,一類是CPU產生的異常,我們稱之為CPU異常(或者硬體異常)。另一類為是通過調用RaiseException API產生的軟體異常,我們稱之為軟體異常。
Windows使用同一的方式(KiDispatchException)來描述和分發這兩類異常。但是,在處理各自異常時,會略有區別。
一般來說,異常處理過程可以分為2個階段,第1階段:異常登記過程;第2階段:異常分發過程。下面分別簡要介紹。
2.異常登記
1) CPU異常(硬體異常)登記:
在windows kernel中,存在一張中斷描述符表(IDT, Interupt Descriptor Table). IDT是一張位於核心態實體記憶體中的線性表,其有256個表項。IDT中的每個表項叫做門描述符(Gate Descriptor)。門描述符的基本作用就是將CPU異常對應的中斷號與其對應的異常處理函數KiTrapXX關聯起來。
例如,0號中斷(即除0錯誤)對應的處理常式為nt!KiTrap00
同時,我們可以通過以下debug comnand來列出IDT表中的各個表項。
lkd>!idt -a
對於CPU異常,通過中斷向量找到其中斷處理常式 KiTrapXX後,該KiTrapXX會調用CommDispatchException函數,其會擷取異常發生時候的適當參數,用來初始化EXCEPTION_RECORD結構體之後,開始調用KiDispatchException進行異常分發。
(簡述如下:中斷向量 - 〉KiTrapXX - > CommonDispatchException - >KiDispatchException)
EXCEPTION_RECORD的結構如下:
0:000> dt ntdll!_EXCEPTION_RECORD
+0x000 ExceptionCode : Int4B
+0x004 ExceptionFlags : Uint4B
+0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD
+0x00c ExceptionAddress : Ptr32 Void
+0x010 NumberParameters : Uint4B
+0x014 ExceptionInformation : [15] Uint4B
2) 軟體異常登記
軟體異常是通過直接或者間接調用核心服務NtRaiseException而產生的。而使用者態中可以通過RaiseException API,或者Try-catch等進階語言來調用這個核心服務,而通過RaiseException來登記軟體異常的過程可以簡單表述如下:
RaiseException在初始化一個EXCEPTION_RECORD結構體之後,開始調用NTDLL中的RtlRaiseException; RtlRaiseException在初始化CONTEXT結構體之後,開始調用核心中NtRaiseException, NtRaiseException再調用另外一個核心功能KiRaiseException。接下來KiRaiseException會調用KiDispatchException開始異常的分發。
如下所示:
CONTEXT是一個用來儲存使用者態-核心態切換現場的資料結構,主要是切換狀態時候的各個寄存器的狀態,其結構如下:
struct _CONTEXT
+0x000 ContextFlags : Uint4B
...
...
+0x09c Edi : Uint4B
+0x0a0 Esi : Uint4B
+0x0a4 Ebx : Uint4B
+0x0a8 Edx : Uint4B
+0x0ac Ecx : Uint4B
+0x0b0 Eax : Uint4B
+0x0b4 Ebp : Uint4B
+0x0b8 Eip : Uint4B
+0x0c4 Esp : Uint4B
...
...
3. 異常派發過程(Dispatch Exception)
當產生CPU異常或者軟體異常之後,最後都會調用到系統服務KiDispatchException進行異常的派發和處理。 對於CPU異常和軟體異常,其處理過程略有不同。下面分別簡要介紹
1)CPU異常派發過程:
對於第一輪的異常,其會嘗試先讓核心調試器來處理該異常(KiDebugRoutine)。如果KiDebugRoutine返回為True,也就是核心調試器處理了該異常,那麼便停止異常分發。否則,會調用kernel mode下的RtlDispatchException (NTOSKRNL)來試圖尋找已經註冊的結構化異常處理器。
如果沒有相應的異常處理器,系統會嘗試進行第二次分發。如果這次KeDebugRoutine仍然返回FAlSE,表明這是一個無人處理的異常,從而調用KeBugCheckEx引發藍屏。
其過程如下所示:
2) 軟體異常派發過程:
當軟體異常被派發到user-mode之後,如何處理這個exception呢?實際上,在TEB中有一個非常重要的結構體,叫做_NT_TIB。在_NT_TIB中有一個_EXCEPTION_REGISTRATION_RECORD類型的欄位叫做exceptionlist, 他的值就是指向異常處理器(_exception_handler)的首地址。EXCEPTION_REGISTRATION_RECORD是一個單向鏈表。
那麼這個EXCEPTION_REGISTRATION_RECORD的首地址值從何而來呢?他是儲存在FS:[0]寄存器中的。也就是說,當異常發生時,取得FS:[0]中的值,即為EXCEPTION_REGISTRATION_RECORD的首地址。我們從windbg中可以得到驗證,如下:
代碼
0:000> !teb
TEB at 7ffdf000
ExceptionList: 0012fd04
StackBase: 00130000
StackLimit: 0012e000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdf000
EnvironmentPointer: 00000000
ClientId: 0000312c . 00001a50
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffdb000
LastErrorValue: 0
LastStatusValue: c0000135
Count Owned Locks: 0
HardErrorMode: 0
0:000> r fs
fs=0000003b
0:000> dd fs:[0] L4
003b:00000000 0012fd04 00130000 0012e000 00000000
在得到該異常處理鏈表之後,便開始遍曆該鏈表。在遍曆鏈表的過程中,當前節點的Exception_hanlder會判斷是否能否handle當前的異常。如不能,則返回枚舉類型_EXCEPTION_DISPOSITION的一個值 (ExceptionContinueSearch),以便讓其繼續向後遍曆該鏈表,直到找到該exception_handler,並最終返回ExceptionContinueExecution,以便停止向下遍曆的過程。其過程如所示:
但是,如果遍曆到最後都沒有找到handle當前exception的exception handler,那麼便會觸發unhandled exception並最終調用ntdll!RtlUnhandledExceptionFilter,對於案頭型應用程式,其就會崩潰; 而對於服務端程式,為了更好的使用者體驗,這時候比如asp.net 的runtime 就會捕捉到該exception,在用戶端可能就看到service unavailable,或者伺服器端錯誤等等。
代碼
_TEB (thread environment blcok即線程環境塊)定義如下:
=============
typedef struct _TEB{
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
... ... }TEB
_NT_TIB的結構定義如下:
===========
typedef struct _NT_TIB{
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
... ... +0x018 Self : Ptr32 _NT_TIB
}NT_TIB
而ntdll!_EXCEPTION_REGISTRATION_RECORD的定義如下:
===========typedef struct _EXCEPTION_REGISTRATION_RECORD{
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
}EXCEPTION_REGISTRATION_RECORD ntdll!_EXCEPTION_DISPOSITION的定義如下:============
typedef enum _EXCEPTION_DISPOSITION{ ExceptionContinueExecution = 0
ExceptionContinueSearch = 1
ExceptionNestedException = 2
ExceptionCollidedUnwind = 3
}EXCEPTION_DISPOSITION
附件1:
NTDLL模組中與exception處理相關的常見幾個系統服務和相關函數
代碼
ntdll!RtlpUnhandledExceptionFilter
ntdll!RtlpDphRaiseException
ntdll!RtlpHeapExceptionFilter
ntdll!RtlpDphUnexpectedExceptionFilter
ntdll!RtlUnhandledExceptionFilter2
ntdll!RtlSetUnhandledExceptionFilter
ntdll!RtlDispatchException
ntdll!RtlRaiseException
ntdll!RtlpExecuteHandlerForException
ntdll!KiRaiseUserExceptionDispatcher
ntdll!KiUserCallbackExceptionHandler
ntdll!KiUserExceptionDispatcher
ntdll!KiUserApcExceptionHandler
參考文檔:
=======
A Crash Course on the Depths of Win32 Structured Exception Handling
http://www.microsoft.com/msj/0197/exception/exception.aspx