Windows SEH學習 x86


      windows 提供的異常處理機制實際上只是一個簡單的框架。我們通常所用的異常處理(比如 C++ 的 throw、try、catch)都是編譯器在系統提供的異常處理機制上進行加工了的增強版本。這里先拋開增強版的不提,先說原始版本。
     原始版本的機制很簡單:誰都可以觸發異常,誰都可以處理異常(只要它能看得見)。但是不管是觸發還是處理都得先注冊。系統把這些注冊信息保存在一個鏈表里,並且這個鏈表保存在線程的數據結構里。也就是說,異常所涉及的一些行為都是線程相關的。比如,線程 T1 觸發的異常就只能由線程 T1 來處理,其他線程根本就不知道 T1 發生了什么事,更不會處理。等注冊完畢后,線程就可以拋出或處理異常了,系統也可以做相應的管理工作了。
系統提供的管理工作簡單來說包括(但不限於):找到觸發異常的線程的異常處理鏈表(前面注冊的那個),然后按照規則對該異常進行分發,根據分發后的處理結果再進行下一步的分發或者結束處理。
     系統管理所使用的數據結構:

  #define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD * POINTER_32)-1)

    typedef enum _EXCEPTION_DISPOSITION {
        ExceptionContinueExecution,  
        ExceptionContinueSearch,
        ExceptionNestedException,
        ExceptionCollidedUnwind
    } EXCEPTION_DISPOSITION;

    typedef struct _EXCEPTION_RECORD {
        DWORD ExceptionCode;
        DWORD ExceptionFlags;
        struct _EXCEPTION_RECORD *ExceptionRecord; 
        PVOID ExceptionAddress;
        DWORD NumberParameters;
        ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
    } EXCEPTION_RECORD;

    typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;

    typedef
    EXCEPTION_DISPOSITION
    (*PEXCEPTION_ROUTINE) (
        IN struct _EXCEPTION_RECORD *ExceptionRecord,
        IN PVOID EstablisherFrame,
        IN OUT struct _CONTEXT *ContextRecord,
        IN OUT PVOID DispatcherContext
        );

    typedef struct _EXCEPTION_REGISTRATION_RECORD {
        //指向下一個 EXCEPTION_REGISTRATION_RECORD,由此構成一個異常注冊信息鏈表。
        //鏈表中的最后一個結點會將 Next 置為 EXCEPTION_CHAIN_END,表示鏈表到此結束。
        struct _EXCEPTION_REGISTRATION_RECORD *Next;
        PEXCEPTION_ROUTINE Handler;  //指向異常處理函數
    } EXCEPTION_REGISTRATION_RECORD;

    typedef EXCEPTION_REGISTRATION_RECORD *PEXCEPTION_REGISTRATION_RECORD;

       當接收到異常后,系統找到當前線程的異常鏈表,從鏈表中的第一個結點開始遍歷,找到一個 EXCEPTION_REGISTRATION_RECORD 就調用它的 Handler,並把該異常(類型為 EXCEPTION_RECORD 的參數)表示傳遞給該 Handler,Handler 處理並返回一個類型為 EXCEPTION_DISPOSITION 的枚舉值。該返回值指示系統下一步該做什么:
  ExceptionContinueExecution 表示:已經處理了異常,回到異常觸發點繼續執行。
  ExceptionContinueSearch 表示:沒有處理異常,繼續遍歷異常鏈表。
  ExceptionCollidedUnwind 表示在展開過程中再次觸發異常。

   ExceptionNestedException這里先不做解釋

  這樣系統根據不同的返回值來繼續遍歷異常鏈表或者回到觸發點繼續執行。

內核模式異常處理:

 首先,CPU 執行的指令觸發了異常,CPU 改執行 IDT 中 KiTrap??,KiTrap?? 會調用 KiDispatchException。

該函數原型如下:  功能如名字一樣,分發異常

VOID   KiDispatchException (
      IN PEXCEPTION_RECORD ExceptionRecord,
      IN PKEXCEPTION_FRAME ExceptionFrame,
      IN PKTRAP_FRAME TrapFrame,
      IN KPROCESSOR_MODE PreviousMode,
      IN BOOLEAN FirstChance );
View Code

Wrk中關於KiDispatchException的源代碼在后面貼出,基本流程就是:


檢查 ExceptionRecord->ExceptionCode,
如果是 STATUS_BREAKPOINT,那么將 CONTEXT::Eip 減一;
如果是 KI_EXCEPTION_ACCESS_VIOLATION,那么將檢查是否是由 AtlThunk 觸發(這個小環節沒有深究),
如果是觸發 NX(不可執行),那么將 ExceptionRecord->ExceptionInformation [0] 置為 0(貌似表示觸發操作的類型,0表示讀、1表示寫),MSDN有詳細解釋,推薦閱讀
如果 PreviousMode 是 KernelMode,
         那么如果 FirstChance 為 TRUE,那么將該異常傳達給內核調試器,如果內核調試器沒有處理,那么調用 RtlDispatchException 進行處理。
         如果 FirstChance 為 FALSE,那么再次將該異常傳達給內核調試器,如果內核調試器沒有處理,那么 BUGCHECK。
如果 PreviousMode 是 UserMode,
        那么,如果 FirstChance 為 TRUE,那么將該異常傳達給內核調試器,如果內核調試器沒有處理,那么將異常傳達給應用層調試器。
                     如果仍然沒有處理,那么將 KTRAP_FRAME 和 EXCEPTION_RECORD 拷貝到 UserMode 的棧中,並設置 KTRAP_FRAME::Eip 設置為                               ntdll!KiUserExceptionDispatcher,返回(將該異常交由應用層異常處理程序進行處理)。
         如果 FirstChance 為 FALSE,那么再次將異常傳達給應用層調試器,如果仍然沒有處理,那么調用 ZwTerminateProcess 結束進程,並 BUGCHECK。

VOID
KiDispatchException (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN PKEXCEPTION_FRAME ExceptionFrame,
    IN PKTRAP_FRAME TrapFrame,
    IN KPROCESSOR_MODE PreviousMode,
    IN BOOLEAN FirstChance
    )

/*++

Routine Description:

    This function is called to dispatch an exception to the proper mode and
    to cause the exception dispatcher to be called. If the previous mode is
    kernel, then the exception dispatcher is called directly to process the
    exception. Otherwise the exception record, exception frame, and trap
    frame contents are copied to the user mode stack. The contents of the
    exception frame and trap are then modified such that when control is
    returned, execution will commense in user mode in a routine which will
    call the exception dispatcher.

Arguments:

    ExceptionRecord - Supplies a pointer to an exception record.

    ExceptionFrame - Supplies a pointer to an exception frame. For NT386,
        this should be NULL.

    TrapFrame - Supplies a pointer to a trap frame.

    PreviousMode - Supplies the previous processor mode.

    FirstChance - Supplies a boolean value that specifies whether this is
        the first (TRUE) or second (FALSE) chance for the exception.

Return Value:

    None.

--*/

{

    CONTEXT ContextRecord;
    BOOLEAN DebugService;
    EXCEPTION_RECORD ExceptionRecord1;
    BOOLEAN ExceptionWasForwarded = FALSE;
    ULONG64 FaultingRsp;
    PMACHINE_FRAME MachineFrame;
    ULONG64 UserStack1;
    ULONG64 UserStack2;

    //
    // Move machine state from trap and exception frames to a context frame
    // and increment the number of exceptions dispatched.
    //

    KeGetCurrentPrcb()->KeExceptionDispatchCount += 1;
    ContextRecord.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_SEGMENTS;
    //在當前棧中分配一個 CONTEXT,調用 KeContextFromKframes 初始化它
    KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextRecord);

    //
    // If the exception is a break point, then convert the break point to a
    // fault.
    //

    if (ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) {
        ContextRecord.Rip -= 1;
    }

    //
    // If the exception is an internal general protect fault, invalid opcode,
    // or integer divide by zero, then attempt to resolve the problem without
    // actually raising an exception.
    // 

    if (KiPreprocessFault(ExceptionRecord,
                          TrapFrame,
                          &ContextRecord,
                          PreviousMode) != FALSE) {

        goto Handled1;
    }

    //
    // Select the method of handling the exception based on the previous mode.
    //

    if (PreviousMode == KernelMode) {

        //
        // Previous mode was kernel.
        //
        // If the kernel debugger is active, then give the kernel debugger
        // the first chance to handle the exception. If the kernel debugger
        // handles the exception, then continue execution. Otherwise, attempt
        // to dispatch the exception to a frame based handler. If a frame
        // based handler handles the exception, then continue execution.
        //
        // If a frame based handler does not handle the exception, give the
        // kernel debugger a second chance, if it's present.
        //
        // If the exception is still unhandled call bugcheck.
        //

        if (FirstChance != FALSE) {
            if ((KiDebugRoutine)(TrapFrame,  //內核調試器處理  KdpTrap   KdpStub
                                 ExceptionFrame,
                                 ExceptionRecord,
                                 &ContextRecord,
                                 PreviousMode,
                                 FALSE) != FALSE) {

                goto Handled1;
            }

            //
            // Kernel debugger didn't handle exception.
            //
            // If interrupts are disabled, then bugcheck.
            //

            if (RtlDispatchException(ExceptionRecord, &ContextRecord) != FALSE) {
                goto Handled1;
            }
        }

        //
        // This is the second chance to handle the exception.
        //

        if ((KiDebugRoutine)(TrapFrame,
                             ExceptionFrame,
                             ExceptionRecord,
                             &ContextRecord,
                             PreviousMode,
                             TRUE) != FALSE) {

            goto Handled1;
        }
        //藍屏
        KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED,
                     ExceptionRecord->ExceptionCode,
                     (ULONG64)ExceptionRecord->ExceptionAddress,
                     ExceptionRecord->ExceptionInformation[0],
                     ExceptionRecord->ExceptionInformation[1]);

    } else {  //UserMode

        //
        // Previous mode was user.
        //
        // If this is the first chance and the current process has a debugger
        // port, then send a message to the debugger port and wait for a reply.
        // If the debugger handles the exception, then continue execution. Else
        // transfer the exception information to the user stack, transition to
        // user mode, and attempt to dispatch the exception to a frame based
        // handler. If a frame based handler handles the exception, then continue
        // execution with the continue system service. Else execute the
        // NtRaiseException system service with FirstChance == FALSE, which
        // will call this routine a second time to process the exception.
        //
        // If this is the second chance and the current process has a debugger
        // port, then send a message to the debugger port and wait for a reply.
        // If the debugger handles the exception, then continue execution. Else
        // if the current process has a subsystem port, then send a message to
        // the subsystem port and wait for a reply. If the subsystem handles the
        // exception, then continue execution. Else terminate the process.
        //
        // If the current process is a wow64 process, an alignment fault has
        // occurred, and the AC bit is set in EFLAGS, then clear AC in EFLAGS
        // and continue execution. Otherwise, attempt to resolve the exception.
        //

        if ((PsGetCurrentProcess()->Wow64Process != NULL) &&
            (ExceptionRecord->ExceptionCode == STATUS_DATATYPE_MISALIGNMENT) &&
            ((TrapFrame->EFlags & EFLAGS_AC_MASK) != 0)) {

            TrapFrame->EFlags &= ~EFLAGS_AC_MASK;
            goto Handled2;
        }

        //
        // If the exception happened while executing 32-bit code, then convert
        // the exception to a wow64 exception. These codes are translated later
        // by wow64.
        //

        if ((ContextRecord.SegCs & 0xfff8) == KGDT64_R3_CMCODE) {
            
            switch (ExceptionRecord->ExceptionCode) {
            case STATUS_BREAKPOINT:
                ExceptionRecord->ExceptionCode = STATUS_WX86_BREAKPOINT;
                break;

            case STATUS_SINGLE_STEP:
                ExceptionRecord->ExceptionCode = STATUS_WX86_SINGLE_STEP;
                break;
            }

            //
            // Clear the upper 32-bits of the stack address and 16-byte
            // align the stack address.
            //

            FaultingRsp = (ContextRecord.Rsp & 0xfffffff0UI64);

        } else {
            FaultingRsp = ContextRecord.Rsp;
        }

        if (FirstChance == TRUE) {

            //
            // This is the first chance to handle the exception.
            //
            // If the current processor is not being debugged and user mode
            // exceptions are not being ignored, or this is a debug service,
            // then attempt to handle the exception via the kernel debugger.
            //


            DebugService = KdIsThisAKdTrap(ExceptionRecord,
                                           &ContextRecord,
                                           UserMode);

            if (((PsGetCurrentProcess()->DebugPort == NULL) &&
                 (KdIgnoreUmExceptions == FALSE)) ||
                (DebugService == TRUE)) {

                //
                // Attempt to handle the exception with the kernel debugger.
                //

                if ((KiDebugRoutine)(TrapFrame,
                                     ExceptionFrame,
                                     ExceptionRecord,
                                     &ContextRecord,
                                     PreviousMode,
                                     FALSE) != FALSE) {

                    goto Handled1;
                }
            }

            if ((ExceptionWasForwarded == FALSE) &&
                (DbgkForwardException(ExceptionRecord, TRUE, FALSE))) {

                goto Handled2;
            }

            //
            // Clear the trace flag in the trap frame so a spurious trace
            // trap is guaranteed not to occur in the trampoline code.
            //
    
            TrapFrame->EFlags &= ~EFLAGS_TF_MASK;

            //
            // Transfer exception information to the user stack, transition
            // to user mode, and attempt to dispatch the exception to a frame
            // based handler.
            //

            ExceptionRecord1.ExceptionCode = STATUS_ACCESS_VIOLATION;

        repeat:
            try {

                //
                // Compute address of aligned machine frame, compute address
                // of exception record, compute address of context record,
                // and probe user stack for writeability.
                //

                MachineFrame =
                    (PMACHINE_FRAME)((FaultingRsp - sizeof(MACHINE_FRAME)) & ~STACK_ROUND);

                UserStack1 = (ULONG64)MachineFrame - EXCEPTION_RECORD_LENGTH;
                UserStack2 = UserStack1 - CONTEXT_LENGTH;
                ProbeForWriteSmallStructure((PVOID)UserStack2,
                                            sizeof(MACHINE_FRAME) + EXCEPTION_RECORD_LENGTH + CONTEXT_LENGTH,
                                            STACK_ALIGN);

                //
                // Fill in machine frame information.
                //

                MachineFrame->Rsp = FaultingRsp;
                MachineFrame->Rip = ContextRecord.Rip;

                //
                // Copy exception record to the user stack.
                //

                *(PEXCEPTION_RECORD)UserStack1 = *ExceptionRecord;

                //
                // Copy context record to the user stack.
                //

                *(PCONTEXT)UserStack2 = ContextRecord;

                //
                // Set the address of the new stack pointer in the current
                // trap frame.
                //

                TrapFrame->Rsp = UserStack2;

                //
                // Set the user mode 64-bit code selector.
                //

                TrapFrame->SegCs = KGDT64_R3_CODE | RPL_MASK;

                //
                // Set the address of the exception routine that will call the
                // exception dispatcher and then return to the trap handler.
                // The trap handler will restore the exception and trap frame
                // context and continue execution in the routine that will
                // call the exception dispatcher.
                //

                TrapFrame->Rip = (ULONG64)KeUserExceptionDispatcher;
                return;

            } except (KiCopyInformation(&ExceptionRecord1,
                        (GetExceptionInformation())->ExceptionRecord)) {

                //
                // If the exception is a stack overflow, then attempt to
                // raise the stack overflow exception. Otherwise, the user's
                // stack is not accessible, or is misaligned, and second
                // chance processing is performed.
                //

                if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) {
                    ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
                    *ExceptionRecord = ExceptionRecord1;

                    goto repeat;
                }
            }
        }

        //
        // This is the second chance to handle the exception.
        //

        if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) {
            goto Handled2;

        } else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) {
            goto Handled2;

        } else {
            ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
            KeBugCheckEx(KMODE_EXCEPTION_NOT_HANDLED,
                         ExceptionRecord->ExceptionCode,
                         (ULONG64)ExceptionRecord->ExceptionAddress,
                         ExceptionRecord->ExceptionInformation[0],
                         ExceptionRecord->ExceptionInformation[1]);
        }
    }

    //
    // Move machine state from context frame to trap and exception frames and
    // then return to continue execution with the restored state.
    //

Handled1:
    KeContextToKframes(TrapFrame,
                       ExceptionFrame,
                       &ContextRecord,
                       ContextRecord.ContextFlags,
                       PreviousMode);

    //
    // Exception was handled by the debugger or the associated subsystem
    // and state was modified, if necessary, using the get state and set
    // state capabilities. Therefore the context frame does not need to
    // be transferred to the trap and exception frames.
    //

Handled2:
    return;
}
KiDispatchException

我們主要關注KernelMode的異常處理情況,重點是RtlDispatchException的操作:

函數流程大致如下:

遍歷當前線程的異常鏈表,挨個調用 RtlpExecuteHandlerForException,RtlpExecuteHandlerForException 會調用異常處理函數。再根據返回值做出不同的處理:

對於 ExceptionContinueExecution,結束遍歷,返回。(對於標記為‘EXCEPTION_NONCONTINUABLE’的異常,會調用 RtlRaiseException。)
對於 ExceptionContinueSearch,繼續遍歷下一個結點;
對於 ExceptionNestedException,則從指定的新異常繼續遍歷;
只有正確處理 ExceptionContinueExecution 才會返回 TRUE,其他情況都返回 FALSE。

BOOLEAN
RtlDispatchException (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN PCONTEXT ContextRecord
    )

/*++

Routine Description:

    This function attempts to dispatch an exception to a call frame based
    handler by searching backwards through the stack based call frames. The
    search begins with the frame specified in the context record and continues
    backward until either a handler is found that handles the exception, the
    stack is found to be invalid (i.e., out of limits or unaligned), or the end
    of the call hierarchy is reached.

Arguments:

    ExceptionRecord - Supplies a pointer to an exception record.

    ContextRecord - Supplies a pointer to a context record.

Return Value:

    If the exception is handled by one of the frame based handlers, then
    a value of TRUE is returned. Otherwise a value of FALSE is returned.

--*/

{

    BOOLEAN Completion = FALSE;
    DISPATCHER_CONTEXT DispatcherContext;
    EXCEPTION_DISPOSITION Disposition;
    PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
    PEXCEPTION_REGISTRATION_RECORD NestedRegistration;
    ULONG HighAddress;
    ULONG HighLimit;
    ULONG LowLimit;
    EXCEPTION_RECORD ExceptionRecord1;
    ULONG Index;

    //
    // Get current stack limits.
    //

    RtlpGetStackLimits(&LowLimit, &HighLimit);

    //
    // Start with the frame specified by the context record and search
    // backwards through the call frame hierarchy attempting to find an
    // exception handler that will handler the exception.
    //

    RegistrationPointer = RtlpGetRegistrationHead();
    NestedRegistration = 0;

    while (RegistrationPointer != EXCEPTION_CHAIN_END) {

        //
        // If the call frame is not within the specified stack limits or the
        // call frame is unaligned, then set the stack invalid flag in the
        // exception record and return FALSE. Else check to determine if the
        // frame has an exception handler.
        //

        HighAddress = (ULONG)RegistrationPointer +
            sizeof(EXCEPTION_REGISTRATION_RECORD);

        if ( ((ULONG)RegistrationPointer < LowLimit) ||
             (HighAddress > HighLimit) ||
             (((ULONG)RegistrationPointer & 0x3) != 0) 
           ) {

            //
            // Allow for the possibility that the problem occured on the
            // DPC stack.
            //

            ULONG TestAddress = (ULONG)RegistrationPointer;

            if (((TestAddress & 0x3) == 0) &&
                KeGetCurrentIrql() >= DISPATCH_LEVEL) {

                PKPRCB Prcb = KeGetCurrentPrcb();
                ULONG DpcStack = (ULONG)Prcb->DpcStack;

                if ((Prcb->DpcRoutineActive) &&
                    (HighAddress <= DpcStack) &&
                    (TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {

                    //
                    // This error occured on the DPC stack, switch
                    // stack limits to the DPC stack and restart
                    // the loop.
                    //

                    HighLimit = DpcStack;
                    LowLimit = DpcStack - KERNEL_STACK_SIZE;
                    continue;
                }
            }

            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
            goto DispatchExit;
        }

        // See if the handler is reasonable

        if (!RtlIsValidHandler(RegistrationPointer->Handler)) {
            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
            goto DispatchExit;
        }

        //
        // The handler must be executed by calling another routine
        // that is written in assembler. This is required because
        // up level addressing of the handler information is required
        // when a nested exception is encountered.
        //

        if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
            Index = RtlpLogExceptionHandler(
                            ExceptionRecord,
                            ContextRecord,
                            0,
                            (PULONG)RegistrationPointer,
                            4 * sizeof(ULONG));
                    // can't use sizeof(EXCEPTION_REGISTRATION_RECORD
                    // because we need the 2 dwords above it.
        }

        Disposition = RtlpExecuteHandlerForException(
            ExceptionRecord,
            (PVOID)RegistrationPointer,
            ContextRecord,
            (PVOID)&DispatcherContext,
            (PEXCEPTION_ROUTINE)RegistrationPointer->Handler);

        if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) {
            RtlpLogLastExceptionDisposition(Index, Disposition);
        }

        //
        // If the current scan is within a nested context and the frame
        // just examined is the end of the context region, then clear
        // the nested context frame and the nested exception in the
        // exception flags.
        //

        if (NestedRegistration == RegistrationPointer) {
            ExceptionRecord->ExceptionFlags &= (~EXCEPTION_NESTED_CALL);
            NestedRegistration = 0;
        }

        //
        // Case on the handler disposition.
        //

        switch (Disposition) {

            //
            // The disposition is to continue execution. If the
            // exception is not continuable, then raise the exception
            // STATUS_NONCONTINUABLE_EXCEPTION. Otherwise return
            // TRUE.
            //

        case ExceptionContinueExecution :
            if ((ExceptionRecord->ExceptionFlags &
               EXCEPTION_NONCONTINUABLE) != 0) {
                ExceptionRecord1.ExceptionCode =
                                        STATUS_NONCONTINUABLE_EXCEPTION;
                ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                ExceptionRecord1.ExceptionRecord = ExceptionRecord;
                ExceptionRecord1.NumberParameters = 0;
                RtlRaiseException(&ExceptionRecord1);

            } else {
                Completion = TRUE;
                goto DispatchExit;
            }

            //
            // The disposition is to continue the search. If the frame isn't
            // suspect/corrupt, get next frame address and continue the search 
            //

        case ExceptionContinueSearch :
            if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID)
                goto DispatchExit;

            break;

            //
            // The disposition is nested exception. Set the nested
            // context frame to the establisher frame address and set
            // nested exception in the exception flags.
            //

        case ExceptionNestedException :
            ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
            if (DispatcherContext.RegistrationPointer > NestedRegistration) {
                NestedRegistration = DispatcherContext.RegistrationPointer;
            }

            break;

            //
            // All other disposition values are invalid. Raise
            // invalid disposition exception.
            //

        default :
            ExceptionRecord1.ExceptionCode = STATUS_INVALID_DISPOSITION;
            ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord1.ExceptionRecord = ExceptionRecord;
            ExceptionRecord1.NumberParameters = 0;
            RtlRaiseException(&ExceptionRecord1);
            break;
        }

        //
        // If chain goes in wrong direction or loops, report an
        // invalid exception stack, otherwise go on to the next one.
        //

        RegistrationPointer = RegistrationPointer->Next;
    }

    //
    // Call vectored continue handlers.
    //

DispatchExit:

    return Completion;
}
RtlDispatchException 源代碼
EXCEPTION_DISPOSITION
RtlpExecuteHandlerForException (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN PVOID EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PVOID DispatcherContext,
    IN PEXCEPTION_ROUTINE ExceptionRoutine
    );
RtlpExecuteHandlerForException

然后了解異常鏈表在線程中的位置

 kd> dt _ETHREAD
  ntdll!_ETHREAD
     +0x000 Tcb              : _KTHREAD


 kd> dt _KTHREAD
  ntdll!_KTHREAD
                ......
     +0x074 Teb              : Ptr32 Void

 kd> dt _TEB
  ntdll!_TEB
     +0x000 NtTib            : _NT_TIB


 kd> dt _NT_TIB
  ntdll!_NT_TIB
     +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD  //異常處理鏈表
     +0x004 StackBase        : Ptr32 Void
     +0x008 StackLimit       : Ptr32 Void
     +0x00c SubSystemTib     : Ptr32 Void
     +0x010 FiberData        : Ptr32 Void
     +0x010 Version          : Uint4B
     +0x014 ArbitraryUserPointer : Ptr32 Void
     +0x018 Self             : Ptr32 _NT_TIB

系統根據FS寄存器來尋找異常處理鏈表,在應用層,FS 寄存器“指向”當前執行線程的 _TEB 結構體。

在內核層,FS 寄存器“指向”另一個跟 CPU 相關的結構體:_KPCR,來看看它的結構,

kd> dt _kpcr
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
     ......省略

與 _TEB 一樣,它的第一個域成員也是 _NT_TIB,只不過此時是 nt!_NT_TIB,而在應用層是 ntdll!_NT_TIB,但它們的結構是一樣的。
這樣,不論在應用層還是在內核層,系統都可以使用 FS:[0] 找到異常鏈表。

 

總結一下異常處理調用流程
硬件異常:
CPU 檢測到異常 -> KiTrap?? -> KiDispatchException -> RtlDispatchException -> RtlpExecuteHandlerForException
軟件異常:
RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException

接下來看看RtlRaiseException 函數,大致流程就是:

首先調用 RtlDispatchException 分發異常,如果 RtlDispatchException 成功分發,有處理函數處理了這個異常,那么結束本函數。
 如果沒有成功分發,那么調用 ZwRaiseException 再次觸發該異常,這次傳入的異常的 FirstChance 被置為 FALSE。

DECLSPEC_NOINLINE
VOID
RtlRaiseException (
    IN PEXCEPTION_RECORD ExceptionRecord
    )

/*++

Routine Description:

    This function raises a software exception by building a context record
    and calling the raise exception system service.

Arguments:

    ExceptionRecord - Supplies a pointer to an exception record.

Return Value:

    None.

--*/

{

    CONTEXT ContextRecord;
    ULONG64 ControlPc;
    ULONG64 EstablisherFrame;
    PRUNTIME_FUNCTION FunctionEntry;
    PVOID HandlerData;
    ULONG64 ImageBase;
    NTSTATUS Status = STATUS_INVALID_DISPOSITION;

    //
    // Capture the current context, unwind to the caller of this routine, set
    // the exception address, and call the appropriate exception dispatcher.
    //

    RtlCaptureContext(&ContextRecord);
    ControlPc = ContextRecord.Rip;
    FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
    if (FunctionEntry != NULL) {
        RtlVirtualUnwind(UNW_FLAG_NHANDLER,
                         ImageBase,
                         ControlPc,
                         FunctionEntry,
                         &ContextRecord,
                         &HandlerData,
                         &EstablisherFrame,
                         NULL);

        ExceptionRecord->ExceptionAddress = (PVOID)ContextRecord.Rip;

        if (RtlDispatchException(ExceptionRecord, &ContextRecord) != FALSE) {
            return;
    
        }

        Status = ZwRaiseException(ExceptionRecord, &ContextRecord, FALSE);
    }

    //
    // There should never be a return from either exception dispatch or the
    // system service unless there is a problem with the argument list itself.
    // Raise another exception specifying the status value returned.
    //

    RtlRaiseStatus(Status);
    return;
}
RtlRaiseException

 

到這里,系統提供的 SEH 機制,大致完畢,我們可以回顧一下:
1. 系統的SEH的實現較簡單,代碼量不大,而且 wrk 基本上有所有關鍵函數的實現代碼。
2. 系統的SEH的功能過於簡單,實際過程中很難直接使用。整個異常處理過程無非就是遍歷異常鏈表,挨個調用異常注冊信息的處理函數,
    如果其中有某個處理函數處理了該異常(返回值為 ExceptionContinueExecution),
    那么就從異常觸發點(如果是斷點異常,則要回退一個字節的指令(int 3 指令本身))重新執行。
    否則不管是整個鏈表中沒有找到合適的處理函數(返回值為 ExceptionContinueSearch),
    或者遍歷過程中出現問題(返回值為 ExceptionNestedException),系統都會簡單粗暴的 BUGCHECK。

那么問題來了:
線程運行過程中會調用很多個函數,每個函數都有可能注冊異常處理,
它們提供的異常處理函數既可能處理該函數自身觸發的異常,又可能需要處理其子孫函數觸發的異常。
前者還好說,自己出了問題,多少還有可能自己修復。
而后者就很頭疼了,它無法了解所有其調用的子孫函數內部的實現,要想修復子孫函數觸發的異常,太困難了。
而一旦沒有正確處理,或者沒人處理,系統就崩掉,這個后果太嚴重。
於是實際上現實程序設計中,基本上沒有直接使用系統的SEH機制,而是使用編譯器提供的增強版本。

 

下面就學習編譯器提供的增強版本。

 增強版中的結構體

    typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
    struct _EXCEPTION_REGISTRATION{
        PEXCEPTION_POINTERS xpointers;
        struct _EXCEPTION_REGISTRATION *prev;
        void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
        struct scopetable_entry *scopetable;    //類型為 scopetable_entry 的數組
        int trylevel;          //數組下標,用來索引 scopetable 中的數組成員。
        int _ebp;    //包含該 _EXCEPTION_REGISTRATION 結構體的函數的棧幀指針。
                     //對於沒有 FPO 優化過的函數,一開頭通常有個 push ebp 的操作,_ebp 的值就是被壓入的 ebp 的值
    };

 

也就是說它沿用了系統SEH的注冊信息結構,只是在域成員名稱上做了些改動,把 Next 改名為 prev,把 Handler 改為 handler。

除此之外,在原始版本基礎上增加了4個域成員(scopetable、trylevel、_ebp、xpointers),用來支持它的增強功能。

scopetable_entry
   +0x000 previousTryLevel : Uint4B
   +0x004 lpfnFilter       : Ptr32     int 
   +0x008 lpfnHandler      : Ptr32     int

 

按照原始版本的設計,每一對“觸發異常-處理異常”都會有一個注冊信息即 EXCEPTION_REGISTRATION_RECORD。
也就是說,如果按照原始的設計,每一個 __try/__except(__finally) 都應該對應一個 EXCEPTION_REGISTRATION。但是實際的 MSC(微軟編譯器,我用的VS2010)實現不是這樣的。
真正的實現是:
每個使用 __try/__except(__finally) 的函數,不管其內部嵌套或反復使用多少 __try/__except(__finally),都只注冊一遍,
即只將一個 EXCEPTION_REGISTRATION 掛入當前線程的異常鏈表中
(對於遞歸函數,每一次調用都會創建一個 EXCEPTION_REGISTRATION,並掛入線程的異常鏈表中,這是另外一回事)。


那如何處理函數內部出現的多個 __try/__except(__finally) 呢?
這多個 __except 代碼塊的功能可能大不相同,而注冊信息 EXCEPTION_REGISTRATION 中只能提供一個處理函數 handler,怎么辦?


MSC 的做法是,MSC 提供一個處理函數,即 EXCEPTION_REGISTRATION::handler 被設置為 MSC 的某個函數,而不是我們自己提供的 __except 代碼塊。
我們自己提供的多個 __except 塊被存儲在 EXCEPTION_REGISTRATION::scopetable 數組中。
我們看看上面的 scopetable_entry 定義,由於我沒有找到它的定義代碼,所以就貼了 windbg 中 dt 輸出結果。
其中 scopetable_entry::lpfnHandler 就是程序猿提供的 __except 異常處理塊代碼。
而 lpfnFilter 就是 __except 的過濾塊代碼。對於 __finally 代碼塊,其 lpfnFilter 被置為 NULL,lpfnHandler 就是其包含的代碼塊。

下面,我們用一小段簡單的偽代碼來學習

 1    VOID SimpleSEH()
 2       {
 3          __try
 4            {   
 5            }   
 6          __except(ExceptionFilter_0(...))
 7           {   
 8               ExceptCodeBlock_0;
 9           }   
10 
11 
12         
13          __try
14           {
15               __try
16               {
              //假設觸發異常
17 } 18 __except(ExceptionFilter_1(...)) 19 { 20 ExceptCodeBlock_1; 21 } 22 } 23 __except(ExceptionFilter_2(...)) 24 { 25 ExceptCodeBlock_2; 26 } 27 }

編譯時,編譯器會為 SimpleSeh 分配一個 EXCEPTION_REGISTRATION 和一個擁有3個成員的 scopetable 數組,並將 EXCEPTION_REGISTRATION::scopetable 指向該數組(請留意:EXCEPTION_REGISTRATION::scopetable 只是一個指針,不是數組)。然后按照 __try 關鍵字出現的順序,將對應的__except/__finally 都存入該數組,步驟如下:

  scopetable[0].lpfnFilter = ExceptionFilter_0;
  scopetable[0].lpfnHandler = ExceptCodeBlock_0;

  scopetable[1].lpfnFilter = ExceptionFilter_1;
  scopetable[1].lpfnHandler = ExceptCodeBlock_1;

  scopetable[2].lpfnFilter = ExceptionFilter_2;
  scopetable[2].lpfnHandler = ExceptCodeBlock_2;

我們假象當前開始執行 SimpleSEH 函數,在行16和17行之間觸發了異常。
根據之前的流程:RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException。
RtlpExecuteHandlerForException 會調用注冊信息中的處理函數,即 EXCEPTION_REGISTRATION::handler。
該函數是由 MSC 提供的,內部會依次調用 scopetable 中的 lpfnHandler。
那咱們來模擬執行一下,在16和17行之前觸發異常,那應該先從 scopetable[2] 的 ExceptionFilter_2 開始執行,
假設該函數返回 EXCEPTION_CONTINUE_SEARCH,那接下來應該是 scopetable[1];
假設 ExceptionFilter_1 也返回 EXCEPTION_CONTINUE_SEARCH;
那么接下來是不是就應該輪到 scopetable[0] 了?不是。
再看看上面的偽代碼,行16和行17之間的代碼並沒處於第一個 __try/__except 的范圍中,該異常輪不到 scopetable[0] 來處理。
那怎么辦?
SimpleSEH 執行的過程中怎么知道到 scopetable[1] 就應該停止?

 

MSC 是通過 scopetable_entry::previousTryLevel 來解決這個問題的。上面數組的設置,完整的形式其實是這樣:

  scopetable[0].previousTryLevel = TRYLEVEL_NONE;
  scopetable[0].lpfnFilter = ExceptionFilter_0;
  scopetable[0].lpfnHandler = ExceptCodeBlock_0;

  scopetable[1].previousTryLevel = TRYLEVEL_NONE;
  scopetable[1].lpfnFilter = ExceptionFilter_1;
  scopetable[1].lpfnHandler = ExceptCodeBlock_1;

  scopetable[2].previousTryLevel = 1;
  scopetable[2].lpfnFilter = ExceptionFilter_2;
  scopetable[2].lpfnHandler = ExceptCodeBlock_2;

scopetable_entry::previousTryLevel 包含的意思是“下一個該輪到數組下標為 previousTryLevel 的單元了”。

當 scopetable_entry::previousTryLevel 等於 TRYLEVEL_NONE(-1) 時,就會停止遍歷 scopetable。

TRYLEVEL_NONE           equ     -1
TRYLEVEL_INVALID        equ     -2

咱再來模擬執行一遍,當14和15行之間觸發異常時,首先遍歷到 scopetable[2],處理完后,找到 scopetable[2].previousTryLevel,發現其值為1,那么遍歷到 scopetable[1],處理完后,找到 scopetable[1].previousTryLevel,發現其值為 TRYLEVEL_NONE,於是停止遍歷。
 好像挺圓滿的。

再假設下,如果行4和行5之間觸發了同樣的異常,執行流程應該如何。
首先,執行 scopetable[2],然后在 scopetable[1],然后……(省略若干同上字)。
停!這次的異常是在第一個 __try/__except 中觸發的,輪不到 scopetable[2] 來處理,怎么辦?


這個時候就輪到 EXCEPTION_REGISTRATION::trylevel 出場了。EXCEPTION_REGISTRATION::trylevel 的作用就是標識從那個數組單元開始遍歷。
與 scopetable_entry::previousTryLevel 不同,EXCEPTION_REGISTRATION::trylevel 是動態變化的,也就是說,這個值在 SimpleSeh 執行過程中是會經常改變的。
比如:
執行到行4和行5之間,該值就會被修改為0;
執行到第12行,該值被修改為1;
執行到14行,該值為2。
這樣,當異常觸發時候,MSC 就能正確的遍歷 scopetable 了。

 

 到目前位置,已經熟悉了增強版的概要流程。下面結合真實代碼來分析。代碼分為三塊:SEH 創建代碼、MSC 提供的 handler 函數,以及展開函數。

先把后面分析要用的宏和結構體列出來:

    #define EXCEPTION_NONCONTINUABLE 0x1    // Noncontinuable exception
    #define EXCEPTION_UNWINDING 0x2         // Unwind is in progress
    #define EXCEPTION_EXIT_UNWIND 0x4       // Exit unwind is in progress
    #define EXCEPTION_STACK_INVALID 0x8     // Stack out of limits or unaligned
    #define EXCEPTION_NESTED_CALL 0x10      // Nested exception handler call
    #define EXCEPTION_TARGET_UNWIND 0x20    // Target unwind in progress
    #define EXCEPTION_COLLIDED_UNWIND 0x40  // Collided exception handler call

    #define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \
                              EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)

    nt!_EXCEPTION_RECORD
       +0x000 ExceptionCode    : Int4B
       +0x004 ExceptionFlags   : Uint4B
       +0x008 ExceptionRecord  : Ptr32 _EXCEPTION_RECORD
       +0x00c ExceptionAddress : Ptr32 Void
       +0x010 NumberParameters : Uint4B
       +0x014 ExceptionInformation : [15] Uint4B

    typedef enum _EXCEPTION_DISPOSITION {
        ExceptionContinueExecution,
        ExceptionContinueSearch,
        ExceptionNestedException,
        ExceptionCollidedUnwind
    } EXCEPTION_DISPOSITION;

    // scopetable_entry::lpfnFilter 的返回值,也就是 __except 過濾塊的返回值
    #define EXCEPTION_EXECUTE_HANDLER       1
    #define EXCEPTION_CONTINUE_SEARCH       0
    #define EXCEPTION_CONTINUE_EXECUTION    -1

一、SEH 創建代碼

#include <ntifs.h>
#include <devioctl.h>

VOID TestSeh();
LONG Filter_0();
LONG Filter_2();

NTSTATUS
DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
{
    TestSeh();
    return STATUS_SUCCESS;
}

VOID TestSeh()
{
    
       ULONG ulVal = 0;

        __try // 第一個 __try 域
        {
            ulVal = 0x11111111; // 最后一位為1表示“在 __try 代碼塊中”
        }
        __except(Filter_0())
        {
            ulVal = 0x11111110; // 最后一位為0表示“在 __except/__finally 代碼塊中”
        }

        __try // 第二個 __try 域
        {
            ulVal = 0x22222222;

            __try // 第三個 __try 域
            {
                ulVal = 0x33333333;

                *((ULONG*)NULL) = ulVal; // 觸發異常
            }
            __finally
            {
                ulVal = 0x33333330;
            }
        }
        __except(Filter_2())
        {
            ulVal = 0x22222220;
        }

        return;

}

LONG Filter_0()
{
    return EXCEPTION_EXECUTE_HANDLER;
}


LONG Filter_2()
{
    return EXCEPTION_EXECUTE_HANDLER;

}

 

將生成的文件用Ida反匯編查看,TestSeh() 函數如下

 

.text:00011030 ; _DWORD __stdcall TestSeh()
.text:00011030 _TestSeh@0      proc near               ; CODE XREF: DriverEntry(x,x)+5p
.text:00011030
.text:00011030 ulVal           = dword ptr -1Ch
.text:00011030 ms_exc          = CPPEH_RECORD ptr -18h
.text:00011030
.text:00011030                 mov     edi, edi
.text:00011032                 push    ebp
.text:00011033                 mov     ebp, esp
.text:00011035                 push    0FFFFFFFEh
.text:00011037                 push    offset scopetable ; ExceptionRegister->scopetable
.text:0001103C                 push    offset __except_handler4 ; ExceptionRegistration-->handler  系統自己的
.text:00011041                 mov     eax, large fs:0 ; prev
.text:00011047                 push    eax
.text:00011048                 add     esp, 0FFFFFFF4h
.text:0001104B                 push    ebx
.text:0001104C                 push    esi
.text:0001104D                 push    edi
.text:0001104E                 mov     eax, ___security_cookie
.text:00011053                 xor     [ebp+ms_exc.registration.ScopeTable], eax ; 對scopetable進行加密
.text:00011056                 xor     eax, ebp        ; 對security_cookie進行異或加密
.text:00011058                 push    eax
.text:00011059                 lea     eax, [ebp+ms_exc.registration]
.text:0001105C                 mov     large fs:0, eax ; registration掛入線程異常鏈表
.text:00011062                 mov     [ebp+ms_exc.old_esp], esp
.text:00011065                 mov     [ebp+ulVal], 0
.text:0001106C                 mov     [ebp+ms_exc.registration.TryLevel], 0 ; 進入第一個__try域,TryLevel=0
.text:00011073                 mov     [ebp+ulVal], 11111111h
.text:0001107A                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh ; 離開第一個__try域,TryLevel=TRYLEVEL_NONE (-2)
.text:00011081                 jmp     short loc_1109A
.text:00011083 ; ---------------------------------------------------------------------------
.text:00011083
.text:00011083 $LN7:                                   ; DATA XREF: .rdata:scopetableo
.text:00011083                 call    _Filter_0@0     ; Exception filter 0 for function 11030
.text:00011088
.text:00011088 $LN9:
.text:00011088                 retn
.text:00011089 ; ---------------------------------------------------------------------------
.text:00011089
.text:00011089 $LN8:                                   ; DATA XREF: .rdata:scopetableo
.text:00011089                 mov     esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 11030
.text:0001108C                 mov     [ebp+ulVal], 11111110h
.text:00011093                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:0001109A
.text:0001109A loc_1109A:                              ; CODE XREF: TestSeh()+51j
.text:0001109A                 mov     [ebp+ms_exc.registration.TryLevel], 1 ; 第二個__try域,TryLevel=1
.text:000110A1                 mov     [ebp+ulVal], 22222222h
.text:000110A8                 mov     [ebp+ms_exc.registration.TryLevel], 2 ; 第三個__try域,TryLevel=2
.text:000110AF                 mov     [ebp+ulVal], 33333333h
.text:000110B6                 mov     eax, [ebp+ulVal]
.text:000110B9                 mov     large ds:0, eax ; *((ULONG*)NULL) = ulVal; 觸發異常
.text:000110BE                 mov     [ebp+ms_exc.registration.TryLevel], 1 ; 離開第三個__try域,TryLevel=1
.text:000110C5                 call    $LN15           ; Finally handler 2 for function 11030
.text:000110CA ; ---------------------------------------------------------------------------
.text:000110CA
.text:000110CA loc_110CA:                              ; CODE XREF: TestSeh():$LN16j
.text:000110CA                 jmp     short $LN18
.text:000110CC ; ---------------------------------------------------------------------------
.text:000110CC
.text:000110CC $LN15:                                  ; CODE XREF: TestSeh()+95j
.text:000110CC                                         ; DATA XREF: .rdata:scopetableo
.text:000110CC                 mov     [ebp+ulVal], 33333330h ; Finally handler 2 for function 11030
.text:000110D3
.text:000110D3 $LN16:
.text:000110D3                 retn
.text:000110D4 ; ---------------------------------------------------------------------------
.text:000110D4
.text:000110D4 $LN18:                                  ; CODE XREF: TestSeh():loc_110CAj
.text:000110D4                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:000110DB                 jmp     short loc_110F4
.text:000110DD ; ---------------------------------------------------------------------------
.text:000110DD
.text:000110DD $LN11:                                  ; DATA XREF: .rdata:scopetableo
.text:000110DD                 call    _Filter_0@0     ; Exception filter 1 for function 11030
.text:000110E2
.text:000110E2 $LN13:
.text:000110E2                 retn
.text:000110E3 ; ---------------------------------------------------------------------------
.text:000110E3
.text:000110E3 $LN12:                                  ; DATA XREF: .rdata:scopetableo
.text:000110E3                 mov     esp, [ebp+ms_exc.old_esp] ; Exception handler 1 for function 11030
.text:000110E6                 mov     [ebp+ulVal], 22222220h ; 第二個__except處理
.text:000110ED                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:000110F4
.text:000110F4 loc_110F4:                              ; CODE XREF: TestSeh()+ABj
.text:000110F4                 mov     ecx, [ebp+ms_exc.registration.Next] ; 恢復舊的EXCEPTION_REGISTRATION,從鏈表中摘除ExceptionRegistration
.text:000110F7                 mov     large fs:0, ecx
.text:000110FE                 pop     ecx
.text:000110FF                 pop     edi
.text:00011100                 pop     esi
.text:00011101                 pop     ebx
.text:00011102                 mov     esp, ebp
.text:00011104                 pop     ebp
.text:00011105                 retn
.text:00011105 _TestSeh@0      endp

 

 

用WinDbg來看看 scopetable 的內容:

kd> uf SEHx86!TestSeh
SEHx86!TestSeh [d:\sehx86\sehx86.c @ 18]:
   18 91de8030 8bff            mov     edi,edi
   18 91de8032 55              push    ebp
   18 91de8033 8bec            mov     ebp,esp
   18 91de8035 6afe            push    0FFFFFFFEh
   18 91de8037 68c890de91      push    offset SEHx86!__safe_se_handler_table+0x8 (91de90c8)
   18 91de803c 683081de91      push    offset SEHx86!_except_handler4 (91de8130)
   18 91de8041 64a100000000    mov     eax,dword ptr fs:[00000000h]
   18 91de8047 50              push    eax
   18 91de8048 83c4f4          add     esp,0FFFFFFF4h
   18 91de804b 53              push    ebx
   18 91de804c 56              push    esi
   18 91de804d 57              push    edi
   18 91de804e a100a0de91      mov     eax,dword ptr [SEHx86!__security_cookie (91dea000)]
   18 91de8053 3145f8          xor     dword ptr [ebp-8],eax
   18 91de8056 33c5            xor     eax,ebp
   18 91de8058 50              push    eax
   18 91de8059 8d45f0          lea     eax,[ebp-10h]
   18 91de805c 64a300000000    mov     dword ptr fs:[00000000h],eax
   18 91de8062 8965e8          mov     dword ptr [ebp-18h],esp
   20 91de8065 c745e400000000  mov     dword ptr [ebp-1Ch],0
   22 91de806c c745fc00000000  mov     dword ptr [ebp-4],0
   24 91de8073 c745e411111111  mov     dword ptr [ebp-1Ch],11111111h
   25 91de807a c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
   25 91de8081 eb17            jmp     SEHx86!TestSeh+0x6a (91de809a)

SEHx86!TestSeh+0x6a [d:\sehx86\sehx86.c @ 31]:
   31 91de809a c745fc01000000  mov     dword ptr [ebp-4],1
   33 91de80a1 c745e422222222  mov     dword ptr [ebp-1Ch],22222222h
   35 91de80a8 c745fc02000000  mov     dword ptr [ebp-4],2
   37 91de80af c745e433333333  mov     dword ptr [ebp-1Ch],33333333h
   39 91de80b6 8b45e4          mov     eax,dword ptr [ebp-1Ch]
   39 91de80b9 a300000000      mov     dword ptr ds:[00000000h],eax
   41 91de80be c745fc01000000  mov     dword ptr [ebp-4],1
   41 91de80c5 e802000000      call    SEHx86!TestSeh+0x9c (91de80cc)
   41 91de80ca eb08            jmp     SEHx86!TestSeh+0xa4 (91de80d4)

SEHx86!TestSeh+0xa4 [d:\sehx86\sehx86.c @ 45]:
   45 91de80d4 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
   45 91de80db eb17            jmp     SEHx86!TestSeh+0xc4 (91de80f4)

SEHx86!TestSeh+0xc4 [d:\sehx86\sehx86.c @ 53]:
   53 91de80f4 8b4df0          mov     ecx,dword ptr [ebp-10h]
   53 91de80f7 64890d00000000  mov     dword ptr fs:[0],ecx
   53 91de80fe 59              pop     ecx
   53 91de80ff 5f              pop     edi
   53 91de8100 5e              pop     esi
   53 91de8101 5b              pop     ebx
   53 91de8102 8be5            mov     esp,ebp
   53 91de8104 5d              pop     ebp
   53 91de8105 c3              ret
uf Windbg

 

 

kd> dd 91de90c8
91de90c8 【fffffffe 00000000 ffffffd4 00000000】   >>>16個字節的空間
91de90d8 【fffffffe 91de8083 91de8089】【fffffffe   >>>3個socpetable_entry結構 
91de90e8 91de80dd 91de80e3】【00000001 00000000
91de90f8 91de80cc】 00000000 00000000 00000000
91de9108 00000000 00000000 00000000 00000000
91de9118 00000000 00000000 00000000 00000000
91de9128 00000000 00000000 00000000 00000000
91de9138 00000000 00000000 00000000 00000000

 

接下來再看系統自己的handler函數

text:00011130 ; _EXCEPTION_DISPOSITION __cdecl _except_handler4(_EXCEPTION_RECORD *ExceptionRecord, _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, _CONTEXT *ContextRecord, void *DispatcherContext)
.text:00011130 __except_handler4 proc near             ; DATA XREF: TestSeh()+Co
.text:00011130                                         ; .rdata:___safe_se_handler_tableo
.text:00011130
.text:00011130 ExceptionPointers= _EXCEPTION_POINTERS ptr -14h
.text:00011130 ScopeTableRecord= dword ptr -0Ch
.text:00011130 Disposition     = dword ptr -8
.text:00011130 Revalidate      = byte ptr -1
.text:00011130 ExceptionRecord = dword ptr  8
.text:00011130 EstablisherFrame= dword ptr  0Ch
.text:00011130 ContextRecord   = dword ptr  10h
.text:00011130 DispatcherContext= dword ptr  14h
.text:00011130
.text:00011130                 mov     edi, edi        ; 這里的_EXCEPTION_REGISTRATION_RECORD是編譯器增強版,不是wrk中的定義
.text:00011132                 push    ebp
.text:00011133                 mov     ebp, esp
.text:00011135                 sub     esp, 14h
.text:00011138                 push    ebx
.text:00011139                 mov     ebx, [ebp+EstablisherFrame]
.text:0001113C                 push    esi
.text:0001113D                 mov     esi, [ebx+8]    ; scopetable
.text:00011140                 xor     esi, ___security_cookie ; 解密scopetable
.text:00011146                 push    edi
.text:00011147                 mov     eax, [esi]
.text:00011149                 mov     [ebp+Revalidate], 0 ; BOOLEAN 用來表示是否執行過任何 scopetable_entry::lpfnFilter
.text:0001114D                 mov     [ebp+Disposition], 1 ; 函數的返回值,初始化為EXCEPTION_EXECUTE_HANDLER(0)
.text:00011154                 lea     edi, [ebx+10h]  ; ebx為ExceptionRigstration,+10h即為_ebp
.text:00011157                 cmp     eax, 0FFFFFFFEh
.text:0001115A                 jz      short loc_11169
.text:0001115C                 mov     ecx, [esi+4]    ; 檢驗scopetable(1)
.text:0001115F                 add     ecx, edi
.text:00011161                 xor     ecx, [eax+edi]  ; cookie
.text:00011164                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00011169
.text:00011169 loc_11169:                              ; CODE XREF: __except_handler4+2Aj
.text:00011169                 mov     ecx, [esi+0Ch]  ; 檢驗scopetable(2)
.text:0001116C                 mov     eax, [esi+8]
.text:0001116F                 add     ecx, edi
.text:00011171                 xor     ecx, [eax+edi]  ; cookie
.text:00011174                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00011179                 mov     eax, [ebp+ExceptionRecord]
.text:0001117C                 test    byte ptr [eax+4], 66h ; wrk中的定義EXCEPTION_UNWIND equ 00066H
.text:0001117C                                         ; ExceptionRecord->ExceptionFlags & EXCEPTION_UNWIND
.text:0001117C                                         ; 判斷是異常處理過程還是展開過程
.text:00011180                 jnz     $LN31           ; 展開
.text:00011186                 mov     ecx, [ebp+ContextRecord] ; 異常處理過程
.text:00011189                 lea     edx, [ebp+ExceptionPointers]
.text:0001118C                 mov     [ebx-4], edx    ; ebx是第二個參數ExceptionRigstrationRecord,
.text:0001118C                                         ; [ebx-4]就是前文提到過的xpointers
.text:0001118F                 mov     ebx, [ebx+0Ch]  ; ebx = TryLevel
.text:00011192                 mov     [ebp+ExceptionPointers.ExceptionRecord], eax ; 將參數拷貝到自己的函數棧
.text:00011195                 mov     [ebp+ExceptionPointers.ContextRecord], ecx
.text:00011198                 cmp     ebx, 0FFFFFFFEh
.text:0001119B                 jz      short loc_111FC
.text:0001119D                 lea     ecx, [ecx+0]
.text:000111A0
.text:000111A0 loc_111A0:                              ; CODE XREF: __except_handler4+A0j
.text:000111A0                 lea     eax, [ebx+ebx*2] ; eax=ebx*3
.text:000111A3                 mov     ecx, [esi+eax*4+14h] ; 這里的eax*4加上上面的ebx*3,相當於是*12,
.text:000111A3                                         ; 為了跳過trylevel個scopetable_entry(大小為12個字節)
.text:000111A3                                         ; 然后+14h,為了過上文提到過的10h的一個坑,
.text:000111A3                                         ; 即ecx = scopetable[i].lpfnFilter
.text:000111A7                 lea     eax, [esi+eax*4+10h] ; eax = &scopetable[i]
.text:000111AB                 mov     [ebp+ScopeTableRecord], eax ; ScopeTableRecord存放的就是當前異常
.text:000111AB                                         ; 的ScopeTableEntry
.text:000111AE                 mov     eax, [eax]      ; eax = scopetable[i].previousTryLevel
.text:000111B0                 mov     [ebp+ExceptionRecord], eax
.text:000111B3                 test    ecx, ecx
.text:000111B5                 jz      short loc_111CB ; ecx =0,即lpfnHandler為NULL,則跳轉
.text:000111B7                 mov     edx, edi        ; edi = _ebp
.text:000111B9                 call    @_EH4_CallFilterFunc@8 ; 在函數里面 call ecx,即調用lpfnFilter
.text:000111BE                 mov     [ebp+Revalidate], 1 ; 表示是否執行過 lpfnFilter
.text:000111C2                 test    eax, eax        ; 檢驗lpfnHandler函數的返回值
.text:000111C4                 jl      short loc_11206 ; 如果是 EXCEPTION_CONTINUE_EXECUTION (-1) 就跳
.text:000111C6                 jg      short loc_1120F ; 如果是 EXCEPTION_EXECUTE_HANDLER (1) 就跳
.text:000111C8                 mov     eax, [ebp+ExceptionRecord] ; eax = scopetable[i].previousTryLevel
.text:000111CB
.text:000111CB loc_111CB:                              ; CODE XREF: __except_handler4+85j
.text:000111CB                 mov     ebx, eax
.text:000111CD                 cmp     eax, 0FFFFFFFEh ; cmp scopetable[i].previousTryLevel, TRYLEVEL_INVALID
.text:000111D0                 jnz     short loc_111A0 ; 不為TRYLEVEL_INVALID(-2),跳轉,尋找下一個
.text:000111D2                 cmp     [ebp+Revalidate], 0
.text:000111D6                 jz      short loc_111FC ; 沒有執行過 lpfnFilter,無需進行安全檢查
.text:000111D8
.text:000111D8 loc_111D8:                              ; CODE XREF: __except_handler4+DDj
.text:000111D8                                         ; __except_handler4+14Fj
.text:000111D8                 mov     eax, [esi]
.text:000111DA                 cmp     eax, 0FFFFFFFEh ; 根據 scopetable 空間的第一個DWORD值
.text:000111DA                                         ; 判斷是否需要做進一步的安全檢查
.text:000111DD                 jz      short loc_111EC
.text:000111DF                 mov     ecx, [esi+4]    ; 檢驗scopetable完整性(1)
.text:000111E2                 add     ecx, edi
.text:000111E4                 xor     ecx, [eax+edi]  ; cookie
.text:000111E7                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:000111EC
.text:000111EC loc_111EC:                              ; CODE XREF: __except_handler4+ADj
.text:000111EC                 mov     ecx, [esi+0Ch]  ; 檢驗scopetable完整性(2)
.text:000111EF                 mov     edx, [esi+8]
.text:000111F2                 add     ecx, edi
.text:000111F4                 xor     ecx, [edx+edi]  ; cookie
.text:000111F7                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:000111FC
.text:000111FC loc_111FC:                              ; CODE XREF: __except_handler4+6Bj
.text:000111FC                                         ; __except_handler4+A6j ...
.text:000111FC                 mov     eax, [ebp+Disposition] ; 函數返回EXCEPTION_EXCUTE_HANDLER (1)
.text:000111FF                 pop     edi
.text:00011200                 pop     esi
.text:00011201                 pop     ebx
.text:00011202                 mov     esp, ebp
.text:00011204                 pop     ebp
.text:00011205                 retn
.text:00011206 ; ---------------------------------------------------------------------------
.text:00011206
.text:00011206 loc_11206:                              ; CODE XREF: __except_handler4+94j
.text:00011206                 mov     [ebp+Disposition], 0 ; 函數返回EXECEPTION_CONTINUE_SEARCH (0)
.text:0001120D                 jmp     short loc_111D8
.text:0001120F ; ---------------------------------------------------------------------------
.text:0001120F
.text:0001120F loc_1120F:                              ; CODE XREF: __except_handler4+96j
.text:0001120F                 mov     ecx, [ebp+EstablisherFrame] ; 全局展開操作
.text:00011212                 call    @_EH4_GlobalUnwind@4 ; _EH4_GlobalUnwind(x)
.text:00011217                 mov     eax, [ebp+EstablisherFrame]
.text:0001121A                 cmp     [eax+0Ch], ebx
.text:0001121D                 jz      short loc_11231
.text:0001121F                 push    offset ___security_cookie
.text:00011224                 push    edi
.text:00011225                 mov     edx, ebx
.text:00011227                 mov     ecx, eax
.text:00011229                 call    @_EH4_LocalUnwind@16 ; _EH4_LocalUnwind(x,x,x,x)
.text:0001122E                 mov     eax, [ebp+EstablisherFrame]
.text:00011231
.text:00011231 loc_11231:                              ; CODE XREF: __except_handler4+EDj
.text:00011231                 mov     ecx, [ebp+ExceptionRecord]
.text:00011234                 mov     [eax+0Ch], ecx
.text:00011237                 mov     eax, [esi]
.text:00011239                 cmp     eax, 0FFFFFFFEh
.text:0001123C                 jz      short loc_1124B
.text:0001123E                 mov     ecx, [esi+4]
.text:00011241                 add     ecx, edi
.text:00011243                 xor     ecx, [eax+edi]  ; cookie
.text:00011246                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:0001124B
.text:0001124B loc_1124B:                              ; CODE XREF: __except_handler4+10Cj
.text:0001124B                 mov     ecx, [esi+0Ch]
.text:0001124E                 mov     edx, [esi+8]
.text:00011251                 add     ecx, edi
.text:00011253                 xor     ecx, [edx+edi]  ; cookie
.text:00011256                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:0001125B                 mov     eax, [ebp+ScopeTableRecord]
.text:0001125E                 mov     ecx, [eax+8]
.text:00011261                 mov     edx, edi
.text:00011263                 call    @_EH4_TransferToHandler@8 ; _EH4_TransferToHandler(x,x)
.text:00011268
.text:00011268 $LN31:                                  ; CODE XREF: __except_handler4+50j
.text:00011268                 mov     edx, 0FFFFFFFEh
.text:0001126D                 cmp     [ebx+0Ch], edx
.text:00011270                 jz      short loc_111FC
.text:00011272                 push    offset ___security_cookie
.text:00011277                 push    edi
.text:00011278                 mov     ecx, ebx
.text:0001127A                 call    @_EH4_LocalUnwind@16 ; _EH4_LocalUnwind(x,x,x,x)
.text:0001127F                 jmp     loc_111D8
.text:0001127F __except_handler4 endp

 在函數的最后有展開操作,先學習一下展開的概念

 我們假設一系列使用 SEH 的函數調用流程: 
  func1 -> func2 -> func3,然后在 func3 執行的過程中觸發了異常。

  看看分發異常流程 RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException
  RtlDispatchException 會遍歷異常鏈表,對每個 EXCEPTION_REGISTRATION 都調用 RtlpExecuteHandlerForException。
  RtlpExecuteHandlerForException 會調用 EXCEPTION_REGISTRATION::handler,也就是 PassThrough!_except_handler4。如咱們上面分析,該函數內部遍歷 EXCEPTION_REGISTRATION::scopetable,如果遇到有 scopetable_entry::lpfnFilter 返回 EXCEPTION_EXECUTE_HANDLER,那么 scopetable_entry::lpfnHandler 就會被調用,來處理該異常。
  因為 lpfnHandler 不會返回到 PassThrough!_except_handler4,於是執行完 lpfnHandler 后,就會從 lpfnHandler 之后的代碼繼續執行下去。也就是說,假設 func3 中觸發了一個異常,該異常被 func1 中的 __except 處理塊處理了,那 __except 處理塊執行完畢后,就從其后的指令繼續執行下去,即異常處理完畢后,接着執行的就是 func1 的代碼。不會再回到 func2 或者 func3,這樣就有個問題,func2 和 func3 中占用的資源怎么辦?這些資源比如申請的內存是不會自動釋放的,豈不是會有資源泄漏問題?

  這就需要用到“展開”了。
  所謂“展開”就是進行清理,這里的清理主要包含動態分配的資源的清理,棧空間是由 func1 的“mov esp,ebp” 這類操作順手清理 

 那這個展開工作由誰來完成呢?由 func1 來完成肯定不合適,畢竟 func2 和 func3 有沒有申請資源、申請了哪些資源,func1 無從得知。於是這個展開工作還得要交給 func2 和 func3 自己來完成。

 展開分為兩種:“全局展開”和“局部展開”。
  全局展開是指針對異常鏈表中的某一段,局部展開針對指定 EXCEPTION_REGISTRATION。用上面的例子來講,局部展開就是針對 func3 或 func2 (某一個函數)內部進行清理,全局展開就是 func2 和 func3 的局部清理的總和。再歸納一下,局部展開是指具體某一函數內部的清理,而全局展開是指,從異常觸發點(func3)到異常處理點(func1)之間所有函數(包含異常觸發點 func3)的局部清理的總和。

 下面來看看wrk中RtlUwind的源代碼:

VOID
RtlUnwind (
    IN PVOID TargetFrame OPTIONAL,
    IN PVOID TargetIp OPTIONAL,
    IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
    IN PVOID ReturnValue
    )

/*++

Routine Description:

    This function initiates an unwind of procedure call frames. The machine
    state at the time of the call to unwind is captured in a context record
    and the unwinding flag is set in the exception flags of the exception
    record. If the TargetFrame parameter is not specified, then the exit unwind
    flag is also set in the exception flags of the exception record. A backward
    walk through the procedure call frames is then performed to find the target
    of the unwind operation.

    N.B.    The captured context passed to unwinding handlers will not be
            a  completely accurate context set for the 386.  This is because
            there isn't a standard stack frame in which registers are stored.

            Only the integer registers are affected.  The segment and
            control registers (ebp, esp) will have correct values for
            the flat 32 bit environment.

    N.B.    If you change the number of arguments, make sure you change the
            adjustment of ESP after the call to RtlpCaptureContext (for
            STDCALL calling convention)

Arguments:

    TargetFrame - Supplies an optional pointer to the call frame that is the
        target of the unwind. If this parameter is not specified, then an exit
        unwind is performed.

    TargetIp - Supplies an optional instruction address that specifies the
        continuation address of the unwind. This address is ignored if the
        target frame parameter is not specified.

    ExceptionRecord - Supplies an optional pointer to an exception record.

    ReturnValue - Supplies a value that is to be placed in the integer
        function return register just before continuing execution.

Return Value:

    None.

--*/

{
    PCONTEXT ContextRecord;
    CONTEXT ContextRecord1;
    DISPATCHER_CONTEXT DispatcherContext;
    EXCEPTION_DISPOSITION Disposition;
    PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
    PEXCEPTION_REGISTRATION_RECORD PriorPointer;
    ULONG HighAddress;
    ULONG HighLimit;
    ULONG LowLimit;
    EXCEPTION_RECORD ExceptionRecord1;
    EXCEPTION_RECORD ExceptionRecord2;

    //
    // Get current stack limits.
    //

    RtlpGetStackLimits(&LowLimit, &HighLimit);

    //
    // If an exception record is not specified, then build a local exception
    // record for use in calling exception handlers during the unwind operation.
    //

    if (ARGUMENT_PRESENT(ExceptionRecord) == FALSE) {
        ExceptionRecord = &ExceptionRecord1;
        ExceptionRecord1.ExceptionCode = STATUS_UNWIND;
        ExceptionRecord1.ExceptionFlags = 0;
        ExceptionRecord1.ExceptionRecord = NULL;
        ExceptionRecord1.ExceptionAddress = _ReturnAddress();  //返回地址
        ExceptionRecord1.NumberParameters = 0;
    }

    //
    // If the target frame of the unwind is specified, then set EXCEPTION_UNWINDING
    // flag in the exception flags. Otherwise set both EXCEPTION_EXIT_UNWIND and
    // EXCEPTION_UNWINDING flags in the exception flags.
    //

    if (ARGUMENT_PRESENT(TargetFrame) == TRUE) {
        ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING;
    } else {
        ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING |
                                                        EXCEPTION_EXIT_UNWIND);
    }

    //
    // Capture the context.
    //

    ContextRecord = &ContextRecord1;
    ContextRecord1.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS;
    RtlpCaptureContext(ContextRecord);

    //
    // Adjust captured context to pop our arguments off the stack
    //
    ContextRecord->Esp += sizeof(TargetFrame) +
                          sizeof(TargetIp)    +
                          sizeof(ExceptionRecord) +
                          sizeof(ReturnValue);
    ContextRecord->Eax = (ULONG)ReturnValue;

    //
    // Scan backward through the call frame hierarchy, calling exception
    // handlers as they are encountered, until the target frame of the unwind
    // is reached.
    //

    RegistrationPointer = RtlpGetRegistrationHead();//異常鏈表頭
    while (RegistrationPointer != EXCEPTION_CHAIN_END) {

        //
        // If this is the target of the unwind, then continue execution
        // by calling the continue system service.
        //
        //說明展開完畢
        if ((ULONG)RegistrationPointer == (ULONG)TargetFrame) {
            ZwContinue(ContextRecord, FALSE);

        //
        // If the target frame is lower in the stack than the current frame,
        // then raise STATUS_INVALID_UNWIND exception.
        //

        } else if ( (ARGUMENT_PRESENT(TargetFrame) == TRUE) &&
                    ((ULONG)TargetFrame < (ULONG)RegistrationPointer) ) {
            //超出了異常鏈表的查找范圍
            ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord2.ExceptionRecord = ExceptionRecord;
            ExceptionRecord2.NumberParameters = 0;
            RtlRaiseException(&ExceptionRecord2);
        }

        //
        // If the call frame is not within the specified stack limits or the
        // call frame is unaligned, then raise the exception STATUS_BAD_STACK.
        // Else restore the state from the specified frame to the context
        // record.
        //

        HighAddress = (ULONG)RegistrationPointer +
            sizeof(EXCEPTION_REGISTRATION_RECORD);
        //低於線程棧底或者高於線程棧頂,進行錯誤處理
        if ( ((ULONG)RegistrationPointer < LowLimit) ||
             (HighAddress > HighLimit) ||
             (((ULONG)RegistrationPointer & 0x3) != 0) 
           ) {

            //
            // Allow for the possibility that the problem occured on the
            // DPC stack.
            //

            ULONG TestAddress = (ULONG)RegistrationPointer;
            //& 0x3 檢查是否4字節對齊 ,IRQL級別,如果當前正在執行dpc操作,restart
            if (((TestAddress & 0x3) == 0) &&
                KeGetCurrentIrql() >= DISPATCH_LEVEL) {

                PKPRCB Prcb = KeGetCurrentPrcb();
                ULONG DpcStack = (ULONG)Prcb->DpcStack;

                if ((Prcb->DpcRoutineActive) &&
                    (HighAddress <= DpcStack) &&
                    (TestAddress >= DpcStack - KERNEL_STACK_SIZE)) {

                    //
                    // This error occured on the DPC stack, switch
                    // stack limits to the DPC stack and restart
                    // the loop.
                    //

                    HighLimit = DpcStack;
                    LowLimit = DpcStack - KERNEL_STACK_SIZE;
                    continue;
                }
            }

            ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK;
            ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord2.ExceptionRecord = ExceptionRecord;
            ExceptionRecord2.NumberParameters = 0;
            RtlRaiseException(&ExceptionRecord2);
        } else { //一般情況的展開

            //
            // The handler must be executed by calling another routine
            // that is written in assembler. This is required because
            // up level addressing of the handler information is required
            // when a collided unwind is encountered.
            //
            //在內部調用Handler處理函數
            Disposition = RtlpExecuteHandlerForUnwind(
                ExceptionRecord,
                (PVOID)RegistrationPointer,
                ContextRecord,
                (PVOID)&DispatcherContext,
                RegistrationPointer->Handler);

            //
            // Case on the handler disposition.
            //
            //檢查Handler的返回值
            switch (Disposition) {

                //
                // The disposition is to continue the search. Get next
                // frame address and continue the search.
                //

            case ExceptionContinueSearch :
                break;

                //
                // The disposition is colided unwind. Maximize the target
                // of the unwind and change the context record pointer.
                //

            case ExceptionCollidedUnwind :

                //
                // Pick up the registration pointer that was active at
                // the time of the unwind, and simply continue.
                //

                RegistrationPointer = DispatcherContext.RegistrationPointer;
                break;


                //
                // All other disposition values are invalid. Raise
                // invalid disposition exception.
                //

            default :
                ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
                ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                ExceptionRecord2.NumberParameters = 0;
                RtlRaiseException(&ExceptionRecord2);
                break;
            }

            //
            // Step to next registration record
            //

            PriorPointer = RegistrationPointer;
            RegistrationPointer = RegistrationPointer->Next;

            //
            // Unlink the unwind handler, since it's been called.
            //

            RtlpUnlinkHandler(PriorPointer);

            //
            // If chain goes in wrong direction or loops, raise an
            // exception.
            //

        }
    }

    if (TargetFrame == EXCEPTION_CHAIN_END) {

        //
        //  Caller simply wants to unwind all exception records.
        //  This differs from an exit_unwind in that no "exit" is desired.
        //  Do a normal continue, since we've effectively found the
        //  "target" the caller wanted.
        //

        ZwContinue(ContextRecord, FALSE);

    } else {

        //
        //  Either (1) a real exit unwind was performed, or (2) the
        //  specified TargetFrame is not present in the exception handler
        //  list.  In either case, give debugger and subsystem a chance
        //  to see the unwind.
        //

        ZwRaiseException(ExceptionRecord, ContextRecord, FALSE);

    }
    return;
}
wrk RtlUnwind

 

代碼不長,主要功能也不復雜:從異常鏈表頭開始遍歷,一直遍歷到指定 EXCEPTION_REGISTRATION_RECORD,對每個遍歷到的 EXCEPTION_REGISTRATION_RECORD,執行 RtlpExecuteHandlerForUnwind 進行局部展開。 

匯編代碼如下,就不寫注釋了,直接看wrk源碼更清晰

.text:00475C9B                   ; int __stdcall RtlUnwind(int, int, PEXCEPTION_RECORD ExceptionRecord, int)
.text:00475C9B                   public _RtlUnwind@16
.text:00475C9B                   _RtlUnwind@16 proc near       ; CODE XREF: __global_unwind2+13p
.text:00475C9B                                                 ; _EH4_GlobalUnwind(x)+10p
.text:00475C9B
.text:00475C9B                   var_384= dword ptr -384h
.text:00475C9B                   var_380= dword ptr -380h
.text:00475C9B                   var_37C= dword ptr -37Ch
.text:00475C9B                   var_378= EXCEPTION_RECORD ptr -378h
.text:00475C9B                   var_328= dword ptr -328h
.text:00475C9B                   var_324= dword ptr -324h
.text:00475C9B                   var_320= dword ptr -320h
.text:00475C9B                   var_31C= dword ptr -31Ch
.text:00475C9B                   var_318= dword ptr -318h
.text:00475C9B                   Context= CONTEXT ptr -2D8h
.text:00475C9B                   var_4= dword ptr -4
.text:00475C9B                   arg_0= dword ptr  8
.text:00475C9B                   ExceptionRecord= dword ptr  10h
.text:00475C9B                   arg_C= dword ptr  14h
.text:00475C9B
.text:00475C9B 8B FF             mov     edi, edi
.text:00475C9D 55                push    ebp
.text:00475C9E 8B EC             mov     ebp, esp
.text:00475CA0 83 E4 F8          and     esp, 0FFFFFFF8h
.text:00475CA3 81 EC 84 03 00 00 sub     esp, 384h
.text:00475CA9 A1 44 2A 52 00    mov     eax, ___security_cookie
.text:00475CAE 33 C4             xor     eax, esp
.text:00475CB0 89 84 24 80 03 00+mov     [esp+384h+var_4], eax
.text:00475CB7 53                push    ebx
.text:00475CB8 56                push    esi
.text:00475CB9 57                push    edi
.text:00475CBA 8B 7D 10          mov     edi, [ebp+ExceptionRecord]
.text:00475CBD 8D 44 24 10       lea     eax, [esp+390h+var_380]
.text:00475CC1 50                push    eax
.text:00475CC2 8D 74 24 18       lea     esi, [esp+394h+var_37C]
.text:00475CC6 E8 80 3D 01 00    call    _RtlpGetStackLimits@8 ; RtlpGetStackLimits(x,x)
.text:00475CCB 84 C0             test    al, al
.text:00475CCD 75 0A             jnz     short loc_475CD9
.text:00475CCF 68 28 00 00 C0    push    0C0000028h            ; Status
.text:00475CD4 E8 9B D0 FB FF    call    _RtlRaiseStatus@4     ; RtlRaiseStatus(x)
.text:00475CD9                   ; ---------------------------------------------------------------------------
.text:00475CD9
.text:00475CD9                   loc_475CD9:                   ; CODE XREF: RtlUnwind(x,x,x,x)+32j
.text:00475CD9 33 F6             xor     esi, esi
.text:00475CDB 3B FE             cmp     edi, esi
.text:00475CDD 75 1F             jnz     short loc_475CFE
.text:00475CDF 8B 45 04          mov     eax, [ebp+4]
.text:00475CE2 8D 7C 24 68       lea     edi, [esp+390h+var_328]
.text:00475CE6 C7 44 24 68 27 00+mov     [esp+390h+var_328], 0C0000027h
.text:00475CEE 89 74 24 6C       mov     [esp+390h+var_324], esi
.text:00475CF2 89 74 24 70       mov     [esp+390h+var_320], esi
.text:00475CF6 89 44 24 74       mov     [esp+390h+var_31C], eax
.text:00475CFA 89 74 24 78       mov     [esp+390h+var_318], esi
.text:00475CFE
.text:00475CFE                   loc_475CFE:                   ; CODE XREF: RtlUnwind(x,x,x,x)+42j
.text:00475CFE 39 75 08          cmp     [ebp+arg_0], esi
.text:00475D01 74 06             jz      short loc_475D09
.text:00475D03 83 4F 04 02       or      dword ptr [edi+4], 2
.text:00475D07 EB 04             jmp     short loc_475D0D
.text:00475D09                   ; ---------------------------------------------------------------------------
.text:00475D09
.text:00475D09                   loc_475D09:                   ; CODE XREF: RtlUnwind(x,x,x,x)+66j
.text:00475D09 83 4F 04 06       or      dword ptr [edi+4], 6
.text:00475D0D
.text:00475D0D                   loc_475D0D:                   ; CODE XREF: RtlUnwind(x,x,x,x)+6Cj
.text:00475D0D 8D 84 24 B8 00 00+lea     eax, [esp+390h+Context]
.text:00475D14 50                push    eax
.text:00475D15 C7 84 24 BC 00 00+mov     [esp+394h+Context.ContextFlags], 10007h
.text:00475D20 E8 3F 01 FE FF    call    _RtlpCaptureContext@4 ; RtlpCaptureContext(x)
.text:00475D25 8B 45 14          mov     eax, [ebp+arg_C]
.text:00475D28 83 84 24 7C 01 00+add     [esp+390h+Context._Esp], 10h
.text:00475D30 89 84 24 68 01 00+mov     [esp+390h+Context._Eax], eax
.text:00475D37 E8 BC 01 FE FF    call    _RtlpGetRegistrationHead@0 ; RtlpGetRegistrationHead()
.text:00475D3C 8B D8             mov     ebx, eax
.text:00475D3E 83 FB FF          cmp     ebx, 0FFFFFFFFh
.text:00475D41 0F 84 DC 00 00 00 jz      loc_475E23
.text:00475D47 33 F6             xor     esi, esi
.text:00475D49 46                inc     esi
.text:00475D4A
.text:00475D4A                   loc_475D4A:                   ; CODE XREF: RtlUnwind(x,x,x,x)+180j
.text:00475D4A 3B 5D 08          cmp     ebx, [ebp+arg_0]
.text:00475D4D 75 11             jnz     short loc_475D60
.text:00475D4F 6A 00             push    0                     ; TestAlert
.text:00475D51 8D 84 24 BC 00 00+lea     eax, [esp+394h+Context]
.text:00475D58 50                push    eax                   ; Context
.text:00475D59 E8 2A D5 FB FF    call    _ZwContinue@8         ; ZwContinue(x,x)
.text:00475D5E EB 2A             jmp     short loc_475D8A
.text:00475D60                   ; ---------------------------------------------------------------------------
.text:00475D60
.text:00475D60                   loc_475D60:                   ; CODE XREF: RtlUnwind(x,x,x,x)+B2j
.text:00475D60 83 7D 08 00       cmp     [ebp+arg_0], 0
.text:00475D64 74 24             jz      short loc_475D8A
.text:00475D66 39 5D 08          cmp     [ebp+arg_0], ebx
.text:00475D69 73 1F             jnb     short loc_475D8A
.text:00475D6B 83 64 24 28 00    and     [esp+390h+var_378.NumberParameters], 0
.text:00475D70 8D 44 24 18       lea     eax, [esp+390h+var_378]
.text:00475D74 50                push    eax                   ; ExceptionRecord
.text:00475D75 C7 44 24 1C 29 00+mov     [esp+394h+var_378.ExceptionCode], 0C0000029h
.text:00475D7D 89 74 24 20       mov     [esp+394h+var_378.ExceptionFlags], esi
.text:00475D81 89 7C 24 24       mov     [esp+394h+var_378.ExceptionRecord], edi
.text:00475D85 E8 96 CF FB FF    call    _RtlRaiseException@4  ; RtlRaiseException(x)
.text:00475D8A                   ; ---------------------------------------------------------------------------
.text:00475D8A
.text:00475D8A                   loc_475D8A:                   ; CODE XREF: RtlUnwind(x,x,x,x)+C3j
.text:00475D8A                                                 ; RtlUnwind(x,x,x,x)+C9j
.text:00475D8A                                                 ; RtlUnwind(x,x,x,x)+CEj
.text:00475D8A 3B 5C 24 14       cmp     ebx, [esp+390h+var_37C]
.text:00475D8E 72 69             jb      short loc_475DF9
.text:00475D90 8D 43 08          lea     eax, [ebx+8]
.text:00475D93 3B 44 24 10       cmp     eax, [esp+390h+var_380]
.text:00475D97 77 60             ja      short loc_475DF9
.text:00475D99 F6 C3 03          test    bl, 3
.text:00475D9C 75 5B             jnz     short loc_475DF9
.text:00475D9E 8B 43 04          mov     eax, [ebx+4]
.text:00475DA1 E8 ED A4 FF FF    call    _RtlIsValidHandler@8  ; RtlIsValidHandler(x,x)
.text:00475DA6 84 C0             test    al, al
.text:00475DA8 74 4F             jz      short loc_475DF9
.text:00475DAA FF 73 04          push    dword ptr [ebx+4]
.text:00475DAD 8D 44 24 10       lea     eax, [esp+394h+var_384]
.text:00475DB1 50                push    eax
.text:00475DB2 8D 84 24 C0 00 00+lea     eax, [esp+398h+Context]
.text:00475DB9 50                push    eax
.text:00475DBA 53                push    ebx
.text:00475DBB 57                push    edi
.text:00475DBC E8 A7 FF FD FF    call    _RtlpExecuteHandlerForUnwind@20 ; RtlpExecuteHandlerForUnwind(x,x,x,x,x)
.text:00475DC1 48                dec     eax
.text:00475DC2 74 29             jz      short loc_475DED
.text:00475DC4 48                dec     eax
.text:00475DC5 48                dec     eax
.text:00475DC6 74 21             jz      short loc_475DE9
.text:00475DC8 83 64 24 28 00    and     [esp+390h+var_378.NumberParameters], 0
.text:00475DCD 8D 44 24 18       lea     eax, [esp+390h+var_378]
.text:00475DD1 50                push    eax                   ; ExceptionRecord
.text:00475DD2 C7 44 24 1C 26 00+mov     [esp+394h+var_378.ExceptionCode], 0C0000026h
.text:00475DDA 89 74 24 20       mov     [esp+394h+var_378.ExceptionFlags], esi
.text:00475DDE 89 7C 24 24       mov     [esp+394h+var_378.ExceptionRecord], edi
.text:00475DE2 E8 39 CF FB FF    call    _RtlRaiseException@4  ; RtlRaiseException(x)
.text:00475DE2                   ; ---------------------------------------------------------------------------
.text:00475DE7 EB                db 0EBh ; 
.text:00475DE8 04                db    4
.text:00475DE9                   ; ---------------------------------------------------------------------------
.text:00475DE9
.text:00475DE9                   loc_475DE9:                   ; CODE XREF: RtlUnwind(x,x,x,x)+12Bj
.text:00475DE9 8B 5C 24 0C       mov     ebx, [esp+390h+var_384]
.text:00475DED
.text:00475DED                   loc_475DED:                   ; CODE XREF: RtlUnwind(x,x,x,x)+127j
.text:00475DED 8B C3             mov     eax, ebx
.text:00475DEF 8B 1B             mov     ebx, [ebx]
.text:00475DF1 50                push    eax
.text:00475DF2 E8 2D 00 FE FF    call    _RtlpUnlinkHandler@4  ; RtlpUnlinkHandler(x)
.text:00475DF7 EB 1F             jmp     short loc_475E18
.text:00475DF9                   ; ---------------------------------------------------------------------------
.text:00475DF9
.text:00475DF9                   loc_475DF9:                   ; CODE XREF: RtlUnwind(x,x,x,x)+F3j
.text:00475DF9                                                 ; RtlUnwind(x,x,x,x)+FCj
.text:00475DF9                                                 ; RtlUnwind(x,x,x,x)+101j
.text:00475DF9                                                 ; RtlUnwind(x,x,x,x)+10Dj
.text:00475DF9 83 64 24 28 00    and     [esp+390h+var_378.NumberParameters], 0
.text:00475DFE 8D 44 24 18       lea     eax, [esp+390h+var_378]
.text:00475E02 50                push    eax                   ; ExceptionRecord
.text:00475E03 C7 44 24 1C 28 00+mov     [esp+394h+var_378.ExceptionCode], 0C0000028h
.text:00475E0B 89 74 24 20       mov     [esp+394h+var_378.ExceptionFlags], esi
.text:00475E0F 89 7C 24 24       mov     [esp+394h+var_378.ExceptionRecord], edi
.text:00475E13 E8 08 CF FB FF    call    _RtlRaiseException@4  ; RtlRaiseException(x)
.text:00475E18                   ; ---------------------------------------------------------------------------
.text:00475E18
.text:00475E18                   loc_475E18:                   ; CODE XREF: RtlUnwind(x,x,x,x)+15Cj
.text:00475E18 83 FB FF          cmp     ebx, 0FFFFFFFFh
.text:00475E1B 0F 85 29 FF FF FF jnz     loc_475D4A
.text:00475E21 33 F6             xor     esi, esi
.text:00475E23
.text:00475E23                   loc_475E23:                   ; CODE XREF: RtlUnwind(x,x,x,x)+A6j
.text:00475E23 83 7D 08 FF       cmp     [ebp+arg_0], 0FFFFFFFFh
.text:00475E27 56                push    esi                   ; SearchFrames
.text:00475E28 8D 84 24 BC 00 00+lea     eax, [esp+394h+Context]
.text:00475E2F 50                push    eax                   ; Context
.text:00475E30 75 07             jnz     short loc_475E39
.text:00475E32 E8 51 D4 FB FF    call    _ZwContinue@8         ; ZwContinue(x,x)
.text:00475E37 EB 06             jmp     short loc_475E3F
.text:00475E39                   ; ---------------------------------------------------------------------------
.text:00475E39
.text:00475E39                   loc_475E39:                   ; CODE XREF: RtlUnwind(x,x,x,x)+195j
.text:00475E39 57                push    edi                   ; ExceptionRecord
.text:00475E3A E8 C5 E4 FB FF    call    _ZwRaiseException@12  ; ZwRaiseException(x,x,x)
.text:00475E3F
.text:00475E3F                   loc_475E3F:                   ; CODE XREF: RtlUnwind(x,x,x,x)+19Cj
.text:00475E3F 8B 8C 24 8C 03 00+mov     ecx, [esp+390h+var_4]
.text:00475E46 5F                pop     edi
.text:00475E47 5E                pop     esi
.text:00475E48 5B                pop     ebx
.text:00475E49 33 CC             xor     ecx, esp
.text:00475E4B E8 88 22 FE FF    call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:00475E50 8B E5             mov     esp, ebp
.text:00475E52 5D                pop     ebp
.text:00475E53 C2 10 00          retn    10h
.text:00475E53                   _RtlUnwind@16 endp
asm RtlUnwind

再看_except_handler4中的局部展開

.text:000113C8 ; int __fastcall _EH4_LocalUnwind(_EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, int TryLevel, int FrameEBP, int *CookiePointer)
.text:000113C8 @_EH4_LocalUnwind@16 proc near          ; CODE XREF: __except_handler4+F9p
.text:000113C8                                         ; __except_handler4+14Ap
.text:000113C8
.text:000113C8 FrameEBP        = dword ptr  4
.text:000113C8 CookiePointer   = dword ptr  8
.text:000113C8
.text:000113C8                 push    ebp
.text:000113C9                 mov     ebp, [esp+4+FrameEBP] ; 切換ebp准備局部展開
.text:000113CD                 push    edx             ; TryLevel
.text:000113CE                 push    ecx             ; EstablisherFrame
.text:000113CF                 push    [esp+0Ch+CookiePointer] ; CookiePointer
.text:000113D3                 call    __local_unwind4
.text:000113D8                 add     esp, 0Ch
.text:000113DB                 pop     ebp
.text:000113DC                 retn    8
.text:000113DC @_EH4_LocalUnwind@16 endp

 

 

text:0001128C ; int __cdecl _local_unwind4(int *CookiePointer, _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, int TryLevel)
.text:0001128C __local_unwind4 proc near               ; CODE XREF: _unwind_handler4+2Dp
.text:0001128C                                         ; _seh_longjmp_unwind4(x)+10p ...
.text:0001128C
.text:0001128C var_20          = dword ptr -20h
.text:0001128C CookiePointer   = dword ptr  4
.text:0001128C EstablisherFrame= dword ptr  8
.text:0001128C TryLevel        = dword ptr  0Ch
.text:0001128C
.text:0001128C                 push    ebx
.text:0001128D                 push    esi
.text:0001128E                 push    edi
.text:0001128F                 mov     edx, [esp+0Ch+CookiePointer]
.text:00011293                 mov     eax, [esp+0Ch+EstablisherFrame]
.text:00011297                 mov     ecx, [esp+0Ch+TryLevel]
.text:0001129B                 push    ebp
.text:0001129C                 push    edx
.text:0001129D                 push    eax
.text:0001129E                 push    ecx
.text:0001129F                 push    ecx
.text:000112A0                 push    offset _unwind_handler4 ; Handler,局部展開發生異常時調用
.text:000112A5                 push    large dword ptr fs:0
.text:000112AC                 mov     eax, ___security_cookie
.text:000112B1                 xor     eax, esp
.text:000112B3                 mov     [esp+28h+var_20], eax
.text:000112B7                 mov     large fs:0, esp ; 安裝新的SEH
.text:000112BE
.text:000112BE _lu_top:                                ; CODE XREF: __local_unwind4+64j
.text:000112BE                                         ; __local_unwind4+80j
.text:000112BE                 mov     eax, [esp+28h+EstablisherFrame]
.text:000112C2                 mov     ebx, [eax+8]    ; 獲取ScopeTable
.text:000112C5                 mov     ecx, [esp+28h+CookiePointer]
.text:000112C9                 xor     ebx, [ecx]      ; 解密scopetable
.text:000112CB                 mov     esi, [eax+0Ch]  ; 獲取TryLevel
.text:000112CE                 cmp     esi, 0FFFFFFFEh ; 判斷是否遍歷完畢
.text:000112D1                 jz      short _lu_done
.text:000112D3                 mov     edx, [esp+28h+TryLevel]
.text:000112D7                 cmp     edx, 0FFFFFFFEh
.text:000112DA                 jz      short loc_112E0
.text:000112DC                 cmp     esi, edx        ; 判斷當前__try是否在EXCEPTION_EXECUTE_HANDLER的__try語句里層
.text:000112DE                 jbe     short _lu_done
.text:000112E0
.text:000112E0 loc_112E0:                              ; CODE XREF: __local_unwind4+4Ej
.text:000112E0                 lea     esi, [esi+esi*2]
.text:000112E3                 lea     ebx, [ebx+esi*4+10h] ; 和_except_handler4中作用一樣,不過這里是+0x10,取scopetable
.text:000112E7                 mov     ecx, [ebx]
.text:000112E9                 mov     [eax+0Ch], ecx  ; 使當前異常幀指向上一個__try語句,也就是移除當前異常幀指向的
.text:000112EC                 cmp     dword ptr [ebx+4], 0
.text:000112F0                 jnz     short _lu_top
.text:000112F2                 push    101h
.text:000112F7                 mov     eax, [ebx+8]
.text:000112FA                 call    __NLG_Notify
.text:000112FF                 mov     ecx, 1
.text:00011304                 mov     eax, [ebx+8]
.text:00011307                 call    __NLG_Call      ;  進入__finally塊處理
.text:0001130C                 jmp     short _lu_top   ; 遍歷上一個__try/__except(或__finally)
.text:0001130E ; ---------------------------------------------------------------------------
.text:0001130E
.text:0001130E _lu_done:                               ; CODE XREF: __local_unwind4+45j
.text:0001130E                                         ; __local_unwind4+52j
.text:0001130E                 pop     large dword ptr fs:0
.text:00011315                 add     esp, 18h
.text:00011318                 pop     edi
.text:00011319                 pop     esi
.text:0001131A                 pop     ebx
.text:0001131B                 retn
.text:0001131B __local_unwind4 endp

 

 

看到了有人寫出了C語言代碼,直接就抄過來了

/** 
 * 
 * 操作系統原始的SEH異常幀結構     
 * struct _EXCEPTION_REGISTRATION_RECORD{     
 *      struct _EXCEPTION_REGISTRATION_RECORD *Next;     
 *      _except_handler Handler;      
 * } 
 * 
 * SEH異常處理函數原型 
 * EXCEPTION_DISPOSITION (__cdecl *PEXCEPTION_ROUTINE)( 
 *          struct _EXCEPTION_RECORD *_ExceptionRecord,     
 *          void * _EstablisherFrame,     
 *          struct _CONTEXT *_ContextRecord,     
 *          void * _DispatcherContext     
 * ); 
 * 
 * C/C++編譯器擴展SEH的異常幀結構:       
 * [ebp-18] ESP       
 * [ebp-14] PEXCEPTION_POINTERS xpointers;          
 *          struct _EXCEPTION_REGISTRATION{          
 * [ebp-10]      struct _EXCEPTION_REGISTRATION *Prev;          
 * [ebp-0C]      PEXCEPTION_ROUTINE Handler;          
 * [ebp-08]      struct _EH4_SCOPETABLE *ScopeTable;          
 * [ebp-04]      int TryLevel;          
 * [ebp-00]      int _Ebp;          
 *          }; 
 * 
 * C/C++運行庫使用的SCOPE TABLE結構   
 * struct _EH4_SCOPETABLE {   
 *      DWORD GSCookieOffset;   
 *      DWORD GSCookieXOROffset;   
 *      DWORD EHCookieOffset;   
 *      DWORD EHCookieXOROffset;   
 *      struct _EH4_SCOPETABLE_RECORD ScopeRecord;   
 * };   
 *   
 * C/C++運行庫使用的SCOPE TABLE RECORD結構   
 * struct _EH4_SCOPETABLE_RECORD {   
 *      DWORD EnclosingLevel;       //上一層__try塊   
 *      PVOID FilterFunc;           //過濾表達式   
 *      union   
 *      {   
 *          PVOID HandlerAddress;   //__except塊代碼   
 *          PVOID FinallyFunc;      //__finally塊代碼   
 *      };   
 * }; 
 *   
 * 參數說明: 
 * CookiePointer    - 安全碼所在地址,用於解密異常幀的ScopeTable. 
 * EstablisherFrame - 當前異常幀結構 
 * TryLevel         - __try/__except(EXCEPTION_EXECUTE_HANDLER)所在的__try 
 * 
**/  
int __cdecl _local_unwind4(int *CookiePointer,   
                    _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame,   
                    int TryLevel  
            )  
{  
    //安裝SEH,_local_unwind4局部展開發生異常時調用  
    __asm push _unwind_handler4  
    __asm push dword ptr fs:[0]  
    __asm mov fs:[0],esp  
      
    //解密ScopeTable  
    struct _EH4_SCOPETABLE * pScopeTable = EstablisherFrame->ScopeTable ^ (*CookiePointer);  
    struct _EH4_SCOPETABLE_RECORD * pScopeRecord = &pScopeTable->ScopeRecord;  
      
    //當前異常幀不存在__try塊,退出!  
    while(EstablisherFrame->TryLevel != 0xFFFFFFFE)  
    {     
        //越里層的__try其TryLevel值越高,捕獲異常的__try通常在引發異常__try的外層  
        //這里判斷當前異常幀指向的__try的TryLevel值是否正常.  
        if(EstablisherFrame->TryLevel!=0xFFFFFFFE && EstablisherFrame->TryLevel <= TryLevel) break;          
      
        //移除當前__try/__except(__finally)信息,使其指向上一層__try塊  
        EstablisherFrame->TryLevel = pScopeRecord->EnclosingLevel;  
          
        //__except過濾表達式存在,也就是__finally塊不存在,向上一層__try/__except(__finally)遍歷  
        if(pScopeRecord->FilterFunc) continue;  
          
        //作用未知!  
        __NLG_Notify(101);  
          
        //進入__finally塊處理  
        pScopeRecord->FinallyFunc();  
    }  
      
    //恢復SEH  
    __asm pop dword ptr fs:[0]  
}  

 

 

 

到這里概要流程就講完了。在處理異常和展開過程中多處涉及到遍歷操作,咱們來總結一下這些遍歷操作。
  1. 在異常處理過程中,每個被"卷入是非"的異常都至少會遍歷異常鏈表兩次(如果發生嵌套異常,比如在展開過程中
      EXCEPTION_REGISTRATION_RECORD::Handler 又觸發異常,則會遍歷更多次。不過這也可以算作是一個新異常了。看如何理解。)。
      一次是在 RtlDispatchException 中,遍歷的目的是找到願意處理該異常的 _EXCEPTION_REGISTRATION_RECORD。
      另一次是在展開過程中、RtlUnwind 函數內,遍歷的目錄是為了對每個遍歷到的 EXCEPTION_REGISTRATION_RECORD 進行局部展開。
  2. 同樣的,每個被"卷入是非"的異常的 scopetable 也會被遍歷至少兩次,
      一次是在 modulename!_except_handler? 中,遍歷目的也是找到願意處理該異常的 scopetable_entry。
      另一次是在展開過程中、_local_unwind4 函數內,遍歷的目的是找到所有指定范圍內的 scopetable_entry::lpfnFilter 為 NULL 的 scopetable_entry,調用它們的 lpfnHandler (即 __finally 處理塊)。

 


免責聲明!

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



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