



本章中,我们主要学习调试系统设计的整个流程,包括模拟调试系统和内核调试系统。当异常发生时,在分发异常阶段如何对异常进行处理?当异常交给内核模式调试后,内核引擎核心与远程调试器(WinDbg)之间如何通过调试协议进行交互通信?其中,调试包的接收和发送过程是本章的重点。如何用Visual Studio 2008对模拟调试系统进行初始化、跟踪,以及实现WinDbg和模拟调试系统的连接、调试,更是本章的亮点。
本章是全书最为精彩的一章,正是在这里实现了系统和远程调试器的连接。因为只有内核调试组件完成初始化后,远程调试器才有机会进行连接调试。系统和远程调试器连接之前,调试组件初始化的过程是跟踪不到的,所以依赖远程调试器来跟踪内核调试组件设计的流程是不可能的。
为了学习调试组件的设计,本书特别提供了两种版本跟踪调试组件的设计过程。第一种方法,不用多想,当然是bochsdbg,它能跟踪从MBR开始每一步的执行过程,内核调试的初始化过程肯定能跟踪到。但是调试组件的代码是用C语言设计的,bochsdbg加载不了符号文件,bochsdbg连调试纯汇编的SU模块都很费劲,更何况是C语言编成的KD模块呢?笔者当初使用的就是这种方法,跟踪过程极其复杂,费时费力,如果代码出现问题,还不能很快解决,所以不推荐使用这种方式。第二种方法就是在Visual Studio 2008中跟踪调试组件的初始化过程。KD组件增加了模拟内核调试系统的代码,它作为应用程序的一部分启动运行,从而进行初始化和连接远程调试器(应用程序和WinDbg是同一系统的Win32应用程序)。这样KD组件运行的代码能跟踪到,而且是以源码形式调试,非常理想,不但Visual Studio 2008能调试KD模块代码自身,WinDbg也能输入命令控制KD模块运行的流程,这种方式近乎完美,也是笔者最为欣赏的设计。
读者在学习本章时,可以直接像一般的控制台程序一样按F10键进行调试。第一种方法跟踪调试系统的初始化过程虽然费力不讨好,但它是最接近内核环境的,也是最正确的方法。第二种方法,虽然极为人性化,但也有一些美中不足,对大部分调试命令无效,毕竟调试系统模块只是应用程序的一部分,不属于内核,一些特权指令是运行不了的。所以,结合使用这两种方法极为重要。为了让两种方法的版本共享一份代码,在kd项目定义了“_WINKD_”和“SIMULATE_DEBUG”宏,由它指定调试系统运行在哪一种环境下,定义“_WINKD_”表明是在真实内核环境下,定义“SIMULATE_DEBUG”表明是在应用程序环境下。在不同环境下生成的kd.lib静态库是不同的版本,在使用时需要特别注意区分。
调试系统总体设计流程(参见图3.1)如下:当异常触发时,执行流程进入相应的异常处理函数。异常处理函数如果没有处理异常,则统一交给分发异常接口函数。由分发异常接口函数决定发给哪种模式调试,如果是用户模式,则交给用户调试;如果是内核模式,则交给内核调试。内核调试根据状态发给不同的处理函数,这些处理函数最后把异常交给内核调试引擎核心。内核调试引擎核心则和远程调试器实现交互式通信。当远程调试器交出控制权后,执行流程将从内核调试引擎核心原路返回。
    图3.1