Windows異常分發


當有異常發生時,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:用來記錄異常標志,它的每一位代表一種標志。

ExceptionRecord:用來指向與該異常有關的另一個異常記錄。

ExceptionAddress;用來記錄異常地址,錯誤類異常與陷阱類異常會有區別。

NumberParameters:附加參數個數,即ExceptionInformation數組的有效個數。

 

 

登記CPU異常

對於CPU異常,KiTrapXX例程在完成針對本異常的特別動作后,通常會調用CommonDispatchException函數,它會在棧中分配一個EXCEPTION_RECORD結構,並把異常信息存儲到該結構中。在准備好這個結構后,它會調用內核中的KiDispatchExcption函數來分發異常。

 

登錄軟件異常 

簡單來捉,軟件異常是通過直接或間接調用內核服務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公布了兩個APIAddVectoredExceptionHandle和RemoveVectoredExceptionHandle來分別注冊和注銷回調函數VectoredHandle。

 

例如:

PVOID AddVectoredExceptionHandle(ULONG FirstHandle, PVECTORED_EXCEPTION_HANDLE   VectoredHandle)。

參數FirstHandle代表該函數被調用的順序,0表示希望最后調用, 1表示希望最先調用。如果注冊了多個回調函數,而且FirstHandle都是非零,那么最后注冊的最先被調用。

 

SEHVEH區別和聯系:

從應用范圍:SEH可以在用戶態代碼中,也可以用在內核態代碼中,但是VEH只能用在用戶態代碼中。

從優先角度:對於同時注冊了SEHVEH的代碼所觸發的異常,VEHSEH先得到處理權。

從登記方式:SEH注冊信息是固定結構存儲在線程棧中,VEH的注冊信息是存儲在進程的內存堆中。

從作用域: VEH對整個進程都有效,具有全局性。SEH是動態建立在所在函數函數棧上的,會隨函數返回而銷毀。

從編譯角度:SEH的登記和注銷是依賴編譯器編譯時所產生的數據結構和代碼的,VEH的注冊和注銷都是通過系統調用的API顯示染成的,不需要經過編譯器的特殊處理。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM