⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 761.html

📁 里面收集的是发表在www.xfocus.org上的文章
💻 HTML
📖 第 1 页 / 共 3 页
字号:
<br />
&nbsp;&nbsp;&nbsp;&nbsp;在进行用户态内核态的异常的分派前,先判断异常是否来自用户模式,是的话将Context.ContextFlags(这时候Context结构还刚初始化完,还未赋初值) or上CONEXT_FLOATING_POINT,意味着对来自用户模式的异常总是尝试分派浮点状态,这样可以允许异常处理程序或调试器检查和修改协处理器的状态。然后从陷阱帧中取出寄存器值填入Context结构,并判断是否是断点异常(int 0x3和int 0x2d),如果是的话先将Context.Eip减一使它指向int 0x3指令(无论是由int 0x3还是由int 0x2d引起的异常,因为前面的陷阱处理程序里已经改变过TrapFrame里面的Eip了)。然后判断异常是发生于内核模式还是用户模式,根据不同模式而采取不同处理过程。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;如果异常发生于内核模式,会给予内核调试器第一次机会和第二次机会处理异常。当异常被处理后就将设置好陷阱帧并返回到陷阱处理程序,在那里iret返回发生异常的地方继续执行。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;内核模式异常处理流程为:<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;(第一次机会)判断KiDebugRoutine是否为空,不为空就将Context、陷阱帧、异常记录、异常帧、发生异常的模式等压入栈并将控制交给KiDebugRoutine。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;若KiDebugRoutine为空(正常的系统这里不为空。正常启动的系统KiDebugRoutine为KdpStub,在Boot.ini里加上/DEBUG启动的系统的KiDebugRoutine为KdpTrap。如果这里为空的话会因为处理不了DbgPrint这类int 0x2d产生的异常而导致系统崩溃)或者KiDebugRoutine未处理异常,则将Context结构和异常记录ExceptionRecord压栈并调用内核模式的RtlDispatchException在内核堆栈中查找基于帧的异常处理例程。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;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。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;调用异常处理链表上的异常处理例程之前会在异常处理例程链表上插入一个新的节点,对应的异常处理例程是用来处理嵌套异常,也就是在处理异常时发生另一个异常。处理后<br />
RtlDispatchException判断异常处理例程的返回值:<br />
&nbsp;&nbsp;&nbsp;&nbsp;若为ExceptionContinueExecution,若异常标志ExceptionRecord.ExceptionFlags未设置EXCEPTION_NONCONTINUABLE不可恢复执行,则返回TRUE到上一层,否则在做了一些工作后调用RtlRaiseException进入到KiDispatchException的第二次机会处理部分。<br />
&nbsp;&nbsp;&nbsp;&nbsp;若为ExceptionContinueSearch,则继续查找异常处理例程。<br />
&nbsp;&nbsp;&nbsp;&nbsp;若为ExceptionNestedException,嵌套异常。保留当前异常处理链表指针为内层异常处理链表并继续查找异常处理例程。当发现当前异常处理链表地址大于保留的内层异常处理链表时,表示当前的异常处理链表比保留的更内层(因为堆栈是由高向低扩展的,地址越高则入栈越早,表示更内层),则将其值赋予内层异常处理链表指针,除了第一次赋初值外发生修改保留的内层异常处理链表指针这种情况表示嵌套了不止一次的异常。当搜索到新的异常处理链表指针和保留的内层异常处理链表指针相同,则清除ExceptionRecord.ExceptionFlags的嵌套异常位(&amp;(~EXCEPTION_NESTED_CALL)),表示嵌套的异常已经处理完。<br />
&nbsp;&nbsp;&nbsp;&nbsp;其它的返回值都视为无效,调用RtlRaiseException回到KiDispatchException的第二次机会处理部分。<br />
&nbsp;&nbsp;&nbsp;&nbsp;当异常处理链表指针为0xffffffff时,异常处理例程链表已到头。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;若RtlDispatchException无法处理异常,(第二次机会)判断KiDebugRoutine是否为空,不为空则交给KiDebugRoutine处理。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;当所有例程都无法处理异常时,调用KeBugCheckEx蓝屏,错误代码为KMODE_EXCEPTION_NOT_HANDLED,表示谁也没有处理异常,系统视这个异常为无法恢复的致命异常。至此内核模式下异常处理完毕。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;若异常发生在用户模式,同样给予调试器第一次机会和第二次机会调试。只不过因为调试器是用户态调试器,所以通过LPC发送消息给会话管理器smss.exe,再由会话管理器将消息转发给调试器。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;用户模式异常处理流程:<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;若KiDebugRoutine不为空,则不为空就将Context、陷阱帧、异常记录、异常帧、发生异常的模式等压入栈并将控制交给KiDebugRoutine。当处理完毕用Context设置陷阱帧并返回到上一级例程。(第一次机会)否则把异常记录压栈并调用DbgkForwardException,在DbgkForwardException里判断当前线程ETHREAD结构的HideFromDebugger成员如果为FALSE(为TRUE表示该异常对用户调试器不可见)则向当前进程的调试端口(DebugPort)发送LPC消息。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;当上一步无法处理异常时将Context结构拷贝到用户堆栈,在堆栈中设置一个陷阱帧,陷阱帧的Eip为Ke(i)UserExceptionDisptcher((i)表示这个函数的Ke和Ki打头的符号其实是一回事),接着返回陷阱处理程序,由陷阱处理程序iret返回用户态执行Ke(i)UserExceptionDispatcher(这个函数虽然是Ke(Ki)打头,却不是内核里的函数。同样性质特殊的还有Ke(i)RaiseUserExceptionDispatcher、Ke(i)UserCallbackDispatcher、Ke(i)UserApcDispatcher。它们的共同特点就是不是被调用的,而是由内核例程设置了陷阱帧TrapFrame.Eip为该函数后iret执行到这里的)。Ke(i)UserExceptionDisptcher调用RtlDispatchException(用户态下的)寻找堆栈中基于帧的异常处理例程(若在XP和2003下先处理VEH再处理SEH),这个流程大家应该很熟了,就是搜索SEH链表,若都不处理就调用顶层异常处理(TOP LEVEL SEH)例程。当再无法处理时就调用默认异常处理例程终止进程(有VC时这里就换成了VC)。有点不同的是用户态下的RtlDispatchException只判断返回值是ExceptionContinueExecution还是ExceptionContinueSearch。若RtlDispatchException找到异常处理例程能够处理异常,则调用ZwContinue按照设置好的Context结构继续执行,否则调用ZwRaiseException,并且把第三个布尔参数设为FALSE,表示进入第二次机会处理。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;ZwRaiseException经过一系列调用最后直接调用KiDispatchException,由于把布尔值FirstChance设置为FALSE,在KiDispatchException里直接进入第二次机会处理。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;(第二次机会)向进程的DebugPort发消息,若无法处理,则改向进程的ExceptionPort发消息(这里同样如果该异常对用户调试器不可见,则只会发送到ExceptionPort)。DebugPort和ExceptionPort的区别在于,若向ExceptionPort发消息,先停止目标进程所有线程的执行,直到收到回应消息后线程才恢复执行,而向DebugPort发消息则不需要停止线程运行。还有DebugPort是向会话管理器发消息,而ExceptionPort是向Win32子系统发消息,当向ExceptionPort发消息时,已经不给用户态调试器任何机会了:)。<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />
&nbsp;&nbsp;&nbsp;&nbsp;当异常还是无法处理,则终止当前线程,并调用KeBugCheckEx蓝屏,错误代码为KMODE_EXCEPTION_NOT_HANDLED。至此用户模式下异常处理完毕。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;有一点要说明的是,这里只是列出了操作系统的流程。如果现在去看比方说驱动或者一个应用程序里,加入了__try/__except代码,却没发现SEH上的节点对应的异常处理例程和自己写的有啥关系。其中的原因就是因为M$的编译器(比方说VC++、DDK)在系统内部封装了SEH的机制。在异常处理例程链表节点上的异常处理例程实际上是_except_handler3,这个函数自己在内部又实现了一套类似SEH的机制,就是通过对每个函数建立一个表,包括了该函数中所有__try块对应的过滤异常例程指针(__try()括号里的对应函数,如果是括号里是EXCEPTION_EXECUTE_HANDLER之类的则该过滤异常例程很简单地把eax赋为相应的1、0或-1然后返回,对应了EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_SEARCH、EXCEPTION_CONTINUE_EXECUTE)和处理例程指针(__except{}和__finally{}里面代码的地址)。所以对应每个被调用到的函数都在SEH链表上注册一个节点,并建立一张表。象诸如EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_SEARCH、EXCEPTION_CONTINUE_EXECUTE实际上是返回给_except_handler3的返回值,而不是返回给RtlDispatchException的。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;看得有点晕吧,呵呵。总结一下流程(指异常未被处理的完整流程,若异常在任一个环节被处理都从该环节上退出继续原来正常程序代码的执行):<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;内核模式下:<br />
&nbsp;&nbsp;&nbsp;&nbsp;KiDispatchException-&gt;(第一次机会)KiDebugRoutine-&gt;RtlDispatchException在内核堆栈寻找SEH(VEH)-&gt;(第二次机会)KiDebugRoutine-&gt;KeBugCheckEx<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;用户模式下:<br />
&nbsp;&nbsp;&nbsp;&nbsp;KiDispatchException-&gt;KiDebugRoutine-&gt;(第一次机会)发送消息到进程调试端口-&gt;RtlDispatchException在用户堆栈寻找SEH(VEH)-&gt;ZwRaiseException回到KiDispatchException-&gt;(第二次机会)发送消息到进程调试或异常端口-&gt;KeBugCheckEx。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;就这么简单,呵呵。举个例子来说明异常的处理。<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;我们程序中通过加入int 0x3来对程序进行调试,那么int 0x3产生的异常是怎么处理的呢?<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;若int 0x3发生在内核模式下(驱动程序里),又分为内核调试器是否加载。内核调试器未加载时,KiDebugRoutine(KdpStub)不处理int 0x3异常,则在堆栈中搜索由驱动程序编写者注册的SEH,若也没有对int 0x3异常的处理,则只好调用KeBugCheckEx蓝屏,错误代码是KMODE_EXCEPTION_NOT_HANDLED(谁都不干活,系统只好悲愤地自我了结了)。若有内核调试器加载,则KiDebugRoutine(KdpTrap)可以处理int 0x3异常,异常处理到这里被正常返回。处理方法是将当前的处理器状态(比如各寄存器等重要信息)发送给通过串口相连的主机上的内核调试器,并一直在等待内核调试器的回应,系统这时在KdpTrap里调用一个Kd函数KdpSendWaitContinue循环等待来自串口的数据(内核调试器和被调试系统通过串口联系),直到内核调试器下达继续执行的命令,系统可以正常从int 0x3后面一条指令执行。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;若int 0x3发生在用户模式下,也分为用户调试器是否加载。用户调试器未加载时,KiDebugRoutine不处理int 0x3异常,且用户进程无DebugPort,将记录异常的相关结构拷入用户堆栈交由用户模式的RtlDispatchException搜索用户堆栈中的SEH。若也没有对int 0x3异常的处理,则调用默认异常处理例程,默认情况下是将发生异常的进程终止。若有用户调试器加载,则向进程的DebugPort或ExceptionPort发送有关异常的LPC消息并判断发送函数的返回状态,若用户调试器继续执行,则返回STATUS_SUCCESS,内核视为异常已解决继续执行。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;这里也说明了一点,就是相同的错误,发生在內核态下比发生在用户态下致命得多。其它异常的处理流程基本也一样。少数不一样的异常象DebugPrint、DebugPrompt、加载和卸载symbol等是通过调用DebugService的(这实际上是通过产生一个异常int 0x2d)。可以在KdpStub中就被处理(处理很简单,只是把Context结构的Eip加一略过当前int 0x2d后那条int 0x3指令),若在KdpTrap中被处理则是和内核调试器更进一步的交互。关于KdpStub、KdpTrap和DebugService更详细的介绍参见我的另一篇文章《windows内核调试器原理浅析》。<br />
<br />
后记:<br />
&nbsp;&nbsp;&nbsp;&nbsp;前几天翻硬盘时找出了这篇半年前写了大半的文章。当时因为还有些不明白的东西,所以没写完,结果一放就放了半年。现在翻出来写完后弄明白了不少东西,也把心得拿出来供大家参考,就当是抛砖引玉吧:)<br />
<br />
QQ:27324838<br />
EMAIL:kinvis@hotmail.com
	</td>
  </tr>
</table>
<div class="footer">
  Copyright &copy; 1998-2003 XFOCUS Team. All Rights Reserved
</div>
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -