《调试软件》作者主页

 

调试软件

http://advdbg.org/books/swdbg/index.aspx

 

在写作《软件调试》的过程中,我使用了很多工具,包括十几种调试器和上百个不同用途的观察和监视工具。因为现有的工具不能满足写作书中某些内容的需要,于是我还专门开发了一些工具,以下是其中的几个:

  • 符号文件观察器——SymView
  • 用户态转储文件观察器——UdmpView
  • 与内核调试引擎对话的交谈器——KdTalker
  • 监视和记录CPU执行轨迹的CpuWhere
  • 观察GDT、IDT的SoZoomer

您在接受以下协议的情况下,可以自由使用以上工具:

  • 这些工具是免费提供给《软件调试》一书的读者的。如果你没有购买这本书,那么你应该感谢那些已经购买了这本书的朋友。
  • 这些工具可能存在瑕疵,不管是否是工具自身的原因,这些工具的作者都不对使用这些工具导致的任何直接或者间接损失负任何责任。

在将以上工具公开前,我还需要对它们做一些测试和完善工作。比如使其适合更多的平台(CPU和OS),使其界面更友好等。所以我会逐步公开以上工具,请大家耐心等待!

 

虽然本书的重点不是编程语言和如何编写代码,但是为了不流于空泛,我们还是给出了大量的代码实例。这些实例可以分为如下几类:

fldlayout.jpg

  • 用于演示相关调试技术的工作原理和实现方法,比如软件断点、硬件断点等。
  • 充当被调试目标的靶子程序。
  • 用来模拟各种错误的反例。

为本书开发的工具程序在工具栏目中有详细介绍和下载。

编译方法

所有示例程序都是使用C/C++语言编写的。大多数程序都可以使用Visual C++ 6.0编译器编译。个别程序需要使用Visual Studio 2005编译。如果程序的项目文件是以DSW/DSP为后缀的,那么便是VC6的项目文件。如果是SLN或PROJ后缀,那么便需要Visual Studio 2005或者2008。 如果您在编译这些程序时遇到困难,那么请先到问答栏目寻找是否已经存在有关的问答,如果没有那么请通过意见反馈栏目中给出的方式给我们联系。

目录结构

所有示例程序是按照右图所示的目录结构来存放的。BIN目录用来存放编译出的可执行文件和符号文件。源程序文件和项目文件保存在每一章的子目录中。编译时产生的临时文件统一放在与BIN目录并行的TEMP目录(没有画出,会自动创建)中。

下载

首先,以下程序都是与《软件调试》一书中的内容密切相关的,因此您应该在阅读书中内容后按照书中的指导使用这些程序。在任何情况下,作者不对因为使用以下程序而导致的任何直接和间接后果承担任何责任。

第二,您合法阅读和使用以下压缩包中的源程序文件和二进制文件的前提条件是您购买了《软件调试》一书。

  • 示例程序的源程序文件和项目文件(2,365KB):swdbgbk_src.zip
  • 示例程序的可执行文件和PDB文件(11,825KB):swdbgbk_bin.zip
  • 试验使用的转储文件和有关程序文件(157KB):swdbgbk_dmp.zip

程序清单

在《软件调试》的附录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

 

以下《软件调试》的读者可能会问到的一些问题。简要罗列和回答如下。如果您有其它问题或者觉得回答不够全面,那么可以通过意见反馈栏目列出的方式进一步与作者和其他读者进行讨论。

为什么要重视软件调试?

在作者看来,当一个软件工程师掌握了基本的软件知识和概念后,那么接下来要学习的最重要技能便是软件调试。根本原因有两个:

  • 使用调试技术可以更好的控制软件。相对于硬件,软件更加复杂、多变,有时甚至狡黠、事故和邪恶。大的方面讲,计算机历史上的重大灾难大多是软件导致的。从小的方面讲,用户遇到的崩溃、病毒入侵、没有响应等,也大多是由软件导致的。人类在控制和驾驭软件方面的能力还在发展的过程中。目前阶段,以调试器为核心的调试技术是征服软件的最强大武器。举例来说,目前有太多的糟糕软件完全不把自己当作“仆人”,它们自作主张,借助强大的计算机硬件恣意妄为,这时最有效的方法就是使用调试器将其擒获。当我们使用调试器来自由操纵桀骜的软件和被调试的计算机系统时,我们才真正找回了主人的尊严。如果我们不得不重新安装整个系统,那么就好像是为了赶走一个糟糕的仆人而重建“家园”。
  • 使用调试技术可以更精确、更直接认识的软件。当我们一步步的跟踪一个软件时,我们对它的认识精确到了指令的级别。指令是软件的本质,是承载思维的载体,软件的好坏和善恶忠奸都在它的指令中,所以在指令一级的认识软件方式对于软件工程师来说是非常重要的。因为时间关系,我们可能无法跟踪软件的每一条语句或者指令,但是我们有必要仔细跟踪关键的操作和可能存在问题的代码片段。

终上,软件调试技术对于认识和控制软件都有着重要作用。一旦掌握了这门技术,便可以在很多方面获益:

  • 侦错,即定位和修正瑕疵。
  • 学习新的软硬件技术。
  • 安全。以研究Rootkit著称的Joanna Rutkowska在接受采访时曾经说她的计算机系统不安装任何“安全软件”,但是她安装了调试器,并且说“MS Kernel Debugger can be very useful tool for system compromise detection”

很多资深的计算机专家都非常重视软件调试,比如以开创MSDN杂志的Under The Hood专栏著称的Matt Pietriek在给本书的短评中就说,调试器是所有系统程序员必须掌握的工具。尽管他没有说所有程序员,但这并不意味着,其他程序员学习调试器没有价值。而只是说,系统程序的复杂度和重要性,要求我们必须用调试器这样的强大工具。编写应用软件时,徒手或者土办法还能应付。对于其他程序员来说,既然有更好和更高效的工具,那我们为什么不用呢?

《Linux内核源代码情景分析》的两位作者在他们的《嵌入式系统》一书中谈到调试曾经说,在软件开发的“生命周期”中,程序调试(debug)以及调试手段的重要性是“怎么强调也不为过”的。

这本书为什么这么厚?

简单来说,因为太多的内容需要写。

软件调试如此重要,但是太多人对它还知之甚少,而且很难找到好的资料来学习。在写这本书之前,我仔细搜索了的关于调试的中外图书。但是没能找到一本我理想中的关于软件调试的书。

  • 我希望这本书具有一般性,探讨调试的一般原理和思想,但是又不空泛,对于关键的技术应该讲透、讲彻底。
  • 我希望这本书有理论价值,但又不脱离实践,讲的应该是当代主流的软硬件环境和工具,而不是多年前的,也不是实验性的,讨论的应该是“真家伙”,面对的应该“真难题”。因为调试软件是打仗,纸上谈兵似的东西没有用。
  • 我希望这本书介绍必要的软硬件基础。打仗要熟悉地形,调试软件要熟悉软件所运行的基础环境,最起码的就是CPU、操作系统和编译器。如果对这三样东西模棱两可,那么打仗时难免晕头转向。

发现还没有这样一本书之后,与其等待,不如自己来,于是我决定写这样一本书。按照上面的“希望”有了构思和框架后,漫长的写作开始了。大约每完成一篇,我会统计一下已经达到的厚度和可能的厚度。在完成第3篇后,我开始有意识的控制篇幅。在重构第3篇时,删除了长达60页的整整一章,那一章花了不只一个月时间,这次心痛的体验让我更重视后面的写作计划。

在开始编辑这本书时,我们也考虑这本书的厚度,排版尽可能紧凑,在不影响阅读的情况下,有效利用空间。比如略微缩小行距,增大字体。在编辑的后期,所有页数统计出来后,我和编辑还是做了一个痛苦的决定,再删!于是又删减了大约70页正文和一个20多页的附录,成为目前的1006页。

这本书为什么没有详细讲Linux有关的内容?

正如前面谈到的,写作这本书的一个基本原则是,有一般性而且有不流于空泛。因此我们选择例子时尽可能使其具有更广泛的代表性。考虑到Windows操作系统的流行性,我们选择了以Windows系统作为本书的操作系统实例。

这本书是否适合初学者阅读?

初学者是个很模糊的概念,因此这个问题很难直接回答。我建议有这个问题的朋友先在网络上免费阅读本书的前言样章。然后自己判断是否适合阅读这本书。

在做2.7.5节的实验时,遇到的情况为什么和书中讲的不一样?

您使用的系统可能启用了CPU的物理地址扩展(即PAE)功能,如果是这样,那么请您参阅《使用WinDBG观察启用PAE后的分页机制》一文来做这个实验。如果是仍然遇到困难,那么请发电子邮件和我联系或者把问题发到高端调试网站中《软件调试》答疑论坛中。

在VS2002/2003/2005/2008下重新编译第3章的Fault程序后,程序为什么不断的循环?

这是因为编译器在编译除法语句nResult=nDividend/nDivisor时,将除数先放到了寄存器中,这样在异常处理中虽然将除数改为非零,但是寄存器中的除数还是零。请参考下面的讨论做修改: 关于第三章Fault演示程序的疑问

 

解读PDB文件的魔码(Magic Code)——表25-6来之不易

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命令遇到这个字符后就会停下来,不会再继续显示后面的二进制内容。

如果使用记事本打开,那么可以看到下图所示的景象:

pdb_notepad.gif  

可见除了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

转载于:https://my.oschina.net/alphajay/blog/39164

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值