您的位置:首页技术文章
文章详情页

Windows 2000对调试技术的支持

【字号: 日期:2023-08-27 15:27:01浏览:5作者:猪猪

尽管本书中的很多内容都称之为“ Undocumented ”,但其中的一些内容只能通过挖掘操作系统的代码才能获取。 Windows 2000 DDK ( Device Driver Kit )提供了一个强大的调试器可以出色的完成这方面的工作。本章将从建立一个完善的调试环境开始介绍。在阅读随后的章节时,你会经常的使用内核调试器来挖掘操作系统内部的各种特性。如果你对内核调试器很是厌烦,或许你需要制作一个自己的调试工具了。因此,本章还将介绍有关 Windows 2000 调试接口的文档化和未文档化的资料,包括微软符号文件( Symbol File )的详细信息。 It features two sample librarIEs with companion applications that list processes, process and system modules, and various kinds of symbol information buried inside the Windows 2000 symbol files 。做为一个特殊收获,在本章结束时,你将得到首份有关 PDB ( Microsoft Program Database )的公开文档。

建立一个调试环境

“嗨,我不想调试 Windows 2000 程 序。在此之前,我想自己写一个先!”当你读到这个标题时你可能会这样大喊出来。“很对!”我说“这就是你该去做的!”但是为什么你要以建立一个调试环境开 始这次旅行呢?答案很简单:调试器是进入系统的后门。当然,这并不是调试器开发人员的主要目的。然而,当你跟踪代码的执行过程或者你的程序意外的玩完时, 任何优秀的调试器都须能够告诉你一些有用的系统信息。仅仅报告一个指向 4GB 地址空间某处的 8 位 崩溃地址,然后让你独自一人去寻找到底发生了什么,真是无法让人接受。调试器至少应该告诉你最后执行的引发错误的代码是哪个模块中的代码,而且,在理想情 况下,它还应该告诉你让你的程序玩完的那个函数的名称。因此,调试器通常必须知道比编程手册还要多的系统信息,你可以利用这些信息来研究系统的内部情况。

Windows 2000 提供了两个调试器: WinDbg.exe (发音很像“ WindBag ”,译注: WindBag 在俚语中指空话连篇的人)一个 Win32 GUI 程序和 i386kd.exe 一个提供与之等价功能的命令行模式程序。我曾经同时使用过这两个程序,最后确定 i386kd.exe 是最好的一个,因为它有一组非常强大的选项。不过,最近看来 WinDbg.exe 似乎有所改进。不过,本书中的所有例子都是与 i386kd.exe 相关的。就像你猜想的那样, i386 前缀表示目标平台( Intel 386 处理器家族,也包括 Pentium ) kd 是 Kernel Debugger (内核调试器)的缩写。 Windows 2000 内核调试器是一个非常强大的工具。比如,他知道如何使用 Windows 2000 安装光盘中的符号文件( Symbol files ),因此,可以给出系统内存中几乎任何地址的相关符号信息(这非常有价值)。而且,它还可以反编译二进制代码、将内存信息的 16 进制转储数据以多种格式显示,甚至还能显示一些内核关键结构的布局。在调试器的在线帮助中有其命令行接口的详细文档。

准备一次崩溃转储( Crash Dump

这 些都是好消息。坏消息是你在内核调试器顺从你之前,必须做一些准备工作。第一个障碍是调试通常涉及两台独立的计算机(通过线路连接在一起),其中一台运行 调试器,另一台用于被调试。然而,如果并不需要实时调试,那么有一个简单的方法,可以不需要第二台机器。例如,如果一个有错误的程序抛出了一个未处理的异 常而引发了声名狼藉的 NT 蓝屏死机( Blue Screen Of Death, BSOD ),你可以选择保存崩溃前的内存映像到一个文件中,在重新启动后,检查这个崩溃转储( Crash Dump )文件。这项技术通常被叫做 post mortem (事后检查)在拉丁文中, post mortem 意思是“ after death ”。这种方式是本书首选方法之一。在这里,我们的主要任务是研究系统内存,在大多数情况下,内存数据是来自还在工作的系统或者来自系统崩溃前内存的一个快照( snapshot )都并不重要。然而,一些有趣的信息则需要通过内核模式的驱动程序深入正在工作的系统的内部才能观察到,这一主题被保留在后面的章节中。

一个崩溃转储( Crash Dump )只是简单将当前内存数据写入一个磁盘文件而已。因此,一个完整的崩溃转储( crash dump )文件的大小通常与系统物理内存一样大(事实上,会略微小些)。崩溃转储( Crash dump )是由内核中的一个特殊程序在处理致命错误过程中生成的。然而,这个例程( handler )并不是立即将内存数据写入目标文件中。这是个不错的处理方式,因为在系统崩溃后,磁盘文件系统可能也不能正常工作。因此,内存映像首先被复制到页面文件存储器( page file storage ),这是系统内存管理器的一部分。因此,你应该将你的页面文件大小增加到至少两倍于物理内存。两倍?一样大还不够吗?当然 — 那只够存放崩溃转储( crash dump )。要知道,在启动时,系统会尝试将崩溃转储( crash dump ) 映像复制到实际的磁盘文件,这意味着,如果系统不能及时的释放映像数据占用的页面文件,它就可能用尽所有的虚拟内存。通常,系统会处理这种情况,它会疯狂 的读写磁盘并向你抛出一个惹人厌的“虚拟内存不足”的警告。只要你预料蓝屏的概率会增大时,将页面文件设置的足够大,这将会为你节省很多时间。

到这儿,你应该打开 Windows 2000 的控制面板,改变如下的设置:

l 增加页面文件到至少两倍物理内存的大小。

l 接下来,配置系统以便当蓝屏发生时生成一个 崩溃转储( crash dump )文件。在系统属性对话框里,选择高级页,然后单击启动和恢复按钮,检查写入调试信息选项。你应该在下拉列表中选择完成内存转储选项。在转储文件对话框中输入一个文件名和路径,转储文件将会从页面文件中复制到你指定的这个文件中。 %SystemRoot%MEMORY.DMP 是默认设置。

让系统崩溃

当设置好系统准备一次 crash dump 后,是时候做在 Windows 2000 系统程序员一生中最厌恶的事了:开始让系统崩溃!通常,只要达摩克利斯的剑挂在了你的头顶上(通常是在离产品截止时间还有几个小时的时候)你就会看到恐怖的蓝屏。现在,是你自愿让系统崩溃,但你可能无法找到有问题的软件来完成这项“工作”。来试试 David Solomon 在他的《 Inside Windows NT 第二版》中提到的那个优雅的诀窍:

“如何能可靠的产生一个崩溃转储( crash dump )文件?只需要使用 Windows NT 资源工具中的 kill.exe 工具, kill 掉 Win32 子系统进程( csRSS.exe )或者 Windows NT 登陆进程( winlogon.exe ),你必须有管理员权限”( Solomon [1998],p.23. )

神奇,太神奇了!这个窍门不能在 Windows 2000 上工作!第一感觉,很不走运,但是从另一个角度看,这是个好消息。当你知道使用微软自己正式发布的一个小工具就能如此轻松的破坏系统,你会怎样想?事实上,微软关闭这个安全漏洞非常对。可是,我们现在需要一种方法来使系统崩溃啊。在这一点上,想想那个古老而简单的 NT 规则:“ If anything seems to be impossible in the Win32 Word, just write a kernel-mode driver, and it will work out all right! ” Windows 2000 非常谨慎的管理 Win32 程序。它在应用程序和内核之间构建了一堵墙,任何企图跨越此边界者都会被毫不留情的解决掉。这对于系统的稳定性是个好消息,但是对于编写需要直接与硬件打交道的程序的人来说并不是个好消息。想想 Dos ,在那儿任何程序都可以直接触及硬件,在这方面 Windows 2000 有些过分讲究。但这并不意味着在 Windows 2000 中访问硬件是不可能的。不同的是,这种访问被限制到一个特殊类型的模块 — 内核模式的驱动程序( Kernel-mode driver )。

我可以告诉你,现在我将简要的介绍一下 Kernel-mode driver 编程技术(这本是第三章的内容)。眼下,这已经足够说明 kernel-mode driver 让系统崩溃是非常容易的事。当驱动程序出错时, Windows 2000 没有提供一种错误恢复机制,这就导致即使无意中试图执行一个不合法的操作也会招来蓝屏。当然,最简单而且危险最小的违规动作就是读取一个无效的内存地址。由于系统显示捕获所有通过空指针进行的内存访问,这是 C 编程中一个常见的错误,读取一个空指针是让系统崩溃的理想动作。示例代码中的 w2k_kill.sys driver 就是这么做的。这是一个非常简单的程序,同时也是出现在本书中的第一个 kernel-mode driver 。

列表 1-1 是 w2k_kill.c 的引用部分,展示了引发蓝屏的错误代码。当编写这样无意义的代码时,需要注意内建于 Visual C/C++ 的优化器可能会抵消你的努力,它会跟踪所有的代码并试图消除其中任何有副作用的部分。在下面的例子中,优化器并未起作用,因为 DriverEntry() 坚持将在 0 地址发现的东西作为其返回值。这意味着这个数值将会被存放到 CPU 的 EAX 寄存器中,最简单的方法就是 MOV EAX, [0] 指令,这个指令将会抛出我们期待的异常。

NTSTATUS DriverEntry ( PDRIVER_OBJECT pDriverObject,

PUNICODE_STRING puRegistryPath )

{

return *((NTSTATUS*)0); // read through NULL pointer

}

列表 1-1 A NULL Pointer Read Operation in Kernel-Mode Crashes the System

w2k_load.exe 程序出现在第三章,用来用来载入并启动 w2k_kill.sys 驱动程序。如果你在精神上做好了 kill 掉你的 Windows 2000 系统,请按照下面的步骤来做:

l 关闭所有应用程序

l 插入本书的附带光盘

l 在开始菜单中选择运行

l 输入 d:binw2k_load w2k_kill.sys ,用你 CD-ROM 的盘符替换 d: ,然后单击确定

当单击后, w2k_load.exe 将试图加载 w2k_kill.sys 文件(位于光盘的 bin 目录下)。随后 DriverEntry() 开始执行,蓝屏出现了,如图 1-3 所示,你会看到当内存数据被转储到页面文件存储器时,屏幕上会有一个计数器从 0 逐渐增加到 100 。 如果你在启动和恢复对话框中选中了自动重起,当崩溃转储完成后,系统会立即重新启动。当系统进入等待登陆状态后,稍等一会直到硬盘灯不再闪烁。这是因为将 崩溃转储数据从页面文件复制到磁盘文件需要一定的时间,特别是你的物理内存很大时。在此时干扰系统,例如,将系统过早的关闭,可能会产生一个无效的崩溃转 储文件。

在图 1-3 中,可以看出系统会显示包含出错代码的模块的名称( w2k_kill.sys ),以及引发异常的指令地址( 0xBECC3000 )。这个地址或许和你的系统的不同,因为它是随硬件配置而变化的。驱动程序的加载地址通常都不确定,和 DLL 的加载地址类似。请记下显示的地址 ---- 稍后在安装和配置内核调试器是你还需要它。

一点小的提示:故意让系统崩溃不该是你每天都作的事。尽管有问题的 w2k_kill.sys 本 身是无害的,但在它执行的那一刻可能并不那么走运。如果度去空指针发生时另一个线程正在做某些重要的事情,系统可能会在该线程有机会做清理工作之前关闭。 比如,在重起之后,活动桌面往往会抱怨发生了一些可恶的事情,它需要进行恢复。因此,在你使系统崩溃之前,应该仔细的检察系统是否影响了重要数据并且保证 所有 cache 中的数据都被写入了磁盘。注意,作者和出版商不会对 w2k_kill.sys 驱动程序造成的破坏负责。

译注: 达摩克利斯希腊传说中的叙拉古国王狄奥尼西奥斯的朝臣,据传说其被迫坐在上悬宝剑的餐桌旁,宝剑由一根头发系住 , 以此来暗示君王命运的多危

安装符号文件

重新启动后,你就有了一个 Windows 2000 系统的快照( snapshot ),包括一个有问题的 Kernel-mode Driver (读取空指针时被捕获)。观察此快照文件和察看实际系统内存是一样的。当然,这个快照文件和动物死尸一样 --- 不再对外界刺激有所反应,但是你现在不需要担心这些。接下来你需要安装内核调试器需要的符号文件( symbol files ),当你分析崩溃文件时,你就会用到了。

MSDN 用户可以在 Windows 2000 Customer Suuport – Diagnostic Tools 光盘上找到这些符号文件。插入光盘,用 IE 打开光盘上的 DBG.HTM 文件,你会看到很多安装选项。如果你运行的是 free build 的 Windows 2000 ,你最好安装 retail symbols 。对于 checked build 版,可以选择安装 debug symbols 。安装程序会从 SYMBOLS.CAB 中复制一些 .dbg 和 .pdb 文件到系统符号文件目录中。默认的系统符号文件目录为: %SystemRoot%Symbols 。 %SystemRoot% 环境变量代表 Windows 2000 的安装目录。

在起动时, Windows 2000 内核调试器会尝试通过环境变量 _NT_SYMBOL_PATH 指示的路径来寻找符号文件,所以最好正确的定义该变量。

译注:

现在可以通过 Symchk.exe 工具来检查和下载最新的符号文件,该工具随 Debugging Tools for Windows 软件包安装。

微软的文档中对于 _NT_SYMBOL_PATH 应该指向哪里的说明有些模糊不清。在 DDK 的内核调试一节里提到必须包含符号子目录,即 C:WINNTSymbols 或等价目录。而在 SDK 关于 dbghelp.dll 库的文档中,有关符号路径的描述又稍微有些区别:

“该库需要使用符号搜索路径来定位 .dll 、 .exe 或 .sys 对应的调试符号( .dbg 文件)。它会在路径后添加 dll 、 exe 或 sys 。例如, .dll 符号文件位于: C:WINNTSymbolsdll , .exe 文件的路径则为: C:WINNTSymbolsexe ”

。。。。。。

“如果你设置了 _NT_SYMBOL_PATH 环境变量,符号管理器按照如下顺序搜索符号文件:

1. 应用程序的当前工作目录

2. _NT_SYMBOL_PATH 指示的目录

3. _NT_ALT_SYMBOL_PATH 指示的目录

4. SYSTMEROOT 指示的目录

这样看来把 _NT_SYMBOL_PATH 设定为 C:WINNT 似乎要好于 C:WINNTSymbols ,为了确定哪种说法是正确的。我试验了这两种方法,很高兴它们都能正常的工作。

配置内核调试器

构建调试环境的最后一步就是安装和配置内核调试器。如果你已经安装了 Windows 2000 DDK ,那你可以在 NTDDKbin 目录中找到调试器。内核调试器的可执行文件名为 i386kd.exe 。另一种方法是从 Windows 2000 Customer Support---Diagnostic Tools 光盘中安装。

为了使用前面我们得到的崩溃转储文件,你需要使用 i368kd 的 –Z 选项。示例如下:

i386kd –z C:WINNTMEMORY.DMF

成功打开我们的 crash dump 后,你会看到类似图 1-7 所示的东东, kd> 提示符会出现,这表示内核调试器已经准备接受命令了。在开始之前,请检查符号搜索路径是否正确。列出的启动信息,表示调试器已经加载了三个扩展 DLL 。 i386kd.exe 一个强大的特性就是其扩展机制,这允许第三方采用独立的 DLL 来扩展其基本功能。对于这些扩展的命令,要在其前面加上!号以区分内建的命令。

如图 1-7 所示,我输入了一个内建命令: u becc3000 , u 的含义是“反编译( unassembel )”, becc3000 是开始反编译的 16 进制地址。默认情况下,均采用 16 进制,但你也可也通过命令来改变此默认值。命令为: n 10 ,此后默认所有数字都是 10 进制表示。你可以使用 0x 前缀来表示这是一个 16 进制数。地址 becc3000 就是 w2k_kill.sys 引起系统崩溃的地方。请使用你在蓝屏时看到的地址。如果一切正确的话,你会看到 mov eax, [00000000] 指令,如上图所示。如果没有看到的话,你可能没有使用正确的崩溃转储文件。 mov eax, [00000000] 指令表示从虚拟地址(也可称作线性地址) 0x00000000 读取一个 32 位的数值到 CPU 寄存器 EAX 中,这明显是列表 1-1 中 *(NTSTATUS*)0) 表达式的实现,等同于读取空指针的操作。没有针对此类错误的异常处理例程,因此,系统在蓝屏上会显示 KMODE_EXCEPTION_NOT_HANDLED ,如图 1-3 所示。如果你想知道有关此错误信息更多的东西请参考《 The NT Insider 》( Open Systems Resources 1999b )。

标签: Windows系统