蓝弧计算机维护网:提供电脑维护,企业局域网建设,病毒防御与查杀,企业网站制作及维护推广,电脑维修等方案  
首页 |  解决方案 |  服务项目 |  服务流程 |  IT外包 |  维护宝典 |  收费标准 |  关于我们 
     
 
电脑维护之Windows异常处理流程(中)
 
  当发生异常后,CPU记录当前各寄存器状态并在内核堆栈中建立陷阱帧TrapFrame,然后将控制交给对应异常的陷阱处理程序。当陷阱处理程序能处理异常时,比如缺页时通过调页程序MmAccessFault将页换入物理内存后通过iret返回发生异常的地方。但大多数无法处理异常,这时先是调用CommonDispatchException在内核堆栈中建立异常记录ExceptionRecord和异常帧ExceptionFrame。ExceptionRecord很重要,它记录了异常代码、异常地址以及一些其它附加的参数。然后调用KiDispatchException进行异常的分派。这个函数是WINDOWS下异常处理的核心函数,负责异常的分派处理。

  KiDispatchException的处理流程(每当异常被某个例程处理时处理的例程将返回TRUE到上一个例程,未处理则返回FALSE。当任何一个例程处理了异常返回TRUE时,则KiDispatchException正常返回):

  在进行用户态内核态的异常的分派前,先判断异常是否来自用户模式,是的话将Context.ContextFlags(这时候Context结构还刚初始化完,还未赋初值) or上CONEXT_FLOATING_POINT,意味着对来自用户模式的异常总是尝试分派浮点状态,这样可以允许异常处理程序或调试器检查和修改协处理器的状态。然后从陷阱帧中取出寄存器值填入Context结构,并判断是否是断点异常(int 0x3和int 0x2d),如果是的话先将Context.Eip减一使它指向int 0x3指令(无论是由int 0x3还是由int 0x2d引起的异常,因为前面的陷阱处理程序里已经改变过TrapFrame里面的Eip了)。然后判断异常是发生于内核模式还是用户模式,根据不同模式而采取不同处理过程。

  如果异常发生于内核模式,会给予内核调试器第一次机会和第二次机会处理异常。当异常被处理后就将设置好陷阱帧并返回到陷阱处理程序,在那里iret返回发生异常的地方继续执行。

  内核模式异常处理流程为:

  (第一次机会)判断KiDebugRoutine是否为空,不为空就将Context、陷阱帧、异常记录、异常帧、发生异常的模式等压入栈并将控制交给KiDebugRoutine。
    |
    |
    |
  若KiDebugRoutine为空(正常的系统这里不为空。正常启动的系统KiDebugRoutine为KdpStub,在Boot.ini里加上/DEBUG启动的系统的KiDebugRoutine为KdpTrap。如果这里为空的话会因为处理不了DbgPrint这类int 0x2d产生的异常而导致系统崩溃)或者KiDebugRoutine未处理异常,则将Context结构和异常记录ExceptionRecord压栈并调用内核模式的RtlDispatchException在内核堆栈中查找基于帧的异常处理例程。
    |
    |
    |    
  RtlDispatchException调用RtlpGetRegistrationHead从fs:[0](0xffdff000)处获取当前线程异常处理链表指针,并调用RtlpGetStackLimits从0xffdff004和0xffdff008取出当前线程堆栈底和顶。然后开始由异常处理链表指针遍历链表查找异常处理例程(若在XP和2003下先处理VEH再处理SEH),其实这就是SEH,只是和用户态有一点不同是既没有顶层异常处理例程(TOP LEVEL SEH)也没有默认异常处理例程。然后对每个当前异常处理链表指针检查判断堆栈是否有效(是否超出了堆栈范围或者未对齐)及堆栈是否是DPC堆栈。若0xffdff80c处DpcRoutineActive为TRUE且堆栈顶和底在0xffdff81c处取出的DpcStack到DpcStack-0x3000(一个内核堆栈大小),若是则更新堆栈顶和底为DpcStack和DpcStack-0x3000并继续处理,否则将异常记录结构里的异常标志ExceptionRecord.ExceptionFlags设置EXCEPTION_STACK_INVALID表示为无效堆栈并返回FALSE。
    |
    |
    |
    |
  调用异常处理链表上的异常处理例程之前会在异常处理例程链表上插入一个新的节点,对应的异常处理例程是用来处理嵌套异常,也就是在处理异常时发生另一个异常。处理后
RtlDispatchException判断异常处理例程的返回值:
  若为ExceptionContinueExecution,若异常标志ExceptionRecord.ExceptionFlags未设置EXCEPTION_NONCONTINUABLE不可恢复执行,则返回TRUE到上一层,否则在做了一些工作后调用RtlRaiseException进入到KiDispatchException的第二次机会处理部分。
  若为ExceptionContinueSearch,则继续查找异常处理例程。
  若为ExceptionNestedException,嵌套异常。保留当前异常处理链表指针为内层异常处理链表并继续查找异常处理例程。当发现当前异常处理链表地址大于保留的内层异常处理链表时,表示当前的异常处理链表比保留的更内层(因为堆栈是由高向低扩展的,地址越高则入栈越早,表示更内层),则将其值赋予内层异常处理链表指针,除了第一次赋初值外发生修改保留的内层异常处理链表指针这种情况表示嵌套了不止一次的异常。当搜索到新的异常处理链表指针和保留的内层异常处理链表指针相同,则清除ExceptionRecord.ExceptionFlags的嵌套异常位(&(~EXCEPTION_NESTED_CALL)),表示嵌套的异常已经处理完。
  其它的返回值都视为无效,调用RtlRaiseException回到KiDispatchException的第二次机会处理部分。
  当异常处理链表指针为0xffffffff时,异常处理例程链表已到头。
    |
    |
    |
  若RtlDispatchException无法处理异常,(第二次机会)判断KiDebugRoutine是否为空,不为空则交给KiDebugRoutine处理。
    |
    |
    |
  当所有例程都无法处理异常时,调用KeBugCheckEx蓝屏,错误代码为KMODE_EXCEPTION_NOT_HANDLED,表示谁也没有处理异常,系统视这个异常为无法恢复的致命异常。至此内核模式下异常处理完毕。

  若异常发生在用户模式,同样给予调试器第一次机会和第二次机会调试。只不过因为调试器是用户态调试器,所以通过LPC发送消息给会话管理器smss.exe,再由会话管理器将消息转发给调试器。

 
 
  更多内容
  •  电脑维护之Windows异常处理流程(上)
  •  电脑维护之Windows异常处理流程(下)
  •  Win 2000操作中Hosts文件的作用