cc682/NetRoc
http://netroc682.spaces.live.com/
调试器操作(内核模式)
本节包含以下主题:
崩溃和重起目标机
和目标机同步
改变上下文
映射驱动文件
本地内核调试
崩溃和重起目标机
进行内核调试时,使用.crash (Force System Crash) 命令可以造成目标机停止响应(即崩溃或bug check)。该命令会使目标机立即停止响应。如果已启用崩溃转储,调试器会创建一个内核模式dump文件。(关于这些文件的更多信息,查看创建内核模式dump文件。)
使用.reboot (Reboot Target Computer) 命令重起目标机。
如果想目标机创建一个dump文件之后重起,可以先使用.crash 命令,然后再使用.reboot命令。如果只是想重起,则不需要.crash命令。
在引导进程的初期,主控机和目标机之间的连接会丢失。调试器不能接收到目标机的信息。
连接断开之后,调试器会关闭所有符号文件,并卸载所有调试器扩展。这时如果运行的是KD或CDB,所有断点都会丢失。在WinDbg中,可以保存当前工作空间,这样可以保存所有断点。
如果这时想结束调试会话,使用CTRL+B 命令( KD中)或点击File菜单中的Exit(在WinDbg中)。
如果不退出调试器,当引导进程到达一定程度时会重新建立连接。这时符号和扩展会重新加载起来。如果运行WinDbg,内核模式工作空间也会重新加载。
可以让调试器在引导过程中的两个可能的时间点自动中断目标机:
要在第一个内核模块加载时自动中断,使用-d
命令行选项。
要设置内核初始化时的自动断点,使用-b
命令行选项。
也可以在调试器运行时改变中断情况:
和目标机同步
有时候在内核模式调试时,目标机会停止响应调试器。
在KD中,可以按下CTRL+R (Re-synchronize) 再按下ENTER来和目标机同步。在WinDbg中,使用CTRL+ALT+R 或Debug | Kernel Connection | Resynchronize。
这些命令可以重建主控机和目标机的连接。但是,重新同步可能并不总是成功,特别在使用1394内核连接时。
.restart (Restart Kernel Connection) 命令提供了更加强大的重新同步方法。该命令等同于退出调试器并重新附加一个新的调试器到存在的会话上。该命令仅在KD中支持,而不在WinDbg中支持。
.restart 命令在进行通过remote.exe的远程调试时很有用。 (在这种调试中,要结束和重起调试器可能很困难。)但是,如果使用通过调试器的远程调试,则可以在调试客户端使用.restart命令。
改变上下文
在内核模式调试时,同时有很多进程、线程有时也有很多用户会话在运行。所以,例如"虚拟地址 0x80002000"或" eax 寄存器"这样的短语都是不明确的。必须指定这些短语所在的上下文,才可以被理解。
调试器在调试时有5种不同的上下文可以设置:
- 会话上下文(session context)指定默认的用户会话。 (该上下文仅在Microsoft Windows XP和之后的Windows中具有。这些系统允许同时有多个登陆会话共存。)
- 进程上下文(process context) 用于决定调试器如何解释虚拟地址。
- 用户模式地址上下文(user-mode address context)几乎从来不会直接设置。该上下文在改变进程上下文时自动设置。
- 寄存器上下文(register context)决定调试器如何 解释寄存器,也用于控制堆栈跟踪的结果。该上下文也称为线程上下文,尽管这个术语不是完全准确。显式上下文(
explicit context)也是一种寄存器上下文。如果指定了显式上下文,则它会取代当前的寄存器上下文。
- 局部上下文(local context)决定调试器如何解释局部变量。该上下文也称为作用域。
会话上下文
在Windows XP和之后版本的Windows中,同时可以运行多个登陆会话。每个登陆会话都有它自己的进程。
!session 扩展命令显示所有登陆会话或改变当前会话上下文。
当!sprocess 和!spoolused 扩展命令输入的会话号为"-2"时,会用到会话上下文。
会话上下文改变时,进程上下文会自动切换到该会话的活动进程。
进程上下文
每个进程都有自己的页目录,用于记录虚拟地址如何映射到物理地址的信息。当一个进程中的任何线程执行时,Windows操作系统使用页目录来解释虚拟地址。
用户模式调试时,当前进程决定了进程上下文。调试器命令、扩展命令和调试信息窗口都会使用当前进程的页目录来解释虚拟地址。
在内核模式调试时,使用.process (Set Process Context) 命令来设置进程上下文。该命令用来选择解释虚拟地址时使用哪个进程的页目录。设置了进程上下文滞后,任何用到地址的命令中都使用该上下文。也可以在该地址中设置断点。通过使用.process 和/i 选项来指定入侵式调试,在内核调试器里面也可以设置用户空间的断点。
也可以在内核空间函数上使用指定进程的断点来在内核调试器中设置用户模式断点。设置这种断点并等待切换到适合的上下文。
用户模式地址上下文是进程上下文的一部分。一般来说不用直接设置用户模式地址上下文。如果设置了进程上下文,用户模式地址上下文会自动改变为使用该进程关联的页表。但是,在基于Itanium的处理器上,一个进程可能拥有多于一个的页目录。这种情况下,可以使用.context (Set User-Mode Address Context) 命令来改变用户模式地址上下文。
在内核模式调试时设置了进程上下文之后,它会一直保持到使用另一个.process命令改变为止。用户模式地址上下文也会保持到使用.process 或.context 命令改变为止。这些上下文在目标机运行时都不会改变,也不会因为改变寄存器上下文和局部上下文而受影响。
寄存器上下文
每个线程都有它自己的寄存器值。当线程执行时这些值保存在CPU寄存器中;当其他线程运行时,则保存在内存中。
用户模式调试时,当前线程一般决定了寄存器上下文。所有调试器命令、扩展命令和调试信息窗口中对寄存器的引用都根据当前线程的寄存器来解释。
使用下面的方法之一,在用户模式调试时可以将寄存器上下文改变为当前线程之外的值:
.cxr (Display Context Record)
.ecxr (Display Exception Context Record)
内核模式调试时,可以使用包括下面一些命令在内的多种命令来控制寄存器上下文:
.thread (Set Register Context)
.cxr (Display Context Record)
.trap (Display Trap Frame)
这些命令不改变CPU寄存器的值,调试器是从内存位置中获得指定的寄存器上下文。实际上,调试器只能找到已保存的寄存器值。(其他值是动态设置的并且不会保存下来。重建堆栈跟踪使用已保存的值就足够了。)
寄存器上下文设置之后,任何使用寄存器值的命令都会使用新的寄存器上下文,例如k (Display Stack Backtrace) 和r (Registers)。
但是,当调试多处理器的计算机时,一些命令可以指定处理器。 (关于这些命令的更多信息,查看多处理器语法。)如果为一条命令指定了处理器,该命令使用指定的处理器上的激活线程的寄存器上下文,而不是当前寄存器上下文,即使指定的处理器就是活动处理器。
同样,如果寄存器上下文和当前处理器模式设置不匹配,这些命令会产生错误或无意义的输出。要避免这种输出错误,在将处理器模式设置为和寄存器上下文匹配之前,这些依赖寄存器状态的命令都会失败。使用.effmach (Effective Machine) 命令改变处理器模式。
改变寄存器上下文也会改变局部上下文。所以,寄存器上下文会影响局部变量的显示。
如果任何程序发生执行、单步或跟踪的事件,寄存器上下文会立即重设为和程序计数器的位置匹配。在用户模式下,当前进程或线程改变时,寄存器上下文也会重设。
寄存器上下文也作用于堆栈跟踪,因为堆栈跟踪是从堆栈寄存器(基于x86处理器上的esp或基于Itanium处理器上的sp)的指向的位置开始的。如果寄存器上下文设置为非法或不可访问的值,就不能进行堆栈跟踪。
可以使用.apply_dbp (Apply Data Breakpoint to Context) 命令在指定的寄存器上下文中设置数据断点。
局部上下文
程序运行时,局部变量的意义由程序计数器来决定,因为这些变量的作用域仅在定义它们的函数内部。
进行用户模式或内核模式调试时,调试器使用当前函数的作用域(堆栈上的当前帧)作为局部上下文。使用.frame (Set Local Context) 命令或者在Calls window中双击需要的帧来改变局部上下文。
在用户模式调试时,局部上下文总是当前线程的堆栈回溯中的一帧。在内核模式调试时,局部上下文总是当前寄存器上下文的线程的堆栈回溯中的一帧。
同一时刻只能使用一个堆栈帧作为局部上下文。位于其他帧中的局部变量不能被访问。
下面这些事件发生时,局部上下文会被重置:
- 任何程序执行、单步或跟踪的动作
- 任何命令中使用线程限定符(~)
- 对寄存器上下文的任何改变
!for_each_frame 扩展命令可以对堆栈中的每一个帧使用一条单独的命令。该命令为每一个帧改变局部上下文,执行命令,然后将局部上下文设置为原始值。
映射驱动文件
更换驱动文件可能是困难的。经常需要启动到Microsoft Windows安全版(safe build),替换驱动的二进制文件,然后再重新启动。
但是,Windows XP和之后版本的Windows支持一种更加简单的替换驱动文件的方法。可以使用它来替换任何内核模式驱动(包括显示驱动)、任何Windows子系统驱动或其他任何内核模式模块。本主题为了简单,将这些文件统称为驱动,但是可以对任何内核模块使用该方法。
只要WinDbg或KD作为内核调试器附加上来,任何时候都可以使用该方法。也可以对引导驱动使用,但是要困难一些。关于如何在引导驱动上使用的更多信息,查看替换引导驱动。
以下面的方法使用驱动替换映射来替换驱动文件:
创建一个驱动替换映射文件(driver replacement map file)。该文件是列出了目标机上的驱动和主控机上的替代驱动的文本文件。可以替换任意数量的驱动。例如,可以在主控机上d:\Map_Files目录创建一个包含以下内容的名为Mymap.ini 的文件。
map
\Systemroot\system32\drivers\videoprt.sys
\\myserver\myshare\new_drivers\videoprt.sys
该文件的语法的更多信息,查看驱动替换映射文件格式。
- 设置到目标机的内核调试连接,在主控机上启动内核调试器(KD或WinDbg)。 (不需要真的中断到目标机。)
以下面的方法加载驱动替换映射文件:
启动内核调试器之前,设置_NT_KD_FILES
环境变量。
D:\Debugging Tools for Windows> set _NT_KD_FILES=d:\Map_Files\mymap.ini
D:\Debugging Tools for Windows> kd
D:\Debugging Tools for Windows> kd
kd> .kdfiles d:\Map_Files\mymap.ini
KD file associations loaded from 'd:\Map_Files\mymap.ini'
可以使用.kdfiles 命令来显示当前的驱动替换映射文件或删除驱动替换映射。如果不使用该命令,映射会一直保持到退出调试器。
完成上面的步骤后,驱动替换映射就起作用了。
当目标机加载驱动时,会询问主控机是否需要映射该驱动。如果需要,替换文件会通过内核连接传送过去并覆盖掉旧驱动。然后加载新驱动。
驱动替换映射文件格式
每个驱动文件替换由驱动替换映射文件中的三行指定。
- 第一行由单词"map"组成
- 第二行指定目标机上的旧驱动的路径和文件名。
- 第三行指定新驱动的完整路径。该驱动可以放在主控机上或其他服务器上。
可以这样重复指定任何多次。
路径和文件名是不区分大小写的,实际的驱动文件名可以不同。当目标机加载驱动之前,第二行的文件会被第三行指定的文件覆盖。
警告 旧驱动的路径和文件名必须是和保存在服务管理器(Service Control Manager (SCM))数据库中的路径和文件名不区分大小写的精确匹配。该路径经常以\SystemRoot\system32\drivers开头。但是,也可能有一些变形(例如,路径以\??\c:\windows\system32\drivers开头)。SCM数据库中的名字和传递给MmLoadSystemImage的名字一样。
文件中可以包含空白行,也可以包含以井号(#)开始的注释行。但是,在"map"出现之后,紧跟的两行必须是旧驱动和新驱动。 三行组成的块不能被空白行和注释行分割开
下面是一个驱动替换映射文件的示例。
map
\Systemroot\system32\drivers\videoprt.sys
e:\MyNewDriver\binaries\videoprt.sys
map
\Systemroot\system32\mydriver.sys
\\myserver\myshare\new_drivers\mydriver0031.sys
# Here is a comment
map
\??\c:\windows\system32\beep.sys
\\myserver\myshare\new_drivers\new_beep.sys
驱动替换映射文件必须是一个文本文件,但是可以使用任何文件名和扩展名 (.ini,.txt, .map等等)。
其他注意事项
驱动替换发生时,调试器中会出现一条信息。
如果使用CTRL+D (KD中) 或CTRL+ALT+D (WinDBg中),可以看到替换请求的详细信息。当不知道自己列出的名字和SCM数据库中的是否匹配时,该信息非常有用。
如果内核调试器退出,则不会再继续进行驱动替换。但是,任何已经被替换掉的驱动不会再恢复成旧的二进制文件,因为驱动文件被实际覆盖掉了。
这种驱动替换功能自动绕过Windows文件保护(Windows File Protection (WFP))。
不需要重起目标机。目标机任何时候加载驱动时都会发生驱动替换,不管它是否经过了重新起动。当然,在大部分驱动在引导过程中就已经加载了,所以实际上还是需要在加载了映射文件之后重起目标机。
如果定义了_NT_KD_FILES 环境变量,指定的驱动替换映射文件在内核调试器启动时就会加载。如果使用了.kdfiles 命令,会立即读取指定文件。这时,调试器会验证文件是否具有map/行/行的格式。但是,实际的路径和文件名直到替换发生时才会进行验证。
映射文件读取之后,调试器会保存它的内容。如果之后修改了该文件,这些修改不会生效。(除非再使用.kdfiles 命令)。
对于大的驱动文件,建议使用1394内核连接。使用COM端口的调试连接可能花很长时间用来复制文件。
替换引导驱动
如果要使用驱动替换方法来替换一个引导驱动,必须将内核调试器连接到Windows boot loader(Ntldr),而不是Windows内核。进行这种连接时,必须安装一个特殊的启用调试的Ntldr版本。可以在Windows Driver Kit(WDK)的%DDKROOT%\debug目录中找到该版本的Ntldr。
由于目标机会忽略Boot.ini文件,所以这种情况下不能设置内核连接协议。必须通过COM1端口连接到目标机。波特率为115200。因此,主控机上的内核调试器需要设置为使用115200速度的COM连接。
这种特殊方法仅对引导驱动有效(即Acpi.sys、Classpnp.sys、 Disk.sys和任何在Windows初始断点时使用!drivers 命令能显示出来的驱动)。如果要替换引导完成之后使用 MmLoadSystemImage 加载的标准驱动,需要用前面描述的标准方法。
不能在使用EFI固件替代Boot.ini文件的计算机上替换引导驱动。(例如,基于Itanium的计算机。)。
本地内核调试
KD和WinDbg可以进行本地内核调试。这种调试是在单台计算机上进行的内核调试。换句话说,调试器调试自己正在运行的计算机。
本地内核调试仅在Microsoft Windows XP和之后版本的Windows中支持。只有拥有调试权限的用户可以开始本地内核调试。
开始本地内核调试
使用/debug 引导开关来启用本地内核调试。关于该开关的更多信息,查看配置目标机的软件。
要开始一个本地内核调试会话,使用-kl命令行选项启动调试器、点击Kernel Debugging 对话框(File | Kernel Debug)的Local 选项卡、或使用.attach -k 命令。关于开始这种会话的更多信息,查看附加到目标机(内核模式)。
不支持的命令
本地内核调试会话中并不支持所有命令。一般来说,不能使用任何使得目标机停止的命令,因为不能恢复操作。
特别是,不能使用下面一些命令:
- 执行命令,如g (Go)、 p (Step)、 t (Trace)、 wt (Trace and Watch Data)、 tb (Trace to Next Branch)、 gh (Go with Exception Handled)和 gn (Go with Exception Not Handled)
- 关闭和dump文件命令,如.crash、 .dump和 .reboot
- 断点命令,例如bp、 bu、 ba、bc、 bd、 be和 bl
- 寄存器显示命令,如r和它的变种
- 堆栈跟踪命令,如k和它的变种
如果使用WinDbg进行本地内核调试,所有同样效果的菜单命令和按钮也不可用。
可以使用的命令
所有的内存输入和输出命令可用。可以自由的读取用户内存和内核内存,也可以写入内存。要确认不会写到错误的内核内存中,因为这会破坏数据结构并经常造成计算机停止响应(即崩溃)。
本地内核调试的困难
本地内核调试是非常敏感的操作。注意不要破坏系统或使得系统崩溃。
本地内核调试一个最困难的地方是机器状态一直在改变。内存在换入和换出、活动进程在不断改变、虚拟地址上下文也不会保持固定。但是,在这些情况下,还是可以慢慢对一些东西进行有效的分析,例如特定的设备状态。
内核模式驱动和Windows系统经常会通过DbgPrint 和相关函数发送信息给内核调试器。在本地内核调试时,这些消息不会自动显示出来。可以通过!dbgprint扩展命令来显示。
LiveKD
LiveKD 工具可以模拟本地内核调试。该工具会创建内核内存的"快照" 转储文件,而在生成快照时不会实际停止系统。 (因此,该快照可能不能实际反映计算机某一时刻的状态。)
LiveKD 不是Windows调试工具包的一部分。可以在SysInternals 网站上下载LiveKd 。