Windows调试2.异常产生详细流程
Posted ltyandy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows调试2.异常产生详细流程相关的知识,希望对你有一定的参考价值。
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 调试技巧(超详细!建议收藏!)