前面我们已经小试了牛刀。这里我们将介绍调试器的基本操作。

Commands, extensions, etc.

有几种命令:普通命令(没有任何修饰),以句点开始的命令(“.”)和以一个惊叹号开始的命令(“!”)。Debugging Tools for Windows的帮助对这几种命令分别解析为普通命令(commands,译注:我称commands为普通命令),元命令(meta-commands)和扩展命令(extension commands)。现在我们的目的是熟悉这些常用的命令。

Breakpoints

断点是可使调试器在执行过程中中断下来的一种基本能力。这里有一些操作断点的方式:

  • 断在操作系统启动的时候
    怎么样使用WinDBG进行内核调试?

    在下次启动系统时,操作系统将中断在ntoskrnl启动之后,其他驱动载入之前,这时WinDbg将得到控制权。这个时候你可以对那些以boot方式启动的驱动程序下断点,让他们在启动的时候断下来。

      想让操作系统在启动早期断下来,你得先确认WinDbg已经连上了目标,按下组合键CTRL+ALT+K,然后你会看到:
  • 普通断点
    bp MyDriver!xyz
    bp f89adeaa

    第1句表示断点设在一个指定模块的函数上(!);第2句表示断点设在指定地址上。当程序执行到这些断点上时操作系统将中断并把控制权交给WinDbg.(你将在finding a name小节看到第2条命令的地址是怎么取得的.)

    注意:第1条命令使用的语法只能在操作系统已经加载了这个模块并且符号文件中提供了识别xyz的足够信息或xyz是导出来的名字。如果在指定模块中找不到xyz的话调试器将告诉我们。

      平常使用的最简单的断点通过bp(“BreakPoing”)命令来下。例如:
  • 推迟断点(Deferred breakpoints)
    bu sioctl!SioctlDeviceControl

    SioctlDeviceControl的地方可以是sioclt.sys的入口点或其他函数的名字。当这个模块被加载时我们假定有足够的信息识别出来SioctlDeviceControl是有效的,因此这个断点将被设置。(如果这个模块已经加载并且找到了那个名字,调试器将会立即将断点设置在上面。)如果操作系统不能找到SioctlDeviceControl,调试器将会报告给我们,并且调试器也不会在SioctlDeviceControl停下来。

    跟普通断点相比,推迟断点的特色是把对modules!names的操作延迟到模块加载时进行地址解析,而普通断点是对地址进行操作,或直接把modules!names转换为地址。推迟断点的另一个特性是系统重启之后还能记住它们(不是记住的已经转换的地址)。这个特性使得推迟断点在模块被卸载之后仍然被会记得,而普通断点就不行了,当模块被卸载之后断点同时会被移除。

      驱动程序还没加载时你的第1个断点是通过bu设置的(看前面的starting to debug the sample driver章节),我们称这个为“推迟(deferred)”断点。bu命令将!作为一个参数,例如:
  • 另一个设置普通断点的方法是直接在源码窗口中按F9。以刚才我们的sioctl.sys为例,当你停在DriverEntry的时候,可以滚动源码窗口到你想要设置断点的地方,然后按F9: 怎么样使用WinDBG进行内核调试?

    上面变成红色的那行就是你通过F9设置的断点。

  • 当你想查看已经设置的断点时,可以使用bl(“Breakpoint Line”)命令:
    kd> bl
    0 e [d:/winddk/3790/src/general/ioctl/sys/sioctl.c @ 123] 0001 (0001) SIoctl!DriverEntry
    1 e [d:/winddk/3790/src/general/ioctl/sys/sioctl.c @ 338] 0001 (0001) Sioctl!SioctlDeviceControl+0x103

    注意上面列出的断点:每个断点前面显示有一个编号和状态,“e”代表断点为“激活”状态,“d”代表断点为“禁用”状态。

  • 假如你现在想暂时禁用一个断点,那么bd命令可以如你所愿。bd命令需要在后面指定一个断点编号,如下:
    kd> bd 1
    kd> bl
    0 e [d:/winddk/3790/src/general/ioctl/sys/sioctl.c @ 123] 0001 (0001) SIoctl!DriverEntry
    1 d [d:/winddk/3790/src/general/ioctl/sys/sioctl.c @ 338] 0001 (0001) SIoctl!SioctlDeviceControl+0x103
  • 如果想永久的清除编号为1的断点,可以使用bc(“Clear Breakpoint”)命令,与bd类似,需要在后面指定一个断点的编号。输入 bd 1后,编号为1号断点将从断点列表中消失。
  • 上面我们完成的相当好。然而,有时我们设置的断点可能在操作系统或驱动调用相当频繁的地方,这时我们就想给断点指定一个条件或条件满足时的动作,这样仅当条件满足时才触发断点。像这样:
    bp SIoctl!SioctlDeviceControl+0x103 "j (@@(Irp)=0xffb5c4f8) ''; 'g'"

    上面命令的意思是说:当Irp指向的地址为0xFFB5C4F8才在SIoctl!SioctlDeviceControl+0×103的地方中断,否则继续执行。

    更进一步研究上面的指令,其实断点本身不具备条件判断能力(译注:意思应该是说bp指令不像softice的bpx指令那样直接支持条件断点吧),但断点有一个行为子句(双引号中),因为子句中的命令J是一个条件判断指令(命令j相当于是一个IF/ELSE)。j命令可以在条件表达式为TRUE或FALSE的时候执行不同的子句(单引号中的部份,译注:学过程序设计的人都知道分支吧?)。上面的命令,当条件表达式的值为TRUE的时候什么都不做(第1个行为子句是空的),因此WinDbg在条件满足时就断下来了。当条件表达式的值为FALSE时执行第2个子句“命令g”,让WinDbg不继续运行。WinDbg就是这样实现条件断点的。

    看下面有少许变化的指令:

    bp SIoctl!SioctlDeviceControl+0x103 "j (@@(Irp)=0xffb5c4f8) '.echo Found the interesting IRP' ; '.echo Skipping an IRP of no interest; g' "

    当条件为TRUE时会输出一条消息并断下。当为FALSE时也会输出一条消息并继续 (WinDbg会继续运行,仅会输出一条消息)。

    注意下面的断点指令,它打算跟Eax的值进行比较(你可以在registers章节找到更多的寄存器的描述),然而它不会像你所期望的那样工作:

    bp SIoctl!SioctlDeviceControl+0x103 "j (@eax=0xffb5c4f8) '.echo Here!' ; '.echo Skipping; g' "

    产生这个问题的原因是“符号扩展”,它把寄存器的值扩展到了64位,即0xFFB5C4F8扩展成了0xFFFFFFFF`FFB5C4F8,所以它跟0×00000000`FFB5C4F8等不起来。仅当32位的最高位为1时才会导致这个问题发生。Debuggint Tools for Windows的帮助中有关于“符号扩展”问题的详细说明(参见“Setting a Conditional Breakpoint” there”章节)。

    我们还可以给断点加上一些限制,如“一次性”断点,这种断点仅有一次有效(当它被中断过后立即就会被清除),这对那些调用非常频繁的代码片段来说只中断1次很方便:

    bp /1 SIoctl!SioctlDeviceControl+0x103

    其他对我们有用的断点限制,如针对一个进程或线程所下的断点:

    bp /p 0x81234000 SIoctl!SioctlDeviceControl+0x103
    bp /t 0xff234000 SIoctl!SioctlDeviceControl+0x103

    上面两条指令各自的意思是,如果进程块(EPROCESS)是在0×81234000的时候这个断点才会被触发,如果线程块(ETHREAD)是0xFF234000的时候这个断点才会被触发。

    我们可以把多种限制加在断点上:

    bp /1 /C 4 /p 0x81234000 SIoctl!SioctlDeviceControl+0x103

    这句指令的意思是,当调用栈的深度大于4时(这里的“C”是大写,代表大于,如果是小写的“c”代表小于),且进程块在0×81234000时触发这个断点,且只断一次。

  • 另一种不太一样的断点需要指定访问方式的,例如:
    ba w4 0xffb5c4f8+0x18+0x4

    正如你所看到的,这个地址来自于IRP,在IRP偏移0×18+0×14就是IoStatus.Information成员。所以这个断点将在有代码试图在向IoStatus.Information中写数据的时候被触发。这种断点称为数据断点(因为它们在数据访问时被触发)或处理器断点(因为这是处理器来触发的调试事件,而不是调试器)。

    未完待续。。。