當有異常發生時,CPU會經過IDT表找到異常處理函數,即內核中的KiTrapXX系列函數,而後轉去執行。可是,KiTrapXX函數一般只是對異常作簡單的表徵和描述,爲了支持調試和軟件本身定義的異常處理函數,系統須要將異常分發給調試器或應用程序的處理函數。數組
爲了更好的管理異常,Windows系統定義了專門的數據結構EXCEPTION_RECORD來描述異常。數據結構
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD, *PEXCEPTION_RECORD;
ExceptionCode:異常代碼,32位整數。函數
ExceptionFlags:用來記錄異常標誌,它的每一位表明一種標誌。spa
ExceptionRecord:用來指向與該異常有關的另外一個異常記錄。線程
ExceptionAddress;用來記錄異常地址,錯誤類異常與陷阱類異常會有區別。設計
NumberParameters:附加參數個數,即ExceptionInformation數組的有效個數。調試
登記CPU異常code
對於CPU異常,KiTrapXX例程在完成針對本異常的特別動做後,一般會調用CommonDispatchException函數,它會在棧中分配一個EXCEPTION_RECORD結構,並把異常信息存儲到該結構中。在準備好這個結構後,它會調用內核中的KiDispatchExcption函數來分發異常。orm
登陸軟件異常 blog
簡單來捉,軟件異常是經過直接或間接調用內核服務KiRaiseException而產生的。函數內部會把Context上下背景文複製到當前線程的內核棧,接下來調用KiDispatchExcption函數來進行分發。
綜上所述,無論什麼異常最後都會調用內核中的KiDispatchExcption函數進行分發,也就是說Windows用統一的方式來管理異常。
VOID
KiDispatchException (
IN PEXCEPTION_RECORD ExceptionRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN FirstChance
)
ExceptionRecord:用來描述要分發的異常。
ExceptionFrame:指向的KTRAP_FRAME結構,用來描述異常發生時的處理器狀態,包括各類通用寄存器、調試寄存器、段寄存器等。
PreviousMode:枚舉,用來表示前一種狀態是內核模式仍是用戶模式。
FirstChance:表示第幾輪分發。
下面先來看看KiDispatchException 分發示意圖。
從圖中咱們能夠看到,KiDispatchException會先調用KeContextFromKframes函數,目的是根據TrapFrame參數指向的KTRAP_FRAME結構產生一個CONTEXT結構,以供向調試器和異常處理器函數報告異常時使用。
接下來會根據模式是內核模式仍是用戶模式進行分發。下面具體說明。
內核態異常的分發過程
對於第一輪異常KiDispatchException會試圖先通知內核調試器來處理異常,若是沒有處理異常,那麼會調用RtlDispatchExcption,試圖尋找已經註冊的結構化異常處理器(SEH)。
若是也沒有找到,那麼就會給內核調試器第二次處理的機會。仍然返回FLASE的話,就會調用KeBugCheckEx觸發藍屏。
用戶態異常的分發過程
首先,KiDispatchException會判斷是否發送給內核調試器,但內核調試器一般不處理用戶態異常,因此KiDispatchException會試圖發送給用戶態調試器,方法是調用DbgkForwardException。若是不成功,KiDispatchException下一步動做是試圖尋找異常處理塊來處理該異常,由於用戶異常發生在用戶態代碼中,異常處理塊也是在用戶態代碼中。因此須要轉到用戶態去執行。(這也就是相對於內核態異常的分發過程,用戶態異常的分發過程會麻煩一點的緣由,具體方式再也不累贅,參考《軟件調試》)若是最終也返回FALSE,那麼就會分發第二輪。
結構化異常處理SEH
爲了讓系統和應用程序代碼均可以簡單方便地支持異常處理,Windows定義了一套標準的機制來處理代碼的設計和編譯,這套機制被稱爲結構化異常處理(Structured Exception Handling),簡稱SEH。
異常處理結構以下:
__try { //被保護塊 } __except(過濾表達式) { //異常處理塊 }
經過TEB結構的NtTib成員能夠很容易的訪問進程的SEH鏈,方法很簡單。
TEB.NtTib.ExceptionList成員是TEB結構體的第一個成員。FS段寄存器指向段內存的起始地址,TEB結構體即位於此,因此經過下列公式能夠輕鬆獲取TEB.NtTib.ExceptionList的地址。
TEB.NtTib.ExceptionList = FS:[0]
那麼那彙編語言實現的話:
PUSH @Handler
PUSH DWORD PTR FS:[0]
MOV DWORD PTR FS:[0], ESP
向量化異常處理VEH
從WindowsXP開始,Windows還支持一種名爲向量化異常處理的異常處理機制,簡稱VEH。
與SEH既能夠在用戶態又能夠在內核態不一樣,VEH只能在用戶態程序中。
VEH的基本思想是經過註冊一下的原型的回調函數來接收和處理異常。
LONG CALLBACK VectoredHandle(PEXCEPTION_POINTERS ExceptionInfo);
相應的,Windows公佈了兩個API,AddVectoredExceptionHandle和RemoveVectoredExceptionHandle來分別註冊和註銷回調函數VectoredHandle。
例如:
PVOID AddVectoredExceptionHandle(ULONG FirstHandle, PVECTORED_EXCEPTION_HANDLE VectoredHandle)。
參數FirstHandle表明該函數被調用的順序,0表示但願最後調用, 1表示但願最早調用。若是註冊了多個回調函數,並且FirstHandle都是非零,那麼最後註冊的最早被調用。
SEH與VEH區別和聯繫:
從應用範圍:SEH能夠在用戶態代碼中,也能夠用在內核態代碼中,可是VEH只能用在用戶態代碼中。
從優先角度:對於同時註冊了SEH與VEH的代碼所觸發的異常,VEH比SEH先獲得處理權。
從登記方式:SEH註冊信息是固定結構存儲在線程棧中,VEH的註冊信息是存儲在進程的內存堆中。
從做用域: VEH對整個進程都有效,具備全局性。SEH是動態創建在所在函數函數棧上的,會隨函數返回而銷燬。
從編譯角度:SEH的登記和註銷是依賴編譯器編譯時所產生的數據結構和代碼的,VEH的註冊和註銷都是經過系統調用的API顯示染成的,不須要通過編譯器的特殊處理。