Windows调试2.异常产生详细流程

Posted ltyandy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows调试2.异常产生详细流程相关的知识,希望对你有一定的参考价值。

程序设置了int3断点函数

CPU获取后,根据IDT表中的int3的处理函数

主要工作大概是

填充 _KTRAP_FRAME 结构体,

保存的是异常发生时的寄存器环境,

因为在异常处理完毕之后,需要返回产生异常的地方继续执行

然后把异常给CommonDispatchException

 

//总结如下几个顺序
__asm int 3;
?
    -> IDT[3]: _KiTrap03
        (填充一个陷阱帧,用于继续执行代码)
?
        -> CommonDisapathException(异常地址, 异常的类型, 异常的参数个数)
            填充了一个 ExceptionRecord 结构
?
            -> KiDispatchException(先前模式,是否是第一次分发,陷阱桢, 异常结构, 0)

 

IDT 表中 int 3 的处理函数

; IDT 表中 int 3 的处理函数
_KiTrap03       proc
    ; 填充 _KTRAP_FRAME 结构体,保存的是异常发生时的寄存器环境,
    ; 因为在异常处理完毕之后,需要返回产生异常的地方继续执行
    push    0                       
    ENTER_TRAP      kit3_a, kit3_t
?
    ;--------------------------------------------
    ; 硬件相关的一些操作
    cmp     ds:_PoHiberInProgress, 0
    jnz     short kit03_01
    lock    inc ds:_KiHardwareTrigger
kit03_01:
    mov     eax, BREAKPOINT_BREAK
?
    ; 判断当前是否属于虚拟 86 模式
    test    byte ptr [ebp+72h], 2
    jne     kit03_30
    ;---------------------------------------------
?
    ; 判断 CS 段寄存器的最低为是否为 1,如果最
    ; 低为为 1 表示异常产生于 R3 用户空间。
    ; 0  R0   00
    ; 3  R3   11
    test    byte ptr [ebp+SegCs], 1
    jne     kit03_04
?
    ; 判断 EFlags 中的段 IF 标志位是否为 0
    test    byte ptr [ebp+71h], 2
    je      kit03_10
    jmp     kit03_05
?
kit03_04:
    ; 如果异常产生于系统代码,则进行其它操作
    cmp     word ptr [ebp+6Ch], 1Bh
    jne     kit03_30
?
kit03_05:
    ; 如果 IF 为 0 ,则设置 IF 
    sti
?
kit03_10:
    ; 设置附加参数
    mov     esi, ecx                ; ExceptionInfo 2
    mov     edi, edx                ; ExceptionInfo 3
    mov     edx, eax                ; ExceptionInfo 1
?
    ; 设置异常信息
    mov     ebx, [ebp]+TsEip
    dec     ebx                     ; ebx = 异常地址(int 3)
    mov     ecx, 3                  ; ecx = 参数个数
    mov     eax, STATUS_BREAKPOINT  ; eax = 异常类型
?
    ; 调用函数继续执行异常的分发,函数不会返回
    call    CommonDispatchException
?
kit03_30:
    ; 如果当前属于 V86 模式或内核模式下
    mov     ebx,PCR[PcPrcbData+PbCurrentThread]
    mov     ebx,[ebx]+ThApcState+AsProcess
    cmp     dword ptr [ebx] + PrVdmObjects,0 
    je      kit03_05
    stdCall _Ki386VdmReflectException_A, <03h>
    test    ax,0FFFFh
    jz      Kit03_10
?
    ; 继续执行发生异常的代码
    jmp     _KiExceptionExit
?
_KiTrap03       endp
?
 
?
; 一个宏,用于填充 _KTRAP_FRAME 结构,该结构用于保存寄存器环境
ENTER_TRAP  macro    AssistLabel, TargetLabel
    ; 清除异常代码的低二字节
    mov     word ptr [esp+2], 0 
?
    ; 当产生异常的时候,CPU会自动保存下列的寄存器
    ;+0x068 Eip              : Uint4B
    ;+0x06c SegCs            : Uint4B
    ;+0x070 EFlags           : Uint4B
?
?
    ; 保存非易失性寄存器
    push    ebp                 
    push    ebx
    push    esi
    push    edi
?
    ; 替换 fs 寄存器和异常链表
    push    fs
    mov     ebx, KGDT_R0_PCR(KPCR)
    mov     fs, bx
    mov     ebx, fs:[PcExceptionList] 
?
    ; 开辟空间存储先前模式(R0\R3)
    sub     esp, 4             
?
    ; 保存易失性寄存器
    push    eax                 
    push    ecx
    push    edx
?
    ; 保存段寄存器
    push    ds                  
    push    es
    push    gs
?
    ; 扩充栈的大小,构建一个完整的 _KTRAP_FRAME 结构
    mov     ax, KGDT_R3_DATA OR RPL_MASK ; 不做考虑
    sub     esp, TsSegGs
    mov     ds, ax  ; 不做考虑
    mov     es, ax  ; 不做考虑
?
    ; **  此时 [esp] 以及 [ebp] 都指向了 _KTRAP_FRAME 结构体 
    mov     ebp, esp
?
    ; 判断当前是否是虚拟 8086 模式,如果是的话就重新进行填充
    test    dword ptr [esp].TsEflags,EFLAGS_V86_MASK
    jnz     V86$AssistLabel(V86_kit3_a)
?
    ; 从 KPCR 结构体中获取当前线程地址 ecx = 当前线程
    mov     ecx, PCR[PcPrcbData+PbCurrentThread]
?
    ; 清除方向标志位
    cld
?
    ; 清除 Dr7 寄存器
    and     dword ptr [ebp].TsDr7, 0
?
    ; 检查是否需要保存调试寄存器,如果是则保存
    test    byte ptr [ecx].ThDebugActive, 0Dfh
    jnz     nt!Dr_kit3_a        ; 保存寄存器
?
Dr_$TargetLabel(Dr_kitb_t):
    ; 调用宏用于填充 _KTRAP_FRAME 结构中的调试部分
    SET_DEBUG_DATA
endm
?
?
?
; 如果是虚拟 8086 模式则跳转
V86_kit3_a  proc near
    mov     eax,dword ptr [ebp + 84h]   ;  V86Fs
    mov     ebx,dword ptr [ebp + 88h]   ;  V86GS
    mov     ecx,dword ptr [ebp + 7Ch]   ;  V86Es
    mov     edx,dword ptr [ebp + 80h]   ;  V86Ds
    mov     word ptr [ebp + 50h], ax    ;  Trap_frame.SegFs = Trap_frame.V86Fs
    mov     word ptr [ebp + 30h], bx    ;  Trap_frame.SegGs = Trap_frame.V86Gs
    mov     word ptr [ebp + 34h], cx    ;  Trap_frame.SegEs = Trap_frame.V86Es
    mov     word ptr [ebp + 38h], dx    ;  Trap_frame.SegDs = Trap_frame.V86Ds
    jmp     nt!KiTrap03+0x42 (83e9278a)
V86_kit3_a  endp
?
?
?
; 设置 _KTRAP_FRAME 中的调试相关部分
SET_DEBUG_DATA  macro
        mov     ebx,[ebp] + TsEbp
        mov     edi,[ebp] + TsEip
        mov     [ebp] + TsDbgArgPointer, edx
        mov     [ebp] + TsDbgArgMark, 0BADB0D00h
        mov     [ebp] + TsDbgEbp,ebx
        mov     [ebp] + TsDbgEip,edi
endm
?
?
?
; 用于保存调试寄存器
Dr_kit3_a   proc near:
    test    dword ptr [ebp+70h],20000h
    jne     nt!Dr_kit3_a+0x13
    test    byte ptr [ebp+6Ch],1
    je      nt!KiTrap03+0x58 (83e92618)
    mov     ebx, dr0
    mov     ecx, dr1
    mov     edi, dr2
    mov     dword ptr [ebp+18h], ebx
    mov     dword ptr [ebp+1Ch], ecx
    mov     dword ptr [ebp+20h], edi
    mov     ebx, dr3
    mov     ecx, dr6
    mov     edi, dr7
    mov     dword ptr [ebp+24h], ebx
    mov     dword ptr [ebp+28h], ecx
    xor     ebx, ebx
    mov     dword ptr [ebp+2Ch], edi
    mov     dr7, ebx
    mov     edi, dword ptr fs:[20h]
    mov     ebx, dword ptr [edi+2F4h]
    mov     ecx, dword ptr [edi+2F8h]
    mov     dr0, ebx
    mov     dr1, ecx
    mov     ebx, dword ptr [edi+2FCh]
    mov     ecx, dword ptr [edi+300h]
    mov     dr2, ebx
    mov     dr3, ecx
    mov     ebx, dword ptr [edi+304h]
    mov     ecx, dword ptr [edi+308h]
    mov     dr6, ebx
    mov     dr7, ecx
    jmp     nt!KiTrap03+0x58 (83e92618)
Dr_kit3_a   endp

 

CommonDispatchException用于填充异常结构,

并调用 _KiDispatchException 函数

//流程总结
KiDispatchException
    R0:
        1: 检查是否有内核调试器,并尝试调用
            
            -> 失败或没有内核调试器:[0]RtlDispatchException(SEH) 
?
                -> 第二次异常分发
?
        2: 检查是否有内核调试器,并尝试调用
?
            -> 失败或没有内核调试器: KeBugCheckEx 蓝屏
?
    R3:
?
        1:是否有内核调试器并且不处于调试状态
?
            -> [0]通过指针 KiDebugRoutine 调用内核调试器
?
            -> [0]DbgkForwardException(DbgkpSendApiMessage) 发送给用户调试器,等待返回是否处理
?
            -> [0]构建了一个 ExceptionPointers 结构体并且将 esp 指向了R3的KiUserExceptionDispatcher
?
            -> [3] KiUserExceptionDispatcher
?
                -> [3] RtlDispatchException: veh vch seh
?
                -> 成功: NtContinue 返回 R0 设置 eip 并且重新回到 R3
?
                -> 失败: 调用 RtlRaiseException 进行第二次的异常分发
?
        2: DbgkForwardException(ExceptionRecord, TRUE, TRUE) 发送给调试端口
?
            DbgkForwardException(ExceptionRecord, FALSE, TRUE) 发送给异常端口
?
            ZwTerminateProcess()  结束进程
?
            KeBugCheckEx

 

 

; 用于填充异常结构,并调用 _KiDispatchException 函数
CommonDispatchException proc
    ; esi = 附加参数2            edi = 附加参数3
    ; edx = 附加参数1            ebx = 异常地址
    ; ecx = 参数个数             eax = 异常类型
    ; esp = EXCEPTION_RECORD    ebp = _KTRAP_FRAME
?
    ; 在栈中开辟记录异常所需的栈空间 EXCEPTION_RECORD
    sub     esp, ExceptionRecordLength
?
    ; 使用 _KiTrap03 传入的参数构建异常结构
    mov     dword ptr [esp].ErExceptionCode,eax     ;*
    xor     eax, eax
    mov     dword ptr [esp].ExceptionFlags,eax  
    mov     dword ptr [esp].ExceptionRecord,eax
    mov     dword ptr [esp].ExceptionAddress,ebx    ;*
    mov     dword ptr [esp].NumberParameters,ecx    ;*
   
    ; 参数个数为0跳转
    cmp     ecx,0
    je      _FLAG01
?
    ; 保存异常的附加参数到结构体中
    lea     ebx,[esp].ExceptionInformation
    mov     dword ptr [ebx],edx
    mov     dword ptr [ebx + 4],esi
    mov     dword ptr [ebx + 8],edi
?
_FLAG01:
    mov     ecx,esp
    
    ; 判断是否为虚拟 86 模式,如果是则跳转
    test    byte ptr [ebp].TsEFlags,2
    je      _FLAG02
    
    ; 如果为虚拟 86 模式
    mov     eax,0FFFFh
    jmp     _FLAG03
?
_FLAG02:
    ; 如果为保护模式则获取 CS 段寄存器
    mov     eax,dword ptr [ebp].SegCs
?
_FLAG03:
    ; 获取 CS 最低位
    and     eax, MODE_MASK
?
    ; 传入参数
    push    1       ; first chance: 表示是否是第一次处理异常
    push    eax     ; PreviousMode: 先前模式,当前异常由 R0 产生还是 R3 产生
    push    ebp     ; TrapFrame: 陷阱帧,保存的是异常产生的寄存器环境
    push    0       ; ExceptionFrame: 空的异常栈帧
    push    ecx     ; EXCEPTION_RECORD: 填充的 EXCEPTION_RECORD
    call    nt!KiDispatchException (83f07ee0)
    mov     esp,ebp
    
    ; 结束异常处理
    jmp     nt!KiExceptionExit (83e91bf8)
CommonDispatchException endp

 

 

如果异常是第一次分发并且进程具有调试端口, 则发送一个消息 到调试端口,并等待回复. 如果调试器处理了这个异常, 则结束异常的分发. 否则, 将异常信息拷贝到用户态的栈中, 并转到用户模式 , 在用户模式下尝试将异常派发 给异常处理程序. 如果异常处理程序处理了异常, 则结束异常分发. 如果用户层的 异常处理程序处理不了, 则调用NtRaiseException函数 主动触发异常, 并将 FirstChance设置为TRUE. 这个函数(KiDispatchException) 将会被第二次调用以 继续处理异常. 如果这次处理是第二次异常处理,并且进程有一个调试端口, 则发送 一个消息到调试端口,并等待调试器回复.如果调试器回复已经处理了异常, 则结束异 常分发. 否则发送给进程的异常端口, 并等其回复. 若异常被处理, 则异常分发结束 否则直接结束掉当前进程.

VOID KiDispatchException (
    IN PEXCEPTION_RECORD ExceptionRecord,   // 指向异常信息结构体
    IN PKEXCEPTION_FRAME ExceptionFrame,    // 指向异常帧,0
    IN PKTRAP_FRAME TrapFrame,              // 指向陷阱帧,寄存器环境 
    IN KPROCESSOR_MODE PreviousMode,        // 异常产生的位置 R0/R3
    IN BOOLEAN FirstChance                  // 是否是第一次分发异常
    )

    // 递增异常分发计数器
    KeGetCurrentPrcb()->KeExceptionDispatchCount++;
 
    // 创建保存线程环境的结构体,其中的参数 1 表示要操作哪些寄存器
    CONTEXT Context =  CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS ;
 
    // --------------------------------------------------------------
    // 如果当前处于用户模式产生的异常,或内核调试器处于开启状态
    if (PreviousMode == UserMode) || KdDebuggerEnabled)
    
        // 尝试获取硬件相关的一些寄存器
        ContextFrame.ContextFlags |= CONTEXT_FLOATING_POINT;
        if (KeI386XMMIPresent) 
            ContextFrame.ContextFlags |= CONTEXT_EXTENDED_REGISTERS;
        
    
    // --------------------------------------------------------------
?
?
    // 通过传入的陷阱帧和异常帧填充 Context 结构体中的线程环境
    KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context);
 
?
    // 对不同的异常执行不同的处理操作
    switch (ExceptionRecord->ExceptionCode)
    
        // 如果是断点异常 [int 3]
    case STATUS_BREAKPOINT:            
        // 因为 int 3 是陷阱类异常,实际产生异常的指令是上一条
        Context.Pc -= sizeof(ULONG);
        break;
 
    // ------------------------------------------------------------------------
    // 如果是设备访问异常
    case KI_EXCEPTION_ACCESS_VIOLATION:
    // 设置异常的类型
    ExceptionRecord->ExceptionCode = STATUS_ACCESS_VIOLATION;
?
    // 用户模式下对 Thunk 进行的一些检查
    if (PreviousMode == UserMode)
    
        if (KiCheckForAtlThunk(ExceptionRecord,&ContextFrame) != FALSE) 
            goto Handled1;
        
?
        if ((SharedUserData->ProcessorFeatures[PF_NX_ENABLED] == TRUE) &&
            (ExceptionRecord->ExceptionInformation [0] == EXCEPTION_EXECUTE_FAULT)) 
            
            if (((KeFeatureBits & KF_GLOBAL_32BIT_EXECUTE) != 0) ||
                (PsGetCurrentProcess()->Pcb.Flags.ExecuteEnable != 0) ||
                (((KeFeatureBits & KF_GLOBAL_32BIT_NOEXECUTE) == 0) &&
                    (PsGetCurrentProcess()->Pcb.Flags.ExecuteDisable == 0))) 
                ExceptionRecord->ExceptionInformation [0] = 0;
            
        
    
    break;
    
    // ------------------------------------------------------------------------
 
?
    // 如果异常产生于内核态且处理器位 V86 模式
    ASSERT ((
             !((PreviousMode == KernelMode) &&
             (ContextFrame.EFlags & EFLAGS_V86_MASK))
           ));
?
?
    // 如果异常产生于内核模式下
    if (PreviousMode == KernelMode(0))
    
         // 查看当前是否是第一次产生的异常分发
         if (FirstChance == TRUE)
         
            // 调用 KiDebugRoutine 函数指针(实际就是内核调试器,如果没有内核调试器,
            // 函数指针保存的是KdpTrap函数的地址,如果有,则保存KdpStub函数地址) 
            // - 1. 指向内核调试器
            // - 2. 指向一个没有意义的函数 KdpStub
            if ((KiDebugRoutine != NULL) &&
               (((KiDebugRoutine) (TrapFrame,       // 线程环境
                                   ExceptionFrame,  // 异常栈帧
                                   ExceptionRecord, // 异常信息记录
                                   &ContextFrame, 
                                   PreviousMode, 
                                   FALSE)) != FALSE)) 
                // 如果调试处理了异常, 则跳转到函数末尾.
                goto Handled;
            
            // 没有调试器, 或者有调试器但没有处理得了异常
            // 则将异常交给内核的 SEH 来处理
            if (RtlDispatchException(ExceptionRecord, &ContextFrame) == TRUE) 
                // 如果SEH处理成功, 则跳转到函数末尾
                goto Handled;
            
        
?
        // 如果调试器和SEH异常处理都没有处理得了异常, 则进行第二次异常分发:
        // 判断有无内核调试器,并调用(再给内核调试器一次处理异常得机会)
        if ((KiDebugRoutine != NULL) &&
            (((KiDebugRoutine) (TrapFrame,
                                ExceptionFrame,
                                ExceptionRecord,
                                &ContextFrame,
                                PreviousMode,
                                TRUE)) != FALSE)) 
            goto Handled;
        
 
        // 没有内核调试器, 或内核调试器不处理, 则调用函数KeBugCheckEx记录异常,之后蓝屏死机
        KeBugCheckEx(
            KERNEL_MODE_EXCEPTION_NOT_HANDLED,
            ExceptionRecord->ExceptionCode,
            (ULONG)ExceptionRecord->ExceptionAddress,
            (ULONG)TrapFrame,
            0);
    
?
?
?
?
    else // 异常在在用户模式下触发 
       
        // 如果异常是第一次分发并且进程[具有调试端口](处于被调试状态), 则发送一个消息
        // 到调试端口,并等待回复. 如果调试器处理了这个异常, 则结束异常的分发. 否则,
        // 将异常信息拷贝到用户态的栈中, 并转到用户模式 , 在用户模式下尝试将异常派发
        // 给异常处理程序. 如果异常处理程序处理了异常, 则结束异常分发. 如果用户层的
        // 异常处理程序处理不了, 则调用NtRaiseException函数 主动触发异常, 并将
        // FirstChance设置为TRUE. 这个函数(KiDispatchException)  将会被第二次调用以
        // 继续处理异常.  如果这次处理是第二次异常处理,并且进程有一个调试端口, 则发送
        // 一个消息到调试端口,并等待调试器回复.如果调试器回复已经处理了异常, 则结束异
        // 常分发. 否则发送给进程的异常端口, 并等其回复. 若异常被处理, 则异常分发结束
        // 否则直接结束掉当前进程.
?
?
        if (FirstChance == TRUE) 
            // 检查是进程是否被调试,如果当前有内核调试器, 并且进程没有被调试,则将异常交给
            // 内核调试器去处理.
            if ((KiDebugRoutine != NULL)  &&
            // DebugPort 调试端口,当程序属于被调试状态时,为非空
                ((PsGetCurrentProcess()->DebugPort == NULL &&
                  !KdIgnoreUmExceptions) ||
                 (KdIsThisAKdTrap(ExceptionRecord, &ContextFrame, UserMode)))) 
?
                // 将异常信息交给内核调试器处理
                if ((((KiDebugRoutine) (TrapFrame,
                                        ExceptionFrame,
                                        ExceptionRecord,
                                        &ContextFrame,
                                        PreviousMode,
                                        FALSE)) != FALSE)) 
                    // 处理成功则异常分发结束
                    goto Handled1;
                
            
            
// 将异常交给调试子系统去处理. DbgkForwardException函数会将
// 异常记录发送给3环的调试器进程, 并等待3环的调试器回复.
// 如果调试器回复了异常被处理, 则异常分发到此结束.
            // *DbgkpSendApiMessage()*   用于发送调试信息,阻塞函数,
            // 会等待调试器返回处理结果
            if (DbgkForwardException(ExceptionRecord, TRUE, FALSE)) 
                goto Handled2;
            
// 如果没有用户调试器,或用户调试器没有处理异常则接着往下走.
// 函数会试图将异常记录, 线程环境进行保存
            ExceptionRecord1.ExceptionCode = 0; // satisfy no_opt compilation
?
        repeat:
            try 
                // ------------------------------------------------------
                // 如果SS不位于32位保护模式下
                if (TrapFrame->HardwareSegSs != (KGDT_R3_DATA | RPL_MASK) ||
                    TrapFrame->EFlags & EFLAGS_V86_MASK ) 
                
                    // 触发一个内存访问异常
                    ExceptionRecord2.ExceptionCode = STATUS_ACCESS_VIOLATION;
                    ExceptionRecord2.ExceptionFlags = 0;
                    ExceptionRecord2.NumberParameters = 0;
                    ExRaiseException(&ExceptionRecord2);
                
                // ------------------------------------------------------
?
// 为将线程上下文块整个结构体拷贝到用户的栈空间, 需要找到栈空间上一个空闲
// 的地址, 此处是计算esp(栈顶位置) - 一个线程上下文块的大小, 也就是相当于
// sub esp , sizeof(CONTEXT) 
                UserStack1 = (ContextFrame.Esp & ~CONTEXT_ROUND) - CONTEXT_ALIGNED_SIZE;
                // ---userstack1(esp)
                // context  <- 线程环境
                // -------
?
// 将指定地址设置为可写入
                ProbeForWrite((PCHAR)UserStack1, CONTEXT_ALIGNED_SIZE, CONTEXT_ALIGN);
// 将线程上下文拷贝到用户的栈空间中.
                RtlCopyMemory((PULONG)UserStack1, &ContextFrame, sizeof(CONTEXT));
?
// 计算处异常信息结构体的在用户栈空间中的位置, 也是为了将异常信息写入
// 到用户栈空间中.   EXCEPTION_RECORD 的大小
                Length = (sizeof(EXCEPTION_RECORD) - (EXCEPTION_MAXIMUM_PARAMETERS -
                         ExceptionRecord->NumberParameters) * sizeof(ULONG) + 3) &
                         (~3);
                UserStack2 = UserStack1 - Length;
                // ---userstack2(esp)
                // EXCEPTION_RECORD
                // ---userstack1
                // context  <- 线程环境
                // -------
                //   在用户空间合成了一个 ExceptionPointers 结构
?
// 将地址设置为可写
                ProbeForWrite((PCHAR)(UserStack2 - 8), Length + 8, sizeof(ULONG));
// 将异常信息拷贝用户的栈空间中.
                RtlCopyMemory((PULONG)UserStack2, ExceptionRecord, Length);
?
// 构造处一个EXCEPTION_POINTERS的结构体, 并保存线程上下文, 异常信息两个结构体
// 变量的首地址.
                *(PULONG)(UserStack2 - sizeof(ULONG)) = UserStack1;
                *(PULONG)(UserStack2 - 2*sizeof(ULONG)) = UserStack2;
?
                // 设置新的SS寄存器和ESP寄存器
                KiSegSsToTrapFrame(TrapFrame, KGDT_R3_DATA);
                KiEspToTrapFrame(TrapFrame, (UserStack2 - sizeof(ULONG)*2));
?
// 填充三环的段寄存器
                TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, PreviousMode);
                TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
                TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
                TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, PreviousMode);
                TrapFrame->SegGs = 0;
?
// 将发生异常的线程的eip的地址设置为KeUserExceptionDispatcher函数的地址
// 这个函数是ntdll中的导出函数,这个导出函数就是负责用户层的异常分发的,
// 在这个函数中,它会把异常发给进程的异常处理机制(VEH,SEH)去处理.
// 这样一来, 当执行流从0环回到3环的时候, eip指向何处, 那个地方的代码就开始
// 被执行.
                TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher;
                // KeUserExceptionDispatcher 是指针指向了R3的 KiUserExceptionDispatcher
                return;
?
             except (KiCopyInformation(&ExceptionRecord1,
                        (GetExceptionInformation())->ExceptionRecord)) 
?
                // 如果这些代码产生了栈溢出错误,就构建一个栈溢出异常进行分发
?
                if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) 
                    ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
                    RtlCopyMemory((PVOID)ExceptionRecord,
                                  &ExceptionRecord1, sizeof(EXCEPTION_RECORD));
                    goto repeat;
                
            
        
?
        // 如果是第二次异常分发
        // 将异常信息发送到调试器的 DebugPort
        // 第三个参数表示当前是不是第二次调用
        if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) 
            goto Handled2;
?
        // 将异常信息发送到调试器的 ExceptionPort
         else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) 
            goto Handled2;
        // 如果还不能处理就结束进程并发送错误报告
         else 
            ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
            
            /////////////// 保留 ////////////////
            KeBugCheckEx(
                KERNEL_MODE_EXCEPTION_NOT_HANDLED,
                ExceptionRecord->ExceptionCode,
                (ULONG)ExceptionRecord->ExceptionAddress,
                (ULONG)TrapFrame,
                0);
        
    
?
Handled:
// 将异常栈帧, 线程环境设置到线程中,并继续执行程序
    // 处理异常可能会修改CONTEXT的寄存器,但是返回异常产生的位置
    // 使用的是 TrapFrame,所以需要将修改更新到 TrapFrame
    KeContextToKframes(TrapFrame, ExceptionFrame, &ContextFrame,
                       ContextFrame.ContextFlags, PreviousMode);
?
Handled2:
    // 如果异常已经被处理,则直接进行返回
    return;

 

RtlDispatchException[R0].c

// 通过遍历 SEH 链调用其中的函数来处理异常并返回处理结果
BOOLEAN RtlDispatchException (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN PCONTEXT ContextRecord
    )

    // 是否处理成功
    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;
?
    // 获取当前堆栈的栈顶和栈底
    RtlpGetStackLimits(&LowLimit, &HighLimit);
?
    // 获取 SEH 链的头节点
    RegistrationPointer = RtlpGetRegistrationHead();
    // mov eax, fs:[0]
    // mov RegistrationPointer, eax
?
    NestedRegistration = 0;
?
    // 依次遍历调用 SEH 链中的函数
    while (RegistrationPointer != EXCEPTION_CHAIN_END(-1)) 
?
        // 获取当前异常处理结构的起始地址和末尾地址
        HighAddress = (ULONG)RegistrationPointer +
            sizeof(EXCEPTION_REGISTRATION_RECORD);
?
        // 如果起始地址小于栈底,结束位置大于栈顶,或者结构体地址没有对齐
        if (((ULONG)RegistrationPointer < LowLimit) ||
             (HighAddress > HighLimit) ||
             // test xxx, 3   0x11
             (((ULONG)RegistrationPointer & 0x3) != 0) 
           )  
// -------------------------------------------------
            // 尝试对结构体的地址进行修复
            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)) 
                    
                    HighLimit = DpcStack;
                    LowLimit = DpcStack - KERNEL_STACK_SIZE;
                    continue;
                
            
?
            // 如果无法修复就退出函数并设置异常标志为栈无效
            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
            goto DispatchExit;
        
?
        // 查看当前处理程序是否是有效的
        if (!RtlIsValidHandler(RegistrationPointer->Handler)) 
            // 如果是无效的就结束遍历
            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
            goto DispatchExit;
        
?
        
        // 如果启用了调试异常的记录,则记录异常
        if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) 
            Index = RtlpLogExceptionHandler(
                            ExceptionRecord,
                            ContextRecord,
                            0,
                            (PULONG)RegistrationPointer,
                            4 * sizeof(ULONG));
        
// -------------------------------------------------
?
        // 执行当前遍历到的 SEH 函数   _except_handler4
        Disposition = RtlpExecuteHandlerForException(
            ExceptionRecord,
            (PVOID)RegistrationPointer,
            ContextRecord,
            (PVOID)&DispatcherContext,
            (PEXCEPTION_ROUTINE)RegistrationPointer->Handler);
?
        // 记录 SEH 函数的处理结果
        if (NtGlobalFlag & FLG_ENABLE_EXCEPTION_LOGGING) 
            RtlpLogLastExceptionDisposition(Index, Disposition);
        
?
        // 当位于嵌套异常且处于上下文末尾,则清除嵌套标志
        if (NestedRegistration == RegistrationPointer) 
            ExceptionRecord->ExceptionFlags &= (~EXCEPTION_NESTED_CALL);
            NestedRegistration = 0;
        
?
        // 根据 SEH 函数的处理结果进行性对应的操作
        switch (Disposition) 
        
        // 继续执行
        case ExceptionContinueExecution :
            // 如果这是一个不可持续的异常,就继续使用 
            // RtlRaiseException进行第二次的异常分发
            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;
            
?
        // 向下搜索
        case ExceptionContinueSearch :
            // 如果栈没有出现损坏,则继续遍历
            if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID)
                goto DispatchExit;
            break;
?
        // 嵌套的异常
        case ExceptionNestedException :
            // 如果在异常处理程序中产生了异常,就从指定位置开始重新遍历
            ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
            if (DispatcherContext.RegistrationPointer > NestedRegistration) 
                NestedRegistration = DispatcherContext.RegistrationPointer;
            
            break;
?
?
        default :
            // 如果以上都不是,就生成一个新的异常
            ExceptionRecord1.ExceptionCode = STATUS_INVALID_DISPOSITION;
            ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
            ExceptionRecord1.ExceptionRecord = ExceptionRecord;
            ExceptionRecord1.NumberParameters = 0;
            RtlRaiseException(&ExceptionRecord1);
            break;
        
?
        // 循环遍历下一个处理程序
        RegistrationPointer = RegistrationPointer->Next;
    
?
DispatchExit:
?
    return Completion;

 

RtlDispatchException[R3].c


     PEXCEPTION_REGISTRATION_RECORD RegistrationFrame;
     PEXCEPTION_REGISTRATION_RECORD NestedFrame = NULL;
     DISPATCHER_CONTEXT DispatcherContext;
     EXCEPTION_RECORD ExceptionRecord2;
     EXCEPTION_DISPOSITION Disposition;
     ULONG_PTR StackLow, StackHigh;
     ULONG_PTR RegistrationFrameEnd;
 
    // 调用用户模式下的向量化异常处理程序(VEH)
    if (RtlCallVectoredExceptionHandlers(ExceptionRecord, Context))
    
        // 异常被处理了就调用向量化异常处理程序(VCH)
        RtlCallVectoredContinueHandlers(ExceptionRecord, Context);
 
        // 返回已处理
        return TRUE;
    
 
    // 获取当前栈顶以及栈限长
     RtlpGetStackLimits(&StackLow, &StackHigh);
     RegistrationFrame = RtlpGetExceptionList();
 
     // 不断循环遍历所有的 SEH 结构
    while (RegistrationFrame != EXCEPTION_CHAIN_END)
    
        // 检查是否为空,一个被注册的异常结构永远不为空
        ASSERT(RegistrationFrame != NULL);
?
        // 获取异常结构的结束位置
        RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame +
                                sizeof(EXCEPTION_REGISTRATION_RECORD);
?
        // 检查异常结构是否在栈内,并且是否对齐
        if ((RegistrationFrameEnd > StackHigh) ||
            ((ULONG_PTR)RegistrationFrame < StackLow) ||
            ((ULONG_PTR)RegistrationFrame & 0x3) ||
            !RtlIsValidHandler(SehBase->Handler))
        
            // 如果不满足条件
            ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
?
            // 异常被处理了就调用向量化异常处理程序(VCH)
            RtlCallVectoredContinueHandlers(ExceptionRecord, Context);
 
            // 返回已处理
            return FALSE;
        
?
        // 如果启用了调试异常的记录,则记录异常
        RtlpCheckLogException(ExceptionRecord,
                            Context,
                            RegistrationFrame,
                            sizeof(*RegistrationFrame));
?
        // 调用异常处理函数, exception_handler4
        Disposition = RtlpExecuteHandlerForException(ExceptionRecord,
                                                    RegistrationFrame,
                                                    Context,
                                                    &DispatcherContext,
                                                    RegistrationFrame->Handler);
?
        // 如果是嵌套异常处理结构体
        if (RegistrationFrame == NestedFrame)
        
            // 屏蔽嵌套标志位
            ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL;
            NestedFrame = NULL;
        
?
        // 根据 SEH 函数的处理结果进行性对应的操作
        switch (Disposition)
        
            // 继续执行
            case ExceptionContinueExecution:
?
                // 如果是一个不可继续的异常
                if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
                
                    // 设置异常信息
                    ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                    ExceptionRecord2.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION;
                    ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                    ExceptionRecord2.NumberParameters = 0;
                    RtlRaiseException(&ExceptionRecord2);
                
                else
                
                    // 调用向量化异常处理程序(VCH)
                    RtlCallVectoredContinueHandlers(ExceptionRecord,
                                                    Context);
                    return TRUE;
                
?
            // 搜索下一层异常处理函数
            case ExceptionContinueSearch:
                break;
?
            // 如果是嵌套异常
            case ExceptionNestedException:
                ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
                if (DispatcherContext.RegistrationPointer > NestedFrame)
                
                    NestedFrame = DispatcherContext.RegistrationPointer;
                
                break;
?
            // 其它操作
            default:
                // 设置异常信息结构
                ExceptionRecord2.ExceptionRecord = ExceptionRecord;
                ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
                ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                ExceptionRecord2.NumberParameters = 0;
                RtlRaiseException(&ExceptionRecord2);
                break;
        
?
        // 获取下一个异常处理函数
        RegistrationFrame = RegistrationFrame->Next;
    
?
    // 调用向量化异常处理程序(VCH)
    RtlCallVectoredContinueHandlers(ExceptionRecord,
                                    Context);
    // 如果没有处理就返回 FALSE
    return FALSE;

 

KiUserExceptionDispatcher.c


    EXCEPTION_RECORD NestedExceptionRecord;
    NTSTATUS Status;
?
    // 处理异常并检查返回值
    if (RtlDispatchException(ExceptionRecord, Context))
    
        // 如果成功了就直接通过 NtContinue 进入 R0 并返回执行代码的位置
        Status = NtContinue(Context, FALSE);
    
    else
    
        // 如果没有处理成功就继续抛出异常(Ki)
        // 最后一个参数表示是否是第一次产生的异常 FALSE
        Status = NtRaiseException(ExceptionRecord, Context, FALSE);
    
?
    // 设置异常函数
    NestedExceptionRecord.ExceptionCode = Status;
    NestedExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
    NestedExceptionRecord.ExceptionRecord = ExceptionRecord;
    NestedExceptionRecord.NumberParameters = Status;
?
    // 抛出异常(Zw)
    RtlRaiseException(&NestedExceptionRecord);

 

遍历当前线程中的所有 SEH 函数及添加自定义的 SEH 函数

#include <iostream>
#include <windows.h>
?
// SEH 保存在 TEB 结构体偏移为 0 的地方,是一个链表
// SEH 链表可以通过 FS:[0] 这样一个地址找到
// TEB 可以通过 FS:[0x18] 这样一个地址找到
?
// try except 的原理就是在 SEH 链表中添加一个新的节点
?
// 没有设置 SEH 的函数
void func1()

?

?
// 设置了 SEH 的函数
void func2()

    // 如果一个函数中设置了 SEH 处理程序,那么必然会操作 FS:[0]
    // 在 VS 下,无论设置了多少个 SEH,最终只会添加  _except_handler4 函数
    // 系统调用 _except_handler4,_except_handler4 内部区分当前的 try 位于第几层
?
    __try 
        __try 
            __try 
                __try 
                    __try 
                    
                    __except (1) 
                    
                
                __except (1) 
                
            
            __except (1) 
            
        
        __except (1) 
        
    
    __except (1) 
    

?
// 自定义的 SEH 函数
EXCEPTION_DISPOSITION NTAPI ExceptionHandler(
    struct _EXCEPTION_RECORD* ExceptionRecord,
    PVOID EstablisherFrame,
    struct _CONTEXT* ContextRecord,
    PVOID DispatcherContext)

    return ExceptionContinueSearch;

?
// 遍历当前线程中的所有 SEH 函数
void GetThreadList()

    // 1. 获取当前 SEH 链表的头节点
    PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
    __asm push FS : [0];
    __asm pop ExceptionList;
?
    // 2. 遍历 SEH 中的所有函数
    //(PEXCEPTION_REGISTRATION_RECORD)-1指Next节点。
    //因为PEXCEPTION_REGISTRATION_RECORD-1是指针减1
    //即减少一个自己大小的结构体
    while (ExceptionList != (PEXCEPTION_REGISTRATION_RECORD)-1)
    
        // 3. 输出当前层,对应的处理函数
        printf("0x%08X\n", ExceptionList->Handler);
?
        // 4. 将指针指向下一个节点
        ExceptionList = ExceptionList->Next;
    
?
    printf("\n");

?
int main()

    // 0. 保存 SEH 头节点,主要用于恢复
    PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
    __asm push FS : [0];
    __asm pop ExceptionList;
?
    // 1. 添加自定义 SEH 函数之前的 SEH 链
    GetThreadList();
?
    // 2. 添加自定义的 SEH 函数
    __asm push ExceptionHandler
    __asm push fs : [0]
        __asm mov fs : [0], esp
?
    // 3. 添加自定义 SEH 函数之后的 SEH 链
    GetThreadList();
?
    // 4. 恢复旧的 SEH 头节点
    __asm add esp, 0x08
    __asm mov eax, ExceptionList
    __asm mov fs : [0], eax
?
    // 5. 应该和以前的节点是相同的
    GetThreadList();
?
    return 0;

 

 

以上是关于Windows调试2.异常产生详细流程的主要内容,如果未能解决你的问题,请参考以下文章

Windows和Linux下排查C++软件异常的常用调试器与内存检测工具详细介绍

软件调试

Windows如何配置和迁移深度学习环境,以及使用Pycharm调试源码?(全网最详细)

史上最全的 IDEA Debug 调试技巧(超详细!建议收藏!)

史上最全的 IDEA Debug 调试技巧(超详细!建议收藏!)

Visual Studio - 调试