漫談相容核心之二十五:Windows的結構化異常處理(二)

來源:互聯網
上載者:User

 先看調用參數。異常紀錄塊ExceptionRecord就是前面準備好的;指標ExceptionFrame是NULL,這是個 KEXCEPTION_FRAME結構指標,但是從代碼中看這隻是為PowerPC晶片而設的,用於儲存一些附加寄存器的內容,386架構的處理器晶片無 此要求;而陷阱架構指標Tf指向堆棧上因異常而形成的架構,我們在前面倒是稱之為“異常架構”。此外,PreviousMode為KernelMode; 而FirstChance為TRUE,表示即將進行的是第一次努力。
    這個函數的代碼中有很大一部分是用於使用者空間異常的,因為KiUserTrapHandler()最後也是調用這個函數。但是我們現在要集中關注系統空間異常,所以把那些代碼刪去了。
    除前面準備好的異常紀錄塊外,對於異常的處理還需要用到陷阱架構中的許多資訊,以及別的資訊、例如有關浮點處理器的現場資訊,所以在進入實質性的異常處理 前要通過KeTrapFrameToContext()將這些資訊整理、收集到一個上下文資料結構中。完成了處理之後,如果要從 KiDispatchException()返回的話,則反過來通過KeContextToTrapFrame()更新有關的原始資訊,因為在異常處理的 過程中可能會有所改變。
    有些異常可能發生在程式調試的過程中,如果是這樣就要由偵錯工具先進行處理,有的是由偵錯工具直接採取措施,有的是由調試人員決定採取什麼措施。例如,由 於程式斷點所引起的異常(其實是自陷),就只能由偵錯工具和調試人員採取措施。所以,只要是處於Debug狀態,異常處理的第一步就是交由偵錯工具 (debugger)處理。當然,對核心的調試不像對應用軟體那麼簡單,但是道理是一樣的。首先,所用的核心映像必須是可調試的版本,在編譯、串連時就加 上了調試可選項。另一方面,還得有個調試工具,那一般就是kd、即“核心調試器(Kernel Debugger)”。這兩個條件是缺一不可的。
    通過KdpEnterDebuggerException()把本次異常提交偵錯工具處理的結果有兩種:
l 核心並不處在被調試的狀態,或偵錯工具(以及調試人員)不能解決問題,本次異常需要由SEH機制加以處理,此時返回常數kdHandleException。
l 偵錯工具已經解決了問題,可以繼續運行、而不需要SEH處理的介入,此時返回常數kdContinue。
    如果返回的是kdContinue,就跳轉到程式標籤Handled下面。此時資料結構Context中的內容可能已有改變,所以通過 KeContextToTrapFrame()對異常架構作相應修改,然後返回,本次異常就這樣對付過去了。對於Context結構內容的改變,不妨設想 一下,調試人員可能會選擇讓程式的執行跳轉到另一個點上,或者修改某一個寄存器的內容,這時候就要修改上下文中的返回地址或寄存器映像。
    從總體上說,對發生於系統空間的異常,核心分三步採取措施:
    1. 第一步、即“FirstChance”,是先把問題提交給偵錯工具,如不能解決(返回的不是kdContinue)就調用 RtlDispatchException()進行實質性的SEH處理。在實際運行中,處於調試狀態的時候總是少數,所以在絕大部分的情況下第一步的核心 就是SEH的處理。SEH機制對異常的處理有三種可能的結果:
l 如果被某個SEH架構所認領,並實施長程跳轉,程式就不返回了,而此刻所在的函數調用架構也隨同別的一些架構一起被跨越和廢棄。
l 被某個SEH架構所認領,但是認為可以不作長程跳轉而繼續運行(例如只需要執行一下善後函數就行)。這樣,程式就會從RtlDispatchException()返回,並且返回的值是TRUE。此時問題已經解決,所以也是通過程式標籤Handled下面的代碼返回。
l 所有的SEH架構都拒絕認領,這意味著處理本次異常的第一步努力已經失敗。
    2. 要是第一步失敗,就進行第二步,再次通過KdpEnterDebuggerException()把問題提交給偵錯工具。注意此時的最後兩個調用參 數都變成了FALSE,而第一次時都是TRUE。這兩個參數,一個是FirstChance,其意義自明;第二個是Gdb,表示需要取得別的調試支援。如 果這一次取得了成功、問題解決了,那麼傳回值是kdContinue。否則就要採取第三步措施了。
3. 第三步,實際上此時已經無計可施,宏操作KEBUGCHECKWITHTF()的作用是顯示出錯資訊並將出錯的現場“轉儲(Dump)”到檔案中以便事後分析,然後就使CPU進入停機狀態。
    對於發生於系統空間的異常,這三步措施、或者說三次努力,都是在KiDispatchException()內部完成的,如果調用參數FirstChance為1就一氣呵成,但是在調用這個函數時也可以使FirstChance為0而跳過第一步。
    而對於發生於使用者空間的異常,則對於ExceptionList的掃描處理是在使用者空間進行的,並且在使用者空間也有一個類似於KiDispatchException();而有的措施卻又需要通過系統空間才能進行,所以不一定能在同一個函數中一氣呵成。
    顯然,SEH處理的核心就是對ExceptionList的掃描處理,這是由RtlDispatchException()完成的,事實上絕大多數的異常都可以通過這個函數得到妥善的處理。
    下面就是由RtlDispatchException()實現的實質性的SEH處理了。這種處理在有些文獻中稱為“基於架構(Frame-Based)”的異常處理,其基礎當然就是ExceptionList。

[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlDispatchException()]

BOOLEAN
NTAPI
RtlDispatchException(IN PEXCEPTION_RECORD ExceptionRecord,
                     IN PCONTEXT Context)
{
PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, NestedFrame = NULL;
. . . . . .

/* Get the current stack limits and registration frame */
RtlpGetStackLimits(&StackLow, &StackHigh);
RegistrationFrame = RtlpGetExceptionList();
DPRINT("RegistrationFrame is 0x%p/n", RegistrationFrame);

/* Now loop every frame */
while (RegistrationFrame != EXCEPTION_CHAIN_END)
{
    /* Find out where it ends */
    RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame + sizeof(*RegistrationFrame);

    /* Make sure the registration frame is located within the stack */
    if ((RegistrationFrameEnd > StackHigh) ||
            ((ULONG_PTR)RegistrationFrame < StackLow) ||
            ((ULONG_PTR)RegistrationFrame & 0x3))
    {
       . . . . . .
       continue;
       . . . . . .
    }

    . . . . . .

    /* Call the handler */
    DPRINT("Executing handler: %p/n", RegistrationFrame->Handler);
    ReturnValue = RtlpExecuteHandlerForException(ExceptionRecord,
                                     RegistrationFrame, Context, &DispatcherContext,
                                     RegistrationFrame->Handler);
    DPRINT("Handler returned: %p/n", (PVOID)ReturnValue);

    /* Check if this is a nested frame */
    if (RegistrationFrame == NestedFrame)
    {
            /* Mask out the flag and the nested frame */
            ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL;
            NestedFrame = NULL;
    }

    /* Handle the dispositions */
    if (ReturnValue == ExceptionContinueExecution)
    {
            /* Check if it was non-continuable */
            if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
            {
                /* Set up the exception record */
                ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                ExceptionRecord2.ExceptionCode =
                                STATUS_NONCONTINUABLE_EXCEPTION;
                ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                ExceptionRecord2.NumberParameters = 0;

                /* Raise the exception */
                DPRINT("Non-continuable/n");
                RtlRaiseException(&ExceptionRecord2);
            }
            else
            {
                /* Return to caller */
                return TRUE;
            }
    }
    else if (ReturnValue == ExceptionNestedException)
    {
            /* Turn the nested flag on */
            ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;

            /* Update the current nested frame */
            if (NestedFrame < DispatcherContext) NestedFrame = DispatcherContext;
    }
    else if (ReturnValue == ExceptionContinueSearch)
    {
            /* Do nothing */
    }
    else
    {
            /* Set up the exception record */
            ExceptionRecord2.ExceptionRecord = ExceptionRecord;
            ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord2.NumberParameters = 0;

            /* Raise the exception */
            RtlRaiseException(&ExceptionRecord2);
    }

    /* Go to the next frame */
    RegistrationFrame = RegistrationFrame->Next;
}

/* Unhandled, return false */
DPRINT("FALSE/n");
return FALSE;
}

    SEH的基礎就是異常處理鏈表,正是這個隊列中的節點及其內容使長程跳轉成為可能。所以這裡一開始就通過RtlpGetExceptionList()找 到異常處理鏈表,這當然就是當前CPU的KPCR結構中的指標ExceptionList。這個指標指向鏈表中的第一個節點,其資料結構是 EXCEPTION_REGISTRATION_RECORD。這種資料結構的定義如下:

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
    struct _EXCEPTION_REGISTRATION_RECORD *Next;
    PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

    這和上一篇漫談中所說的_SEHRegistration_t結構實質上是同一回事,只是結構和成分的名稱不同。這裡的Handler是個函數指標,其類 型為PEXCEPTION_ROUTINE,也跟_SEHRegistration_t結構中的_SEHFrameHandler_t相同,因而這裡見到 的函數指標handler就是前面設定的函數指標SER_Handler。之所以如此,筆者猜想,是因為有關的代碼重用於使用者空間的異常處理,因為曆史的 原因而使用了不同的名稱。注意_SEHRegistration_t資料結構是另一種資料結構_SEHPortableFrame_t內部的第一個成分, 所以獲得了一個_SEHRegistration_t結構指標,也就獲得了指向其所在_SEHPortableFrame_t結構的指標。
    上一篇漫談中還曾講到,異常處理鏈表中的每一個資料結構都在堆棧上,都是相應函數調用架構中的局部量。所以這裡還通過 RtlpGetStackLimits()擷取當前線程的(系統空間)堆棧位置、即其StackLow和StackHigh兩個端點;下面在處理隊列中的 資料結構時先加以比對,以確認其位置上的合理性。不過也有個例外,就是倘若異常發生於DPC處理(Windows的DPC相當於Linux的bh、或“軟 中斷”)的過程中,那麼由於DPC處理時使用的是一個獨立的堆棧,所以就要對StackLow和StackHigh作出相應的調整,然後重新加以比對。當 然,如果比對的結果確實不符,那就無法繼續下去了。
    然後就通過一個while迴圈來依次搜尋處理ExceptionList鏈表中的每一個節點,由 RtlpExecuteHandlerForException()加以嘗試。鏈表中的每個節點都代表著一個局部SHE架構棧。前一篇漫談中講過,局部 SHE架構棧就是一組不僅實質上嵌套、而且(代碼)形式上也嵌套的SEH域所形成的架構。但是,事實上在現有的ReactOS代碼中還沒有見到使用形式嵌 套的SEH域,所以ExceptionList中的每個節點實質上都只代表著單個的SEH域。因此,為敘述上的方便,下面只要不至於引起誤解或與代碼沖 突,就假定ExceptionList中的每個節點都只代表著一個SEH架構(而不是棧)。
    由於異常處理鏈表是個後進先出的隊列,裡面的第一個節點(資料結構)代表著最近進入的SEH架構。如果鏈表中有不止一個的節點,就說明有了SEH架構嵌 套。在嵌套的情況下,隊列中的第一個節點代表著最底層的那個保護域,如果這個節點(執行過濾函數以後)拒絕認領本次異常,就說明並非這個SEH域所針對的 異常,那就應該往上跑一層,看是否為上一層SEH域所針對的異常。所以順著異常處理鏈表考察下一個節點就是在逐層往上跑,直至有某個節點認領本次異常為 止。一個節點認領了實際發生的異常以後,一般就會執行預先規定的長程跳轉,直接就跑到了那個架構中的if語句裡面,而眼下這個函數的調用架構也就因為長程 跳轉而被丟棄了。不過,後面讀者將看到,在長程跳轉之前還有個“展開(Unwinding)”的過程,那就是調用所有被跨越SEH架構的善後函數。
    所以,在正常條件下這個while迴圈註定是短命的。只有在隊列中的每個SHE架構都拒絕認領所發生的異常、或者在處理中出錯的情況下,這個while迴圈才可能以窮盡整個隊列而告終。
    如果RtlpExecuteHandlerForException()返回,那麼其傳回值有這麼幾種可能:

#define ExceptionContinueExecution 0
#define ExceptionContinueSearch   1
#define ExceptionNestedException 2
#define ExceptionCollidedUnwind   3
    這個傳回值實際上是對下一步應該如何進行的指示。所以此後的程式大體上相當於一個以此為條件的case語句。
l ExceptionContinueExecution 表示認領了、但是不作長程跳轉。這說明或者是問題已經解決,或者是可以忽略本次異常的發生,總之是可以繼續執行原來的程式了。但是這裡還有個條件,就是被 異常所中斷的程式還能夠繼續執行才行。這時候就要看異常紀錄塊ExceptionRecord中的EXCEPTION_NONCONTINUABLE標誌 位。如果為1就表示無法繼續執行,所以通過RtlRaiseException()引起一次類型為 STATUS_NONCONTINUABLE_EXCEPTION的“軟異常”。而若可以繼續,則直接結束迴圈而返回,並最終從本次異常返回(此時本次異 常的架構還在堆棧上)。注意此時函數返回的是TRUE,表示問題已經解決,而若返回FALSE則表示失敗。
l ExceptionContinueSearch 表示不予認領,應該繼續考察隊列裡面的下一個節點、即上升到更高一層的SEH架構。這是之所以需要ExceptionList、以及這裡的while迴圈 的原因。此時在本輪迴圈中不需要再做什麼,前進到ExceptionList隊列中的下一個節點就是了。
l ExceptionNestedException 則表示RtlpExecuteHandlerForException()發現本次異常是一次嵌套異常,即發生於異常處理過程中的新的異常。下面讀者將看 到,為了捕捉嵌套異常,在每考察/處理一個ExceptionList中的某個節點時先要在ExceptionList鏈表的頭部插入一個臨時的“保護節 點”,處理完目標節點後再將保護節點刪去。這樣,當嵌套異常發生時,原來對ExceptionList的掃描/處理被中斷、而先要處理這新的異常,並因此 而再次進入這裡的while迴圈。顯然此時最先受到考察的是那個臨時的保護節點,而ExceptionNestedException就是在這個時候返回 的,同時還通過參數DispatcherContext返回一個指標,指向該臨時節點所保護的目標節點、即原來正在處理中的那個節點。在這樣的情況下,代 碼中使局部量NestedFrame指向所保護的目標節點,並使異常紀錄塊中的EXCEPTION_NESTED_CALL標誌位設成1,然後就繼續在 ExceptionList中往前搜尋。一直到過了所保護的節點以後,才把這標誌位以及NestedFrame清0。這樣,只要異常紀錄塊中的這個標誌位 為1,就表示目前處於在處理過程中發生了嵌套異常的那個SEH架構內部。注意對嵌套異常的處理可能在ExceptionList中跑得更遠,也即屬於更高 層的SEH架構。由於嵌套的深度有可能大於1,實際的代碼比之上述還要更複雜一些。
l 如 果除此之外出現了別的傳回值,包括ExceptionCollidedUnwind,那就是發生了嚴重的錯誤。就其本質而言,這樣的錯誤也相當於異常, ExceptionList中理應也有相應的節點為此作好了準備,只是CPU的硬體不會因此而發生硬異常。所以,就通過 RtlRaiseException()引起一次類型為STATUS_INVALID_DISPOSITION的軟異常。這當然是一次嵌套異常,因為原來 的異常架構還在。
    關於通過RtlRaiseException()引起軟異常的問題,下一篇漫談中還要詳細介紹,這裡先簡單提一下。所謂軟異常,就是以函數調用的手段來模 擬一次異常。在典型的情況下,每個SEH域都有一個過濾函數,過濾函數檢查異常紀錄塊的類型以確定是否應該認領和處理本次異常。以上列的第一種情況為例, 假定原來是訪問記憶體失敗而引起的14號異常,類型是STATUS_ACCESS_VIOLATION,這已經得到了處理,結論是繼續執行;然而狀態標誌位 又表明無法繼續。這樣,問題已不再是原來訪問記憶體失敗的問題,而變成了類型為STATUS_NONCONTINUABLE_EXCEPTION的另一個問 題,這就可能需要由針對此種異常的另一個SEH域來處理了,因而以此類型類比一次異常,使SEH機制再次搜尋ExceptionList。這就是發起軟異 常的意圖。
    最後,如果while()迴圈結束,那就說明異常處理鏈表中所有的節點都不是為本類異常準備的,也即事先並沒有估計到本類異常的發生、也沒有為此作出安排,所以就返回FALSE,讓上一層KiDispatchException()採取其第二步措施。
    顯然,關鍵在於RtlpExecuteHandlerForException(),就是對ExceptionList中具體節點的考察和處理。先看其調用介面:

EXCEPTION_DISPOSITION
RtlpExecuteHandlerForException(PEXCEPTION_RECORD ExceptionRecord,
            PEXCEPTION_REGISTRATION RegistrationFrame, PCONTEXT Context,
            PVOID DispatcherContext, PEXCEPTION_HANDLER ExceptionHandler);

    其中第一個參數指向異常紀錄塊,這是關於本次(實際發生的)異常的資訊;第二個參數指向ExceptionList隊列中的一個節點,這是關於當前所考察 SEH域的資訊;第三個參數指向本次異常發生時的上下文資料結構。第四個參數DispatcherContext是個指標,僅對用於嵌套異常的臨時保護節 點有效。最後一個參數是函數指標,指向由當前節點提供的架構處理函數。對於普通的節點這就是_SEHFrameHandler(),是在 _SEHEnterFrame_f()中設定好的。
    再看具體的實現,這是一段彙編代碼。

[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlpDispatchException() > RtlpExecuteHandlerForException()]

_RtlpExecuteHandlerForException@2 0:
    /* Copy the routine in EDX */
    mov edx, offset _RtlpExceptionProtector
    /* Jump to common routine */
    jmp _RtlpExecuteHandler@20

    首先讓寄存器EDX指向一個函數RtlpExceptionProtector(),其作用下面就會看到。然後跳轉到標籤 _RtlpExecuteHandler下面。事實上,_RtlpExecuteHandler下面這一段代碼是由 RtlpExecuteHandlerForException()與另一個函數RtlpExecuteHandlerForUnwind()共用的,所 不同的只是置入EDX的函數指標。

_RtlpExecuteHandlerForUnwind@20:
    /* Copy the routine in EDX */
    mov edx, offset _RtlpExceptionProtector
    /* Run the common routine */
_RtlpExecuteHandler@20:
    /* Save non-volatile */
    push ebx
    push esi
    push edi
    /* Clear registers */
    xor eax, eax
    xor ebx, ebx
    xor esi, esi
    xor edi, edi

    /* Call the 2nd-stage executer */
    push [esp+0x20]
    push [esp+0x20]
    push [esp+0x20]
    push [esp+0x20]
    push [esp+0x20]
    call _RtlpExecuteHandler2@20

    /* Restore non-volatile */
    pop edi
    pop esi
    pop ebx
    ret 0x14

    按理說,在RtlpExecuteHandlerForUnwind()裡面置入EDX的指標應該指向RtlpUnwindProtector()、而不 是像在RtlpExecuteHandlerForException()裡面那樣指向RtlpExceptionProtector()。所以這很可能 是個錯誤。畢竟0.3.0版的ReactOS還很新,裡面有些錯誤也不奇怪。而在0.2.6版的代碼中則確實指向RtlpUnwindProtector (),那應該是正確的。
    顯然,具體的處理是由_RtlpExecuteHandler2()實現的,這裡只是為這個函數的調用進行事先的準備和事後的恢複。

[_KiTrap14() > KiPageFaultHandler() > KiKernelTrapHandler() > KiDispatchException()
> RtlDispatchException() > RtlpExecuteHandlerForException() > _RtlpExecuteHandler2()]

_RtlpExecuteHandler2@20:
    /* Set up stack frame */
    push ebp
    mov ebp, esp
    /* Save the Frame */
    push [ebp+0xC]       /* 指向原節點、這是要保護的目標節點 */
    /* Push handler address */
    push edx        /* 成為新節點中的Handler指標,指向保護函數 */

    /* Push the exception list */
    push [fs:TEB_EXCEPTION_LIST]   /* 成為新節點中的Next指標 */
    /* Link us to it */
    mov [fs:TEB_EXCEPTION_LIST], esp /* 讓ExceptionList指向新節點 */

    /* Call the handler */
    push [ebp+0x14]
    push [ebp+0x10]
    push [ebp+0xC]
    push [ebp+8]
    mov ecx, [ebp+0x18]      /* 參數ExceptionHandler */
    call ecx          /* 調用ExceptionHandler,4個調用參數 */

    /* Unlink us */
    mov esp, [fs:TEB_EXCEPTION_LIST]
    /* Restore it */
    pop [fs:TEB_EXCEPTION_LIST]    /* 新節點已從ExceptionList中摘除 */

    /* Undo stack frame and return */
    mov esp, ebp        /* 新節點不複存在於堆棧上 */
    pop ebp
    ret 0x14

    這裡的常數TEB_EXCEPTION_LIST定義為0,所以“fs:TEB_EXCEPTION_LIST”就是“fs:0”,即指向KPCR結構中 的第一個欄位,即ExceptionList。但是在這裡引用常數TEB_EXCEPTION_LIST有些誤導,因為現在這是在系統空間、而不是在使用者 空間。究其原因,則是這個函數同樣也用於使用者空間的異常處理(代碼重用),而TEB的第一個欄位確實同樣也是ExceptionList。
    注意這裡的call指令所引用的是ECX、而不是EDX。ECX的內容來自堆棧上的調用參數,就是前面的最後一個參數ExceptionHandler。對於普通的節點(下面就要講到不普通的節點),這實際上是_SEHFrameHandler()。
    這段代碼有點奧妙。注意這裡先把作為參數的指標RegistrationFrame壓入堆棧,再把寄存器EDX的內容、即指向 _RtlpExceptionProtector()或_RtlpUnwindProtector()的函數指標壓入堆棧,然後又把[fs:0]、即指標 ExceptionList的內容壓入堆棧,再把當前的堆棧指標寫入ExceptionList。這樣一來,ExceptionList所指處的內容是一 個_SEHRegistration_t結構指標,這個指標的上方是一個函數指標(再上方是指向目標節點的指標)。我們可以把這兩個指標看成一個 _SEHPortableFrame_t資料結構,而這裡對堆棧和[fs:0]的操作,則實際上是把又一個節點插入了異常處理隊列的頭部(邏輯上則是尾 部)。但是,與這個隊列中原有的節點相比,這個新的節點又有所不同。原來的節點雖然同為_SEHRegistration_t資料結構,卻都是 _SEHPortableFrame_t資料結構中的一個成分;而現在掛入隊列的節點卻是孤立地存在(外加一個指標)。但是這並不成為問題,因為現在這個 函數指標所指向的函數也不一樣。畢竟,是這個函數決定了怎樣去訪問和使用有關的資料結構,只要二者配套就行。為區別於目標節點中的架構處理函數,我們稱現 在這個函數為“保護函數(protector)”。與此相應,我們不妨稱這樣的節點為“保護節點”,而稱原來的節點為“普通節點”。
    保護節點的存在是暫時的,從保護函數返回時下面的兩條指令就把這個新的節點刪除了,於是ExceptionList又恢複了原狀。由於節點存在於堆棧上,有關的隊列操作就很乾淨利索。後面讀者就會看到,保護節點不會執行長程跳轉,所以一定會返回。
    那麼為什麼要在ExceptionList中插入一個保護節點呢?這是因為,下面就要對一個普通節點執行_SEHFrameHandler()了,執行這 個函數的過程本身(例如對過濾函數的調用)有可能會引起新的異常,所以也應該把它保護起來、為可能發生的異常作好準備。而相應的保護函數,如果異常果真發 生的話,則是_RtlpExceptionProtector()。其實這個函數並不真的起什麼保護作用,其目的只是用來表明發生了嵌套異常。
 在異常處理的過程中又發生新的異常,這就是嵌套異常。但是讀者要注意嵌套異常和嵌套SEH域的區別,不要混淆。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.