使用者空間的異常機制是對於系統空間的異常機制的類比。在核心中,並非所有的異常都是一來就進入“基於SEH架構(Frame-Based)”的異常處理, 而是先進入_KiTrap14()等等類似於向量中斷的入口,在那裡可以被攔截進行一些優先的處理,例如頁面換入和對Copy-On-Write頁面的處 理等等。這些處理是全域性質的,而不屬於某個SEH域。這相當於是一層全域性的過濾。只有不屬於這個層次的異常才會進入基於SEH架構的異常處理。
為此,使用者空間的每個進程還有一個“向量式異常處理”程式入口的隊列RtlpVectoredExceptionHead,隊列中的每個節點都指向一個具 體的異常處理函數。就像核心中“登記”中斷處理常式一樣,在使用者空間也可以通過RtlAddVectoredExceptionHandler()登記向 量式的例外處理常式,這樣就可以在異常的源頭上加以攔截(不過在Windows的資料中並未見到有使用者空間向量式異常處理的存在,但是在Wine的代碼中 倒是有)。這裡RtlpExecuteVectoredExceptionHand lers()的作用就是先掃描這個隊列,讓隊列中的各個節點依次認領,如果被認領並且得到了處理,就不再進入基於SEH架構、即 ExceptionList的處理了(代碼中if語句的條件“!=”疑為“==”之誤,待考),所以處理完之後就通過系統調用NtContinue()返 回核心,而進入核心以後又會返回使用者空間當初因異常而被中斷的地方。所以,系統調用NtContinue()實質上就是對“中斷返回”的類比。在講述 APC機制的時候,讀者已經看到使用者空間的APC函數也是通過NtContinue()實行“中斷返回”的(相當於Linux的系統調用 sigreturn())。
要是在向量式異常處理隊列中沒有得到處理,那就要通過RtlDispatchException()進行基於SEH架構的異常處理了。
讀者在上一篇漫談中已經看到過RtlDispatchException()的代碼,大致如下:
[KiUserExceptionDispatcher() > RtlDispatchException()]
RtlDispatchException(IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT Context)
{
. . . . . .
/* Get the current stack limits and registration frame */
RtlpGetStackLimits(&StackLow, &StackHigh);
RegistrationFrame = RtlpGetExceptionList();
/* Now loop every frame */
while (RegistrationFrame != EXCEPTION_CHAIN_END)
{
. . . . . . /* 掃描ExceptionList,直至有節點認領/處理本次異常 */
}
/* Unhandled, return false */
return FALSE;
}
但是,那是在核心中,作為異常處理的第一步措施(FirstChance),由KiDispatchException()針對發生於系統空間的異常而加 以調用的。而現在,則顯然是在使用者空間調用這個函數。那莫非在使用者空間就可以直接調用核心中的函數嗎?不是的,這個 RtlDispatchException()並非那個RtlDispatchException(),這隻是“代碼重用”而已。這個 RtlDispatchException()編譯以後串連在Ntdll.dll的映像中(不過並不匯出),並且所處理的ExceptionList是在 使用者空間,所使用的堆棧也是使用者空間堆棧。
ExceptionList是通過RtlpGetExceptionList()擷取的。
_RtlpGetExceptionList@0:
/* Return the exception list */
mov eax, [fs:TEB_EXCEPTION_LIST]
ret
常數TEB_EXCEPTION_LIST定義為0,所以總之就是[fs:0]。當CPU運行於系統空間時,其段寄存器FS指向KPCR;而當運行於使用者 空間時則指向TEB。所以,不管是在系統空間還是使用者空間,RtlpGetExceptionList()總能取到各自的ExceptionList指 針。
RtlpGetStackLimits()也是一樣:
_RtlpGetStackLimits@8:
/* Get the stack limits */
mov eax, [fs:TEB_STACK_LIMIT]
mov ecx, [fs:TEB_STACK_BASE]
/* Return them */
mov edx, [esp+4]
mov [edx], eax
mov edx, [esp+8]
mov [edx], ecx
/* return */
ret 8
常數TEB_STACK_LIMIT定義為8,TEB_STACK_BASE定義為4,分別是StackLimit和StackBase兩個欄位相對於 TEB起點的位移。而在KPCR資料結構中同樣也有這兩個欄位,而且也在相同的位置上。當然,前者用於使用者空間堆棧,而後者用於系統空間堆棧。由此可見, 這些資料結構確實是經過精心設計的。另一方面,在處理ExceptionList的過程中所涉及的各種資料結構也都既用於系統空間又用於使用者空間。
這樣,同一個函數RtlDispatchException()的代碼就既可用於核心,也可用於使用者空間的程式庫。既然如此,我們也就不必再看一遍RtlDispatchException()的代碼了。
回到KiUserExceptionDispatcher(),執行RtlDispatchException()的結果主要有三種可能。
一種是當前異常為ExceptionList中的某個節點所認領、即順利通過了其過濾函數的過濾,並執行了長程跳轉。顯然,在這種情況下 RtlDispatchException()不會返回,包括KiUserExceptionDispatcher()在內的函數調用架構均因長程跳轉而 被跨越和丟棄。不僅如此,複製到使用者空間堆棧上的兩個資料結構也因為長程跳轉而被丟棄。
第二種是荒掣黿詰閎狹熗耍滄髁四承┐恚殘砘怪蔥辛蘇飧黿詰愕納坪蠛遣⑽粗蔥諧こ燙聳盧tlDispatchException() 返回TRUE,於是便通過系統調用NtContinue()完成“中斷返回”。由於堆棧上的上下文資料結構含有當初因異常而進入系統空間時所保留的現場, 最後就返回到了(使用者空間中)當初因異常而被中斷了的地方。
第三種是沒有任何節點認領,RtlDispatchException()中的while迴圈窮盡了所有的節點,所以返回FALSE。這一定是出了什麼問 題,例如ExceptionList被損壞了,因為在正常情況下這是不應該發生的。前面已經提及,在使用者空間,整個線程的代碼都是放在一個SEH域裡執行 的,所以ExceptionList一定是非空,並且其最後一個節點(最早進入的)就是前面在BaseProcessStartup()中進入的那個 SEH域。這個SEH域的過濾函數一般都返回_SEH_EXECUTE_HANDLER,所以是來者不拒。因此,既然窮盡了ExceptionList, 就一定是發生了嚴重的問題,所以要通過系統調用NtRaiseException()引起一次軟異常,以進入針對當前異常的第二步措施。注意這裡調用 NtRaiseException()時的第三個參數為FALSE,表示這已經不是第一次嘗試。
此外還有一種可能,就是在RtlDispatchException()內部就已調用了RtlRaiseException(),例如針對某個節點的 RtlpExecuteHandlerForException()返回ExceptionContinueExecution,而異常紀錄塊中 ExceptionFlags的標誌位EXCEPTION_NONCONTINUABLE卻又是1,此時就要通過RtlRaiseException() 引起原因為STATUS_NONCONTINUABLE_EXCEPTION的軟異常。
在正常的情況下,對於調用點而言,系統調用NtContinue()和NtRaiseException()是不返回的,返回就說明系統調用本身(而不是 對異常的處理)出了錯,例如參數有問題。因此,要是果真從這兩個系統調用返回,那麼正常的處理已經山窮水盡,只能又求助於軟異常了,這就是下面對於 RtlRaiseException()的調用。
調用RtlRaiseException()的目的在於類比一次異常,這“異常”已經不是原來所要處理的異常,而是對原來的異常處理不下去了。此時需要解 決的已經不是引起原來那個異常的問題,而是為什麼處理不下去的問題。所以原來的“異常代碼”ExceptionCode可能是 STATUS_ACCESS_VIOLATION,而現在的ExceptionCode則是從NtRaiseException()或 NtContinue()返回的出錯代碼Status。同樣的道理,通過RtlDispatchException()發起的則是 STATUS_NONCONTINUABLE_EXCEPTION軟異常。正因為現在要發起的異常並非原來要處理的異常,所以要為 RtlRaiseException()準備一個新的異常紀錄塊。
另一方面,之所以通過RtlRaiseException()發起各種不同類型的軟異常,是建立在一個前提上的,那就是相信這個異常最終總會得到處理。這 包括兩個方面,首先是ExceptionList中應該有能夠認領/處理此類異常的節點,即已經準備好了應對此類問題所需的程式。其次,即使 ExceptionList中沒有這樣的節點,終歸也有應對的辦法,那就是前面講的三個步驟,包括Debug、結束當前線程、甚至“死機”在內。注意“死 機”也有受控和失控之分,使CPU進入停機狀態是有控制的死機,那也比任由CPU在程式中亂跳一氣要好。
然而,儘管現在要發起的是與原來不同的異常,但是這畢竟是在處理原來異常的過程中出的問題,與原來的異常是有關係的,應該讓認領新發起異常的處理者知道這 一點。所以異常紀錄塊中有個指標ExceptionRecord,遇到像這樣的情況就讓新的異常紀錄塊通過這個指標指向原來的異常紀錄塊。所以,軟異常的 紀錄塊可以成串,每個紀錄塊都可以指向本次異常的禍端,只有硬異常的紀錄塊不會指向別的紀錄塊。
顯然,RtlRaiseException()是個重要的函數。同樣,這個函數的代碼也是核心和使用者空間兩棲的。注意下列的調用路線只是許多情景中的一種,實際上調用這個函數的地方很多。
[KiUserExceptionDispatcher() > RtlRaiseException()]
VOID NTAPI RtlRaiseException(PEXCEPTION_RECORD ExceptionRecord)
{
CONTEXT Context;
. . . . . .
/* Capture the context */
RtlCaptureContext(&Context);
/* Save the exception address */
ExceptionRecord->ExceptionAddress = RtlpGetExceptionAddress();
/* Write the context flag */
Context.ContextFlags = CONTEXT_FULL;
/* Check if we're being debugged (user-mode only) */
if (!RtlpCheckForActiveDebugger(TRUE))
{
/* Raise an exception immediately */
Status = ZwRaiseException(ExceptionRecord, &Context, TRUE);
}
else
{
/* Dispatch the exception and check if we should continue */
if (RtlDispatchException(ExceptionRecord, &Context))
{
/* Raise the exception */
Status = ZwRaiseException(ExceptionRecord, &Context, FALSE);
}
else
{
/* Continue, go back to previous context */
Status = ZwContinue(&Context, FALSE);
}
}
/* If we returned, raise a status */
RtlRaiseStatus(Status);
}
調用參數ExceptionRecord指向一個異常紀錄塊。如上所述,這個紀錄塊中記載著異常的性質,並且一般都通過指標指向另一個異常紀錄塊。
首先通過RtlCaptureContext()擷取當時的上下文,並通過RtlpGetExceptionAddress()擷取RtlRaiseException()的返回地址,以此作為發生本次異常的地址。
具體的操作視RtlpCheckForActiveDebugger()的結果而異。這個函數有系統空間和使用者空間兩個不同的版本。目前系統空間的版本只 是返回調用參數,所以在這裡總是返回TRYE。而使用者空間的版本則返回NtCurrentPeb()->BeingDebugged,所以只在受調 試時返回TRUE。
所以,如果是在使用者空間調用RtlRaiseException(),而又不是在受調試,就啟動系統調用ZwRaiseException()。注意此時 的第三個參數為TRUE,表示這是第一次努力。可見,在這種情況下RtlRaiseException()是通過系統調用 ZwRaiseException()實現的。而且所用的函數名是ZwRaiseException(),在使用者空間和系統空間都可以調用。
反之如果是在系統空間,或者雖在使用者空間但是在受調試,那就先直接調用RtlDispatchException(),看看本空間的 ExceptionList中是否有節點可以認領和處理。如果有、並且實施了長程跳轉,那就不返回了。而如果返回的話,則視傳回值為TRUE或FALSE 而分別啟動系統調用ZwRaiseException()或ZwContinue()。如果RtlDispatchException()返回TRUE, 就說明已經得到ExceptionList中某個節點的認領、但是並未執行長程跳轉而返回ExceptionContinueExecution,此時通 過ZwRaiseException()進行第二次努力,注意這裡調用ZwRaiseException()時的第3個參數為FALSE,表示對 ExceptionList的搜尋已經失敗,下面該採取第二步措施了。而如果RtlDispatchException()返回FALSE,則說明 ExceptionList中根本就沒有節點可以認領這次異常,所以就直接通過ZwContinue()返回到剛才擷取的那個上下文中,那就是 RtlRaiseException()本次被調用的地方。
在我們現在所考察的情景中,RtlRaiseException()是在KiUserExceptionDispatcher()中受到調用的,並且在此 之前已經有過對ZwRaiseException()或ZwContinue()的調用。那麼現在又來調用ZwRaiseException()或 ZwContinue()是否重複呢?不重複,因為使用的是不同的異常紀錄塊,不同的異常代碼,這是為不同原因而發起的軟異常。
還有,為什麼在受調試的情況下反倒要直接調用RtlDispatchException(),避開調試處理呢?因為調試工具所安排的都是針對正常條件下的 異常,例如頁面訪問異常、除數為0等等,而現在發生的是在處理異常的過程中本來不應該發生的問題,是屬於系統的問題,那不是屬於應該由調試工具來處理的問 題。而如果直接通過ZwRaiseException()發起一次非強制中斷,則必將首先進入偵錯工具。
如果這一次對ZwRaiseException()或ZwContinue()的調用又失敗了(居然返回了),那麼問題就又更嚴重了,所以此時再以 ZwRaiseException()或ZwContinue()返回的出錯代碼為參數調用RtlRaiseStatus()。
[KiUserExceptionDispatcher() > RtlRaiseException() > RtlRaiseStatus()]
VOID NTAPI RtlRaiseStatus(NTSTATUS Status)
{
EXCEPTION_RECORD ExceptionRecord;
CONTEXT Context;
DPRINT1("RtlRaiseStatus(Status 0x%.08lx)/n", Status);
/* Capture the context */
RtlCaptureContext(&Context);
/* Add one argument to ESP */
Context.Esp += sizeof(PVOID);
/* Create an exception record */
ExceptionRecord.ExceptionAddress = RtlpGetExceptionAddress();
ExceptionRecord.ExceptionCode = Status;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.NumberParameters = 0;
ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
/* Write the context flag */
Context.ContextFlags = CONTEXT_FULL;
/* Check if we're being debugged (user-mode only) */
if (!RtlpCheckForActiveDebugger(TRUE))
{
/* Raise an exception immediately */
ZwRaiseException(&ExceptionRecord, &Context, TRUE);
}
else
{
/* Dispatch the exception */
RtlDispatchException(&ExceptionRecord, &Context);
/* Raise exception if we got here */
Status = ZwRaiseException(&ExceptionRecord, &Context, FALSE);
}
/* If we returned, raise a status */
RtlRaiseStatus(Status);
}
這個函數的代碼與RtlRaiseException()其實很相似,只是新的異常紀錄塊建立在本函數架構內部,並且排除了對ZwContinue()的 調用。特別值得注意的是,這個函數有可能遞迴調用其自身,如果對ZwRaiseException()又返回了,就又要調用RtlRaiseStatus ()。顯然,這裡的希望寄託在每次對RtlRaiseStatus()的調用參數、即ZwRaiseException()的失敗原因不同,而總有一次得 到了妥善的處理,就不再從ZwRaiseException()返回了。注意這裡所謂“妥善的處理”也包括在異常處理的第三步中結束線程的運行或者停機。 如果ZwRaiseException()老是返回,那就說明連這也出了問題。
讀者也許因而對異常處理的結局產生一種黯淡的印象,怎麼老是失敗又失敗?但是須知這是在應付最壞的情況,實際的情況不會有這麼糟糕。因為如前所述每個線程 的運行都是在一個SEH域中進行,並且這個SEH域的過濾函數實際上是來者不拒。再說,只要沒有很特殊的情況,即使基於ExceptionList的處理 失敗,後面也還有第二步、第三步措施。在正常的條件下,對使用者空間異常的最後一手就是通過ZwTerminateThread()結束線程的運行,對系統 空間異常的最後一手則是停機。
如前所述,ZwRaiseException()就是系統調用NtRaiseException()。一般在使用者空間程式中使用 NtRaiseException(),而在核心中使用ZwRaiseException()。但是在使用者空間也可以用ZwRaiseException (),這就是為像RtlRaiseException()這樣“兩棲”的代碼而設的,在Ntdll.dll所匯出的函數表中, NtRaiseException()和ZwRaiseException()實際上指向同一段代碼。
這個系統調用的作用就是類比一次異常。在使用者空間調用就類比發生於使用者空間的異常,在系統空間調用就類比發生於系統空間的異常。讀者也許會問,我們前面在 KiUserExceptionDispatcher()中看到對RtlDispatchException()的調用,帶著一個新的異常紀錄塊掃描 ExceptionList,這不也是在類比一次異常碼?這裡面的區別還是不小的。掃描ExceptionList只是異常處理的幾個步驟之一,整個異常 處理的過程可以包含三次努力。另一方面,無論是真實發生的硬異常、還是由這個系統調用類比的軟異常,都會在系統空間堆棧上形成一個異常架構(由系統調用形 成的陷阱架構與此相同)。而直接調用RtlDispatchException()則並不形成新的異常架構。
系統調用NtRaiseException()的調用介面如下:
NTSYSCALLAPI NTSTATUS NTAPI
NtRaiseException(IN PEXCEPTION_RECORD ExceptionRecord,
IN PCONTEXT Context, IN BOOLEAN SearchFrames);
前兩個參數分別是指向異常紀錄塊和上下文結構的指標。第三個參數表示是否需要搜尋ExceptionList,其實就是以前見到過的FirstChance,如果為FALSE就表示處理該次異常的第一次努力已經失敗。
異常紀錄塊中的欄位ExceptionCode說明本次異常的性質、即種類。這是一個32位無符號異常代碼,其意義有如中斷向量,例如 STATUS_INTEGER_DIVIDE_BY_ZERO、STATUS_SINGLE_STEP等等都是。但是,由於不受硬體限制,異常代碼的取值 範圍比中斷向量要廣泛得多,就其本質而言這隻是異常的發起者與處理者(過濾函數)之間的約定。所以,除微軟已經定義的異常代碼、即狀態碼之外,應用軟體 的開發人員也可以按需要自行定義新的代碼。微軟為此定下了規則:
l Bit30-31表示嚴重性: 0 = 成功, 1 = 提示, 2 = 警告,3 = 出錯。
l Bit29 表示定義/使用者:0 = 由微軟定義, 1 = 非微軟定義。
l Bit28 保留不用,必須為0。
l Bit16-27表示類型。
l Bit0-15 為異常編號、即產生異常的具體原因。
例如,STATUS_NETWORK_SESSION_EXPIRED定義為0xC000035C,那就是:嚴重性為“出錯”,由微軟定義,類型代碼為3,具體原因的代碼為0x5C。
所以,在異常紀錄塊中,異常代碼錶示異常的性質和原因。而ExceptionFlags,則以標誌位的形式給出有關的狀態資訊。當然,對於不同編碼的異常,對這些標誌位可以作不同的解釋。
至於上下文Context,則是為NtContinue()準備的。
不管是NtRaiseException()還是ZwRaiseException(),核心中實現這個系統調用的函數總是NtRaiseException()。在0.3.0版ReactOS的代碼中,這是一段組譯工具:
[NtRaiseException()]
_NtRaiseException@12:
/* NOTE: We -must- be called by Zw* to have the right frame! */
/* Push the stack frame */
push ebp
/* Get the current thread and restore its trap frame */
mov ebx, [fs:KPCR_CURRENT_THREAD]
mov edx, [ebp+KTRAP_FRAME_EDX]
mov [ebx+KTHREAD_TRAP_FRAME], edx
/* Set up stack frame */
mov ebp, esp
/* Get the Trap Frame in EBX */
mov ebx, [ebp+0]
/* Get the exception list and restore */
mov eax, [ebx+KTRAP_FRAME_EXCEPTION_LIST]
mov [fs:KPCR_EXCEPTION_LIST], eax
/* Get the parameters */
mov edx, [ebp+16] /* Search frames */
mov ecx, [ebp+12] /* Context */
mov eax, [ebp+8] /* Exception Record */
/* Raise the exception */
push edx
push ebx
push 0
push ecx
push eax
call _KiRaiseException@20
/* Restore trap frame in EBP */
pop ebp
mov esp, ebp
/* Check the result */
or eax, eax
jz _KiServiceExit2
/* Restore debug registers too */
jmp _KiServiceExit
在進入NtRaiseException()之前,在系統空間堆棧上已經因為系統調用而形成了一個陷阱架構,“架構指標”EBP就指向這個架構。這裡先通 過KPCR結構擷取指向當前線程KTHREAD資料結構的指標, 然後使這KTHREAD結構中的指標TrapFrame指向當前的異常架構。這樣,以後通過ExGetPreviousMode()就能擷取正確的“先前 模式”。
但是這裡有個問題值得一說。常數KPCR_CURRENT_THREAD定義為0x124,而KPCR結構的大小隻是0x54,所以指向當前線程 KTHREAD結構的指標顯然不在KPCR中。原來,除KPCR以外,每個處理器還有個KPRCB資料結構(Processor Region Control Block)。KPCR結構中有個指標Prcb,指向與其配對的KPRCB資料結構。可是,KPRCB與KPCR兩個資料結構起點之間的距離卻是固定的, 那就是0x120。而KPRCB結構中的第二個長字,就是指標CurrentThread。所以,KPCR的位置一經確定,相應KPRCB的位置也就定 了。既然如此,為什麼不乾脆把兩個資料結構合二為一呢?這就不得而知了,也許是曆史遺留的問題。
實際的處理是由KiRaiseException()完成的。處理完以後,如果返回的話,就根據傳回值是否為STATUS_SUCCESS、即0而分別經由_KiServiceExit2或_KiServiceExit從異常返回。
[NtRaiseException() > KiRaiseException()]
NTSTATUS NTAPI
KiRaiseException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context,
PKEXCEPTION_FRAME ExceptionFrame,
PKTRAP_FRAME TrapFrame, BOOLEAN SearchFrames)
{
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
CONTEXT LocalContext;
EXCEPTION_RECORD LocalExceptionRecord;
ULONG ParameterCount, Size;
NTSTATUS Status = STATUS_SUCCESS;
DPRINT1("KiRaiseException/n");
/* Set up SEH */
_SEH_TRY
{
/* Check the previous mode */
if (PreviousMode != KernelMode)
{
/* Validate the maximum parameters */
if ((ParameterCount = ExceptionRecord->NumberParameters) >
EXCEPTION_MAXIMUM_PARAMETERS)
{
/* Too large */
Status = STATUS_INVALID_PARAMETER;
_SEH_LEAVE;
}
/* Probe the entire parameters now*/
Size = (sizeof(EXCEPTION_RECORD) -
((EXCEPTION_MAXIMUM_PARAMETERS - ParameterCount) * sizeof(ULONG)));
ProbeForRead(ExceptionRecord, Size, sizeof(ULONG));
/* Now make copies in the stack */
RtlMoveMemory(&LocalContext, Context, sizeof(CONTEXT));
RtlMoveMemory(&LocalExceptionRecord, ExceptionRecord, Size);
Context = &LocalContext;
ExceptionRecord = &LocalExceptionRecord;
/* Update the parameter count */
ExceptionRecord->NumberParameters = ParameterCount;
}
}
_SEH_HANDLE
{
Status = _SEH_GetExceptionCode();
}
_SEH_END;
if (NT_SUCCESS(Status))
{
/* Convert the context record */
KeContextToTrapFrame(Context,
ExceptionFrame,
TrapFrame,
Context->ContextFlags,
PreviousMode);
/* Dispatch the exception */
KiDispatchException(ExceptionRecord,
ExceptionFrame,
TrapFrame,
PreviousMode,
SearchFrames);
}
/* Return the status */
return Status;
}
參數ExceptionFrame是個指向KEXCEPTION_FRAME結構的指標,僅用於PowerPC處理器,所以從上面傳下來的實參是0;另一個參數TrapFrame則是指向堆棧上陷阱架構的指標。其餘參數不言自明。
如果本次系統調用來自使用者空間,那麼作為參數的異常紀錄塊和上下文資料結構都在使用者空間,所以需要先通過RtlMoveMemory()把它們複製到系統空間的臨時緩衝區裡來。然而這恰恰又是可能引起異常的,所以又要有個SEH域將其保護起來。
異常紀錄塊和上下文資料結構都存在於系統空間以後,就可以調用KiDispatchException()了,參數SearchFrames變成了KiDispatchException()的參數FirstChance。
KiDispatchException()的代碼當然不用再看了。總之,就是前述的三步措施、三次努力,主要就是搜尋ExceptionList。但是 搜尋的是哪一個ExceptionList,就取決於“先前模式”,即調用ZwRaiseException()之時所處的空間:
l 如果“先前模式”是系統模式,那主要就是搜尋系統空間的ExceptionList,在正常的情況下會作長程跳轉而不再返回,因此自然也就不會從本次系統調用返回了。如果返回,那就是返回到調用ZwRaiseException()的地方。
l 如 果“先前模式”是使用者模式,那主要就是修改堆棧上異常架構中的返回地址,然後返回,使得一旦返回到使用者空間就進入 KiUserExceptionDispatcher(),並在那裡掃描使用者空間的ExceptionList。在正常的情況下也會因長程跳轉而不再返回 到調用ZwRaiseException()的地方。或者,倘若不作長程跳轉而要回到調用ZwRaiseException()的地方,則可以通過系統調 用ZwContinue()實現。
從KiRaiseException()返回後,NtRaiseException()根據傳回值是否為0而分別經由_KiServiceExit2或_KiServiceExit:
_KiServiceExit:
/* Disable interrupts */
cli
/* Check for, and deliver, User-Mode APCs if needed */
CHECK_FOR_APC_DELIVER 1
. . . . . .
/* Exit and cleanup */
TRAP_EPILOG FromSystemCall, DoRestorePreviousMode, /
DoNotRestoreSegments, DoNotRestoreVolatiles, DoRestoreEverything
_KiServiceExit2:
/* Disable interrupts */
cli
/* Check for, and deliver, User-Mode APCs if needed */
CHECK_FOR_APC_DELIVER 0
/* Exit and cleanup */
TRAP_EPILOG NotFromSystemCall, DoRestorePreviousMode, /
DoRestoreSegments, DoRestoreVolatiles, DoNotRestoreEverything
由於是在.S彙編代碼檔案中,定義和引用宏操作的格式有所不同。這裡的FromSystemCall和NotFromSystemCall都是常數,前者定義為1,後者定義為0,餘類推。
而TRAP_EPILOG,則顯然是與TRAP_PROLOG相對應,類似於中斷返回,具體的代碼就留給讀者自己去看了。