调试软件
http://advdbg.org/books/swdbg/index.aspx
在写作《软件调试》的过程中,我使用了很多工具,包括十几种调试器和上百个不同用途的观察和监视工具。因为现有的工具不能满足写作书中某些内容的需要,于是我还专门开发了一些工具,以下是其中的几个:
您在接受以下协议的情况下,可以自由使用以上工具:
在将以上工具公开前,我还需要对它们做一些测试和完善工作。比如使其适合更多的平台(CPU和OS),使其界面更友好等。所以我会逐步公开以上工具,请大家耐心等待!
虽然本书的重点不是编程语言和如何编写代码,但是为了不流于空泛,我们还是给出了大量的代码实例。这些实例可以分为如下几类:
为本书开发的工具程序在工具栏目中有详细介绍和下载。
所有示例程序都是使用C/C++语言编写的。大多数程序都可以使用Visual C++ 6.0编译器编译。个别程序需要使用Visual Studio 2005编译。如果程序的项目文件是以DSW/DSP为后缀的,那么便是VC6的项目文件。如果是SLN或PROJ后缀,那么便需要Visual Studio 2005或者2008。 如果您在编译这些程序时遇到困难,那么请先到问答栏目寻找是否已经存在有关的问答,如果没有那么请通过意见反馈栏目中给出的方式给我们联系。
所有示例程序是按照右图所示的目录结构来存放的。BIN目录用来存放编译出的可执行文件和符号文件。源程序文件和项目文件保存在每一章的子目录中。编译时产生的临时文件统一放在与BIN目录并行的TEMP目录(没有画出,会自动创建)中。
首先,以下程序都是与《软件调试》一书中的内容密切相关的,因此您应该在阅读书中内容后按照书中的指导使用这些程序。在任何情况下,作者不对因为使用以下程序而导致的任何直接和间接后果承担任何责任。
第二,您合法阅读和使用以下压缩包中的源程序文件和二进制文件的前提条件是您购买了《软件调试》一书。
在《软件调试》的附录A中列出了示例程序的清单,其中也包括了为本书开发的部分工具(见工具栏目)。
程序名称 |
用途 |
正文 |
Err2Fail.exe |
演示在特定条件下才表现出来的错误 |
1.6.1 |
AccKernel.exe |
从用户空间访问内核空间 |
2.5.2 |
AcsVio.exe |
写代码段导致非法访问异常 |
2.5 |
ProtSeg.exe |
应用程序代码不可以直接修改段寄存器 |
2.5 |
Fault.exe |
使用结构化异常器处理除零异常后恢复程序运行 |
3.3.3 |
B2BStep.exe |
分支到分支单步执行 |
4.3.4 |
HiInt3.exe |
在代码中插入断点指令 |
4.1.1 |
DataBP.exe |
手工设置数据断点 |
4.2.8 |
TryInt1.exe |
在用户态代码中插入INT 1指令会违反保护规则 |
4.3.1 |
CpuWhere.exe |
使用CPU的调试存储机制记录CPU的执行路线 |
5.4 |
Bts.sys |
支持CpuWhere的驱动程序 |
5.4 |
LBR.dll |
使用分支记录功能的WinDBG扩展模块 |
5.2.3 |
McaViewer.exe |
读取MCA寄存器 |
6.3.2 |
Breakout.exe |
试验应用程序自己调用DbgUiRemoteBreakin的效果 |
10.6.4 |
DebString |
用于验证OutputDebugString API的工作原理 |
10.7 |
EvtFilter.exe |
用于试验VC调试器的异常处理选项 |
10.5.5 |
HungWnd.exe |
用于观察被中断到调试器后的程序窗口 |
10.6.9 |
MiniDbgee.exe |
用作调试目标的简单Win32程序 |
10.4.2 |
TinyDbge.exe |
用作调试目标的简单控制台程序 |
10.4.2 |
TinyDbgr.exe |
使用调试API编写的简单调试器 |
10.4.2 |
SEH_Excp.exe |
探索SEH的异常处理 |
11.4.3 |
SEH_Trmt.exe |
探索SEH的终结处理 |
11.4.2 |
SEH_Mix.exe |
嵌套使用SEH的异常处理和终结处理 |
11.4.6 |
VEH.exe |
演示向量化异常处理器的用法 |
11.5.3 |
JitDbgr.exe |
一个简单的JIT调试器 |
12.5.3 |
UdmpView.exe |
读取和解析用户态转储文件 |
12.9.4 |
UEF.exe |
触发未处理异常的控制台程序 |
12.1 |
UefWin32.exe |
触发未处理异常的窗口程序 |
12.1 |
UefSndThrd.exe |
在第2个线程总触发未处理异常的控制台程序 |
12.1 |
UefSrvc.exe |
触发未处理异常的系统服务程序 |
12.1 |
UefCSharp.exe |
触发未处理异常的.Net程序 |
12.1 |
UefSilent.exe |
不显示应用程序错误对话框 |
12.4 |
ErrorMode.exe |
观察SetErrorMode API的效果 |
13.6.1 |
HiCLFS.exe |
使用CLFS API创建日志文件和读写日志记录 |
15.6 |
Crimson.exe |
演示Crimson API的用法 |
16.9 |
ETW.exe |
演示使用编程方法控制NT Kernel Logger |
16.7.2 |
RawLog.exe |
不使用清单文件而直接输出日志信息 |
16.9 |
KdTalker.exe |
与内核调试引擎的对话程序 |
18.5.7 |
Verifiee.exe |
探索程序验证器的分析目标 |
19.4.1 |
AllcStk.exe |
演示栈的创建过程和栈溢出 |
22.2.3 |
BoAttack.exe |
缓冲区溢出攻击的基本原理 |
22.10.2 |
BufOvr.exe |
存在缓冲区溢出错误的小程序 |
22.10.1 |
CallConv.exe |
包含各种函数调用协议的小程序 |
22.7 |
CallCV64.exe |
演示64位系统下的函数调用协议 |
22.7.6 |
CheckESP.exe |
不遵守栈平衡原则的小程序 |
22.6 |
HiStack.exe |
用于观察栈的小程序 |
22.3.3 |
LocalVar.exe |
用于观察局部变量的小程序 |
22.4.1 |
SecChk.exe |
演示编译器的安全检查功能 |
22.11 |
StackChk.exe |
演示栈检查函数的工作原理 |
22.8.3 |
StackOver.exe |
通过死循环导致栈溢出的小程序 |
22.8.2 |
StkUFlow.exe |
存在栈下溢错误的小程序 |
22.9 |
MemLeak.exe |
使用CRT的调试支持自动转储内存泄漏 |
23.15 |
FreCheck.exe |
用于分析释放堆块时触发的堆检查 |
23.8.3 |
HeapHFC |
演示Win32堆的释放检查(HFC)机制 |
23.6.2 |
HeapMfc |
演示内存泄漏的MFC程序 |
23.7 |
HeapOver |
演示发生在堆上的缓冲区溢出 |
23.8 |
HiHeap.exe |
用来分析基本内存分配和释放操作的控制台程序 |
23.3 |
SBHeap.exe |
使用CRT的小堆块堆 |
23.11.2 |
Interop.exe |
用于分析在同一个程序中使用两种异常处理机制 |
24.7 |
SehComp.exe |
用于分析SEH异常处理编译方法的调试目标 |
24.5.2 |
SehRaw.exe |
手工注册异常处理器 |
24.4.1 |
VC8Win32.exe |
用于分析异常处理有关的安全问题 |
24.6 |
HiWorld.exe |
VC2005产生的典型Windows程序 |
25.4.1 |
PdbFairy.exe |
直接读取PDB文件的小程序 |
25.6.5 |
Sig2Time.exe |
将PDB文件中的时间戳转换为时间 |
25.8 |
SymOption.exe |
试验不同的符号文件选项 |
25.2 |
SymView.exe |
符号文件观察器 |
25.6.8 |
D4D.dll |
演示可调试设计的DLL模块 |
27.4 |
D4dTest.exe |
使用D4D.dll的测试程序 |
27.4 |
PerfView.exe |
演示性能监视程序的工作原理 |
27.5.3 |
MulThrds.exe |
用于演示线程控制命令的调试目标 |
30.13.1 |
Q&A
以下《软件调试》的读者可能会问到的一些问题。简要罗列和回答如下。如果您有其它问题或者觉得回答不够全面,那么可以通过意见反馈栏目列出的方式进一步与作者和其他读者进行讨论。
在作者看来,当一个软件工程师掌握了基本的软件知识和概念后,那么接下来要学习的最重要技能便是软件调试。根本原因有两个:
终上,软件调试技术对于认识和控制软件都有着重要作用。一旦掌握了这门技术,便可以在很多方面获益:
很多资深的计算机专家都非常重视软件调试,比如以开创MSDN杂志的Under The Hood专栏著称的Matt Pietriek在给本书的短评中就说,调试器是所有系统程序员必须掌握的工具。尽管他没有说所有程序员,但这并不意味着,其他程序员学习调试器没有价值。而只是说,系统程序的复杂度和重要性,要求我们必须用调试器这样的强大工具。编写应用软件时,徒手或者土办法还能应付。对于其他程序员来说,既然有更好和更高效的工具,那我们为什么不用呢?
《Linux内核源代码情景分析》的两位作者在他们的《嵌入式系统》一书中谈到调试曾经说,在软件开发的“生命周期”中,程序调试(debug)以及调试手段的重要性是“怎么强调也不为过”的。
简单来说,因为太多的内容需要写。
软件调试如此重要,但是太多人对它还知之甚少,而且很难找到好的资料来学习。在写这本书之前,我仔细搜索了的关于调试的中外图书。但是没能找到一本我理想中的关于软件调试的书。
发现还没有这样一本书之后,与其等待,不如自己来,于是我决定写这样一本书。按照上面的“希望”有了构思和框架后,漫长的写作开始了。大约每完成一篇,我会统计一下已经达到的厚度和可能的厚度。在完成第3篇后,我开始有意识的控制篇幅。在重构第3篇时,删除了长达60页的整整一章,那一章花了不只一个月时间,这次心痛的体验让我更重视后面的写作计划。
在开始编辑这本书时,我们也考虑这本书的厚度,排版尽可能紧凑,在不影响阅读的情况下,有效利用空间。比如略微缩小行距,增大字体。在编辑的后期,所有页数统计出来后,我和编辑还是做了一个痛苦的决定,再删!于是又删减了大约70页正文和一个20多页的附录,成为目前的1006页。
正如前面谈到的,写作这本书的一个基本原则是,有一般性而且有不流于空泛。因此我们选择例子时尽可能使其具有更广泛的代表性。考虑到Windows操作系统的流行性,我们选择了以Windows系统作为本书的操作系统实例。
初学者是个很模糊的概念,因此这个问题很难直接回答。我建议有这个问题的朋友先在网络上免费阅读本书的前言和样章。然后自己判断是否适合阅读这本书。
您使用的系统可能启用了CPU的物理地址扩展(即PAE)功能,如果是这样,那么请您参阅《使用WinDBG观察启用PAE后的分页机制》一文来做这个实验。如果是仍然遇到困难,那么请发电子邮件和我联系或者把问题发到高端调试网站中《软件调试》答疑论坛中。
这是因为编译器在编译除法语句nResult=nDividend/nDivisor时,将除数先放到了寄存器中,这样在异常处理中虽然将除数改为非零,但是寄存器中的除数还是零。请参考下面的讨论做修改: 关于第三章Fault演示程序的疑问。
http://advdbg.org/books/swdbg/f_pdb_magic.aspx
PDB是Windows系统中使用最多的符号文件格式。最先是由Visual C++的始祖Visual C++ 1.0引入的,时间大约为1992年。
PDB文件的内部格式是不公开的。总的来说,PDB文件是二进制格式。但是有趣的是,在它的起始处总是有一段可读的文本信息。可以用type命令或者任何文本文件编辑器来观察这段内容,比如使用Type命令可以观察到:
c:\dig\dbg\author\code\bin\Debug>type kdtalker.pdb
Microsoft C/C++ program database 2.00
上面显示的这句话被称为PDB签名,它位于每个PDB文件的最开始处,以0x1A结束。因为0x1A在ASCII码中代表文本文件的结束,所以Type命令遇到这个字符后就会停下来,不会再继续显示后面的二进制内容。
如果使用记事本打开,那么可以看到下图所示的景象:
可见除了Type命令显示的签名外,后面还有两个可读的ASCII码,即第二行的JG。这个JG代表什么呢?事实上,它就是我们要说的PDB魔码(Magic Code)。它紧跟在PDB签名后面,长度为四个字节。
上面的PDB文件是VC6产生的,被称为PDB2.0,如果观察VS 2003/2005/2008产生的PDB7.0的文件,那么会发现它的PDB魔码为DS。
为了理解PDB魔码的含义,我开始了艰苦的搜索。搜索很久后,仍一无所获。关于PDB内部格式的文章实在是少。仔细阅读了《Undocumented Windows 2000 Secrets》,也没有答案。但是在搜索的过程中,我发现了Andy Penell的博客。他在名为《“PDB过时了”意味着什么》的短文中介绍了PDB签名。
http://blogs.msdn.com/andypennell/archive/2005/12/09/502267.aspx
读了这篇短文,我立刻意识到Andy可能是给我答案的专家。于是我在这篇博客的评论中先向他提了一个问题,并随便提到DS:
Monday, April 23, 2007 11:11 PM by Raymond Zhang
Andy, could you advise what does MSF mean in the signature. As I know, the signaure is followed by DS.
Andy很快就回复了,对MSF的解释与我猜测的相同。最重要的是他把我暗带的问题也回答了,令我喜出望外:
Tuesday, April 24, 2007 12:08 PM by andypennell