用户异常与模拟异常的派发

Posted onetrainee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用户异常与模拟异常的派发相关的知识,希望对你有一定的参考价值。

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

用户异常与模拟异常的派发

一、KiDispatchException函数处理流程图

  无论用户模拟异常还是CPU异常,经过前面分析,在经过记录之后,最终都会经过KiDispatchException这个派发函数中。

  在KiDispatchException中会对CPU异常和用户异常分别进行处理。

  CPU异常:首先调用内核调试器,如果调用失败则调用RtlDispatchException(该函数后面会有介绍)分发,

      RtlDispatchException函数会检查SEH链中是否有该程序的处理函数,如果有则返回成功,

      若RtlDispatchException函数处理异常失败,其会尝试第二次调用内核调试器进行调试处理,如果最终处理不了直接蓝屏。

  用户异常:先尝试内核调试器,再尝试用户调试器,如果还不行,直接返回用户代码尝试使用try_catch_语法来进行处理,之后还不行再尝试两次用户调试器。

      如果最终还是处理不了异常,该进程会关闭并报出错误。(用户异常不会导致蓝屏出现)

技术图片

 

 二、RtlDispatchException的函数解析代码

  1 VOID
  2 KiDispatchException (
  3     IN PEXCEPTION_RECORD ExceptionRecord,
  4     IN PKEXCEPTION_FRAME ExceptionFrame,
  5     IN PKTRAP_FRAME TrapFrame,
  6     IN KPROCESSOR_MODE PreviousMode,
  7     IN BOOLEAN FirstChance
  8     )
  9 
 10 /*++
 11 
 12 Routine Description:
 13 
 14     This function is called to dispatch an exception to the proper mode and
 15     to cause the exception dispatcher to be called. If the previous mode is
 16     kernel, then the exception dispatcher is called directly to process the
 17     exception. Otherwise the exception record, exception frame, and trap
 18     frame contents are copied to the user mode stack. The contents of the
 19     exception frame and trap are then modified such that when control is
 20     returned, execution will commense in user mode in a routine which will
 21     call the exception dispatcher.
 22 
 23 Arguments:
 24 
 25     ExceptionRecord - Supplies a pointer to an exception record.
 26 
 27     ExceptionFrame - Supplies a pointer to an exception frame. For NT386,
 28         this should be NULL.
 29 
 30     TrapFrame - Supplies a pointer to a trap frame.
 31 
 32     PreviousMode - Supplies the previous processor mode.
 33 
 34     FirstChance - Supplies a boolean value that specifies whether this is
 35         the first (TRUE) or second (FALSE) chance for the exception.
 36 
 37 Return Value:
 38 
 39     None.
 40 
 41 --*/
 42 
 43 {
 44     CONTEXT ContextFrame;
 45     EXCEPTION_RECORD ExceptionRecord1, ExceptionRecord2;
 46     LONG Length;
 47     ULONG UserStack1;
 48     ULONG UserStack2;
 49 
 50     //
 51     // Move machine state from trap and exception frames to a context frame,
 52     // and increment the number of exceptions dispatched.
 53     //
 54 
 55     //-------------------------------------//
 56     // 将当前异常分发次数增加1             //
 57     // 修改ContextFrame.ContextFlags标志位 //
 58     //-------------------------------------//
 59     KeGetCurrentPrcb()->KeExceptionDispatchCount += 1;
 60     ContextFrame.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
 61 
 62     //------------------------------------------//
 63     // 对于用户模式异常的处理 or 调试器可以执行 //
 64     // 标记好Context内容                        //
 65     //------------------------------------------//
 66     if ((PreviousMode == UserMode) || KdDebuggerEnabled) {
 67         //
 68         // For usermode exceptions always try to dispatch the floating
 69         // point state.  This allows exception handlers & debuggers to
 70         // examine/edit the npx context if required.  Plus it allows
 71         // exception handlers to use fp instructions without destroying
 72         // the npx state at the time of the exception.
 73         //
 74         // Note: If there‘s no 80387, ContextTo/FromKFrames will use the
 75         // emulator‘s current state.  If the emulator can not give the
 76         // current state, then the context_floating_point bit will be
 77         // turned off by ContextFromKFrames.
 78         //
 79 
 80         ContextFrame.ContextFlags |= CONTEXT_FLOATING_POINT;
 81         if (KeI386XMMIPresent) {
 82             ContextFrame.ContextFlags |= CONTEXT_EXTENDED_REGISTERS;
 83         }
 84     }
 85 
 86     //----------------------------------//
 87     // 将TrapFrame转换为ContextFrame    //
 88     // 接下来都是处理ContextFrame的内容 //
 89     // 这部分零环无意义,三环有意义     //
 90     // 否则修改返回地址不行了。         //
 91     //----------------------------------//
 92     KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextFrame);
 93 
 94     //
 95     // if it is BREAK_POINT exception, we subtract 1 from EIP and report
 96     // the updated EIP to user.  This is because Cruiser requires EIP
 97     // points to the int 3 instruction (not the instruction following int 3).
 98     // In this case, BreakPoint exception is fatal. Otherwise we will step
 99     // on the int 3 over and over again, if user does not handle it
100     //
101     // if the BREAK_POINT occured in V86 mode, the debugger running in the
102     // VDM will expect CS:EIP to point after the exception (the way the
103     // processor left it.  this is also true for protected mode dos
104     // app debuggers.  We will need a way to detect this.
105     //
106     //
107 
108     switch (ExceptionRecord->ExceptionCode) {
109         //-------------------------//
110         // 如果为int3断点异常      //
111         // 则修改eip执行原来的位置 //
112         //-------------------------//
113         case STATUS_BREAKPOINT:
114             ContextFrame.Eip--;
115             break;
116 
117         //------------------------------//
118         // 执行权限问题而发生的访问违例 //
119         //------------------------------//
120         case KI_EXCEPTION_ACCESS_VIOLATION:
121             ExceptionRecord->ExceptionCode = STATUS_ACCESS_VIOLATION;
122             if (PreviousMode == UserMode) {
123                 if (KiCheckForAtlThunk(ExceptionRecord,&ContextFrame) != FALSE) {
124                     goto Handled1;
125                 }
126 
127                 if ((SharedUserData->ProcessorFeatures[PF_NX_ENABLED] == TRUE) &&
128                     (ExceptionRecord->ExceptionInformation [0] == EXCEPTION_EXECUTE_FAULT)) {
129                     
130                     if (((KeFeatureBits & KF_GLOBAL_32BIT_EXECUTE) != 0) ||
131                         (PsGetCurrentProcess()->Pcb.Flags.ExecuteEnable != 0) ||
132                         (((KeFeatureBits & KF_GLOBAL_32BIT_NOEXECUTE) == 0) &&
133                          (PsGetCurrentProcess()->Pcb.Flags.ExecuteDisable == 0))) {
134                         ExceptionRecord->ExceptionInformation [0] = 0;
135                     }
136                 }
137             }
138             break;
139     }
140 
141     //
142     // Select the method of handling the exception based on the previous mode.
143     //
144 
145     ASSERT ((
146              !((PreviousMode == KernelMode) &&
147              (ContextFrame.EFlags & EFLAGS_V86_MASK))
148            ));
149 
150     //--------------------------//
151     // 如果是内核模式触发的异常 //
152     //--------------------------//
153     if (PreviousMode == KernelMode) {
154 
155         //
156         // Previous mode was kernel.
157         //
158         // If the kernel debugger is active, then give the kernel debugger the
159         // first chance to handle the exception. If the kernel debugger handles
160         // the exception, then continue execution. Else attempt to dispatch the
161         // exception to a frame based handler. If a frame based handler handles
162         // the exception, then continue execution.
163         //
164         // If a frame based handler does not handle the exception,
165         // give the kernel debugger a second chance, if it‘s present.
166         //
167         // If the exception is still unhandled, call KeBugCheck().
168         //
169 
170         //----------------------------------//
171         // 给内核调试器第一次机会来处理异常 //
172         //----------------------------------//
173         if (FirstChance == TRUE) {
174 
175             if ((KiDebugRoutine != NULL) &&
176                (((KiDebugRoutine) (TrapFrame,
177                                    ExceptionFrame,
178                                    ExceptionRecord,
179                                    &ContextFrame,
180                                    PreviousMode,
181                                    FALSE)) != FALSE)) {
182                                    
183                 //------------------------------//
184                 // 如果处理成功,不进行下面处理 //
185                 //------------------------------//
186                 goto Handled1;
187             }
188 
189             // Kernel debugger didn‘t handle exception.
190 
191             //--------------------------------------//
192             // 如果第一次调试器不成功,则派发异常   //
193             // 当派发成功之后,也不会处理第二次异常 //
194             //--------------------------------------//
195             if (RtlDispatchException(ExceptionRecord, &ContextFrame) == TRUE) {
196                 goto Handled1;
197             }
198         }
199 
200         //
201         // This is the second chance to handle the exception.
202         //
203 
204         //--------------------------------//
205         // 第二次机会来调用调试器处理异常 //
206         //--------------------------------//
207         if ((KiDebugRoutine != NULL) &&
208             (((KiDebugRoutine) (TrapFrame,
209                                 ExceptionFrame,
210                                 ExceptionRecord,
211                                 &ContextFrame,
212                                 PreviousMode,
213                                 TRUE)) != FALSE)) {
214 
215             //------------------//
216             // 第二次也处理成功 //
217             //------------------//
218             goto Handled1;
219         }
220 
221         
222         //------------------------------------------------//
223         // 如果两次调试器处理和派发都不成功,系统直接蓝屏 //
224         //------------------------------------------------//
225         KeBugCheckEx(
226             KERNEL_MODE_EXCEPTION_NOT_HANDLED,
227             ExceptionRecord->ExceptionCode,
228             (ULONG)ExceptionRecord->ExceptionAddress,
229             (ULONG)TrapFrame,
230             0);
231 
232     } else {
233         //--------------//
234         // 用户模式异常 //
235         //--------------//
236 
237         //
238         // Previous mode was user.
239         //
240         // If this is the first chance and the current process has a debugger
241         // port, then send a message to the debugger port and wait for a reply.
242         // If the debugger handles the exception, then continue execution. Else
243         // transfer the exception information to the user stack, transition to
244         // user mode, and attempt to dispatch the exception to a frame based
245         // handler. If a frame based handler handles the exception, then continue
246         // execution with the continue system service. Else execute the
247         // NtRaiseException system service with FirstChance == FALSE, which
248         // will call this routine a second time to process the exception.
249         //
250         // If this is the second chance and the current process has a debugger
251         // port, then send a message to the debugger port and wait for a reply.
252         // If the debugger handles the exception, then continue execution. Else
253         // if the current process has a subsystem port, then send a message to
254         // the subsystem port and wait for a reply. If the subsystem handles the
255         // exception, then continue execution. Else terminate the process.
256         //
257 
258 
259         if (FirstChance == TRUE) {
260 
261             //
262             // This is the first chance to handle the exception.
263             //
264 
265             //-------------------------------------------------------------------------------//
266             // 调用内核调试器有两个条件:                                                     //
267             // 1. 当前存在一个调试器                                                         //
268             // 2. (当前进程的调试器端口为NULL && 不忽略异常) or 可以得到当前 ContextFrame )  //
269             //-------------------------------------------------------------------------------//
270             if ((KiDebugRoutine != NULL)  &&
271 
272                 //---------------------------------------------------------------//
273                 // 三环调试器最终会建立一个调试对象,挂在被调试进程的DebugPort处 //
274                 //---------------------------------------------------------------//
275                 ((PsGetCurrentProcess()->DebugPort == NULL &&
276                   !KdIgnoreUmExceptions) ||
277                 //-------------------------------------//
278                 //  判断R3层的INT3是否能进入内核调试器 //
279                 //-------------------------------------//
280                  (KdIsThisAKdTrap(ExceptionRecord, &ContextFrame, UserMode)))) {
281                 //
282                 // Now dispatch the fault to the kernel debugger.
283                 //
284 
285                 //------------------------//
286                 // 先尝试第一次调试器处理 //
287                 //------------------------//
288                 if ((((KiDebugRoutine) (TrapFrame,
289                                         ExceptionFrame,
290                                         ExceptionRecord,
291                                         &ContextFrame,
292                                         PreviousMode,
293                                         FALSE)) != FALSE)) {
294 
295                     goto Handled1;
296                 }
297             }
298 
299             
300             //---------------------------------------------------//
301             // 如果零环调试器无法处理 会调用三环调试器来进行处理 //
302             //---------------------------------------------------//
303             if (DbgkForwardException(ExceptionRecord, TRUE, FALSE)) {
304                 goto Handled2;
305             }
306 
307             //
308             // Transfer exception information to the user stack, transition
309             // to user mode, and attempt to dispatch the exception to a frame
310             // based handler.
311 
312             ExceptionRecord1.ExceptionCode = 0; // satisfy no_opt compilation
313 
314         //---------------------------------------------------------//
315         // 当内核调试器/用户调试器都处理不了该异常时               //
316         // 返回R3层的用户代码,尝试交给自定的try.catch. 来进行处理 //
317         // 对栈的存储跟APC操作类似                                 //
318         //---------------------------------------------------------//
319         repeat:
320             try {
321 
322                 //
323                 // If the SS segment is not 32 bit flat, there is no point
324                 // to dispatch exception to frame based exception handler.
325                 //
326 
327                 
328                 if (TrapFrame->HardwareSegSs != (KGDT_R3_DATA | RPL_MASK) ||
329                     TrapFrame->EFlags & EFLAGS_V86_MASK ) {
330                     //-----------------------------------//
331                     // 如果TrapFrame保存的不是用户层地址 //
332                     // 直接出触发二次异常                //
333                     //-----------------------------------//
334                     ExceptionRecord2.ExceptionCode = STATUS_ACCESS_VIOLATION;
335                     ExceptionRecord2.ExceptionFlags = 0;
336                     ExceptionRecord2.NumberParameters = 0;
337                     ExRaiseException(&ExceptionRecord2);
338                 }
339 
340                 //
341                 // Compute length of context record and new aligned user stack
342                 // pointer.
343                 //
344 
345                 //--------------------------------------//
346                 // 计算"对齐并提高用户栈的大小"后的指针 //
347                 //--------------------------------------//
348                 UserStack1 = (ContextFrame.Esp & ~CONTEXT_ROUND) - CONTEXT_ALIGNED_SIZE;
349 
350                 //
351                 // Probe user stack area for writability and then transfer the
352                 // context record to the user stack.
353                 //
354 
355                 //--------------------------------------------------------------------//
356                 // ProbeForWrite 检测是否可写入,如果可以写入则写入内存,否则抛出异常 //
357                 // 将Context结构保存到三环栈帧中,为进入零环时恢复三环的环境做准备    //                             //
358                 //--------------------------------------------------------------------//
359                 ProbeForWrite((PCHAR)UserStack1, CONTEXT_ALIGNED_SIZE, CONTEXT_ALIGN);
360                 RtlCopyMemory((PULONG)UserStack1, &ContextFrame, sizeof(CONTEXT));
361 
362                 //
363                 // Compute length of exception record and new aligned stack
364                 // address.
365                 //
366 
367                 //----------------------------//
368                 // 计算放入异常参数后栈的大小 //
369                 //----------------------------//
370                 Length = (sizeof(EXCEPTION_RECORD) - (EXCEPTION_MAXIMUM_PARAMETERS -
371                          ExceptionRecord->NumberParameters) * sizeof(ULONG) +3) &
372                          (~3);
373                 UserStack2 = UserStack1 - Length;
374 
375                 //
376                 // Probe user stack area for writeability and then transfer the
377                 // context record to the user stack area.
378                 // N.B. The probing length is Length+8 because there are two
379                 //      arguments need to be pushed to user stack later.
380                 //
381 
382                 //----------------------------------------------//
383                 // 将参数放入内核中 这里提升8因为后面还需要参数 //
384                 //----------------------------------------------//
385                 ProbeForWrite((PCHAR)(UserStack2 - 8), Length + 8, sizeof(ULONG));
386                 RtlCopyMemory((PULONG)UserStack2, ExceptionRecord, Length);
387 
388                 //
389                 // Push address of exception record, context record to the
390                 // user stack.  They are the two parameters required by
391                 // _KiUserExceptionDispatch.
392                 //
393 
394                 //----------------------------------------------------------------------//
395                 // 将上述两个栈地址压入栈中,将该参数是 _KiUserExceptionDispatch 需要的 //
396                 //----------------------------------------------------------------------//
397                 *(PULONG)(UserStack2 - sizeof(ULONG)) = UserStack1;
398                 *(PULONG)(UserStack2 - 2*sizeof(ULONG)) = UserStack2;
399 
400                 //
401                 // Set new stack pointer to the trap frame.
402                 //
403 
404                 //-------------------------------------------//
405                 // 修改_KTRAP_FRAME中寄存器ss与esp寄存器的值 //
406                 // 这样返回用户层时栈帧就是修改栈帧的数据了  //
407                 //-------------------------------------------//
408                 KiSegSsToTrapFrame(TrapFrame, KGDT_R3_DATA);
409                 KiEspToTrapFrame(TrapFrame, (UserStack2 - sizeof(ULONG)*2));
410 
411                 //
412                 // Force correct R3 selectors into TrapFrame.
413                 //
414 
415                 //-------------------------//
416                 // 修改 TrapFrame后的数据  //
417                 //-------------------------//
418                 TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, PreviousMode);
419                 TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
420                 TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
421                 TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, PreviousMode);
422                 TrapFrame->SegGs = 0;
423 
424                 //
425                 // Set the address of the exception routine that will call the
426                 // exception dispatcher and then return to the trap handler.
427                 // The trap handler will restore the exception and trap frame
428                 // context and continue execution in the routine that will
429                 // call the exception dispatcher.
430                 //
431 
432                 //--------------------------------------//
433                 // 修改到返回三环的地址                 //
434                 // KeUserExceptionDispathcer 这个函数中 //
435                 //--------------------------------------//
436                 TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher;
437                 return;
438 
439             } except (KiCopyInformation(&ExceptionRecord1,
440                         (GetExceptionInformation())->ExceptionRecord)) {
441 
442                 //
443                 // If the exception is a stack overflow, then attempt
444                 // to raise the stack overflow exception. Otherwise,
445                 // the user‘s stack is not accessible, or is misaligned,
446                 // and second chance processing is performed.
447                 //
448 
449                 if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) {
450                     ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
451                     RtlCopyMemory((PVOID)ExceptionRecord,
452                                   &ExceptionRecord1, sizeof(EXCEPTION_RECORD));
453                     goto repeat;
454                 }
455             }
456         }
457 
458         //
459         // This is the second chance to handle the exception.
460         //
461 
462         if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) {
463             goto Handled2;
464         } else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) {
465             goto Handled2;
466         } else {
467             ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
468             KeBugCheckEx(
469                 KERNEL_MODE_EXCEPTION_NOT_HANDLED,
470                 ExceptionRecord->ExceptionCode,
471                 (ULONG)ExceptionRecord->ExceptionAddress,
472                 (ULONG)TrapFrame,
473                 0);
474         }
475     }
476 
477     //
478     // Move machine state from context frame to trap and exception frames and
479     // then return to continue execution with the restored state.
480     //
481 
482 Handled1:
483 
484     KeContextToKframes(TrapFrame, ExceptionFrame, &ContextFrame,
485                        ContextFrame.ContextFlags, PreviousMode);
486 
487     //
488     // Exception was handled by the debugger or the associated subsystem
489     // and state was modified, if necessary, using the get state and set
490     // state capabilities. Therefore the context frame does not need to
491     // be transferred to the trap and exception frames.
492     //
493 
494 Handled2:
495     return;
496 }

 

以上是关于用户异常与模拟异常的派发的主要内容,如果未能解决你的问题,请参考以下文章

异常

使用实体框架迁移时 SQL Server 连接抛出异常 - 添加代码片段

用户模拟异常的记录

用户模拟异常的记录

片段中的 EditText 上的空指针异常 [重复]

异常和TCP通讯