《软件调试》导读之提纲挈领
拙作《软件调试》出版两个月了,有热心读者建议我讲些阅读这本书的方法。有读者愿意读自己的书,当然是好事,再说读者是客户,他们的意见就是命令,不能怠慢。粗略思考一番,计划先为《软件调试》的每一篇写一个导读短文。总为开篇,今日先谈谈《软件调试》这本书的篇章结构,用软件的术语就是架构,用写作的术语也就是提纲。
从最初的书名说起
早在2003年,我就萌生了写一本关于软件调试的书的念头。但是软件调试是个大话题,有很多东西可以写,必须选择好一个角度才能写出一本好书来。于是我开始搜索当时已经有的书,无论是美国出的,还是英国出的,一共找到了十来本。而后,逐一了解了已有的这些书,归纳了它们的主要内容和特色。
2004年下半年,第一个版本的规划初步成型了,书名叫Advanced System Debugging(《高级系统调试》)(简称ASD)。针对的目标问题是系统级的调试任务,简单理解,就是在系统范围找BUG,是与模块范围内的常规调试相对而言的。在当时的规划书中,我特意从以下四个方面比较了系统调试和常规调试的不同:
Scope
Ø System wide vs. Module/Product wide
Addressed Issues
Ø Application or OS Hang/Crash vs. Feature Failure
Source Code
Ø Not depends on source code vs. Based on source code
Time Frame
Ø System Debugging is more relevant with issues near or after product deployment
并强调系统调试需要不同的工具,并且更具挑战性:
Addressed issues are more serious
Ø Hang, Crash, Halt, auto Restart, etc.
Locate Issues in system wide
Ø Any component, including hardware, maybe the root cause
Without source code and complete document
Very broad knowledge is needed
Ø OS, Hardware, Firmware, etc.
当时确定的主要内容有以下几个部分:
(一)调试基础
How debugger works?
Ø Break, stack trace, memory view, and variable watch
CPU & OS support to Debugging
Ø Post Mortem (JIT) debug, Attach Debugger
General debug mechanism
Ø Dump, asserts, event log, and debug outputs
Debugging in software engineering
(二)异常
Understand software and hardware exceptions;
Exception handling mechanism;
What if exceptions uncaught in user mode and kernel mode;
Frequent exceptions.
(三)方法学和工具
Advanced Inspecting
Ø View system components inside;
Ø Examine binary (program) files and process;
Advanced Monitoring/Spying
Ø Monitor system activities and kernel objects;
Ø Exploring OS boot process;
Advanced Tracing
Ø Interrupt an application, driver, and OS;
Ø Skills for WinDbg
(四)蓝屏(BSOD)
Why BSOD?
Interpret BSOD
Ø Illustrate Stop Code one by one
Enable and analyze memory dump
Trace BSOD by WinDbg
Ø Kernel debugging
Advanced topics about BSOD
Ø Crash handler
Ø More serious issues than BSOD
(五)调试实践
User mode debugging practice
Ø Dr. Watson, MiniDump
Kernel mode debugging practice
Ø Kernel debug using COM, 1394 and Virtual PC
Ø Debug driver issues
Debug ACPI issues
Ø Resolve tough S3/S1 issues by actual sample
现在回过头来看第一版计划,可以看到,其中包含了大多数后来要写的内容。而且这种从整个计算机系统的角度来着眼的思想一直保持到最后。
2005年时的选题列选单
2005年年初,开始和电子出版社协商出版计划。我开始进一步细化写作内容和篇章结构。于是第一个版本的章节计划产生了,下面是从当时的选题列选单中摘录下来的:
软件调试是软件开发及维护中最重要且最富有挑战性的工作之一,大多数软件工程师都认为他们
50%以上的工作时间是用在软件调试上的。但是软件调试无论是在软件工程实践中还是在学术界
至今都还没有得到应有的重视,少数效率低下的调试方法仍在普遍使用,如何提高软件调试的效
率和增强软件的可调试性还很少得到关注。特别是,纵观浩如烟海的计算机图书世界,目前还找
不到一本系统全面阐述软件调试理论和实践的作品。本书正是出于这种考虑,力争填补软件领域
和国内外出版界的一大空白。本书本着深入全面和理论与实践并重的原则,多方位的向读者展现
软件调试的原理、方法和技巧。全书分为4 篇,18 章。基础篇(1~4 章)除了介绍基本的概念和
术语(第1 章)外,系统的阐述了CPU(第2 章)、操作系统(第3 章)和编译器(第4 章)是
如何支持软件调试的。开发篇(5~7 章)开创性的提出如何在软件工程的各个环节中,尤其是设
计阶段,融入软件调试策略,提高软件的可调试性,以从根本上降低软件调试的复杂度,提高调
试效率(第5 章)。该篇不仅全面归纳比较了常用的提高软件可调试性的方法(第6 章),还提出
了一些新的模型和实现,有很强的实践参考价值(第7 章)。工具篇(8~12 章)在对各类调试工
具进行概括性介绍(第8 章)后,深入的解析了三类常用调试工具的原理和用法以及一批经典工
具。第9 章在介绍微软的著名内核调试器WinDbg 的同时,深入地揭示了内核调试的原理(目前
还没有一本书包含此内容)。第10 章通过深入解析微软.Net 调试器的源代码,介绍了目前流行的
中间/脚本语言(比如.Net 和Java)调试的原理。第11 章以介绍JTAG 原理为线索,探索了极富挑
战性的嵌入式调试领域,介绍了如何调试常见的嵌入式系统(xScale 系统, ARM 系统等)。第12
章把近百个经典、小巧、免费的调试工具归纳为几类,对它们做了个大检阅(这些工具是附带光
盘工具箱的一部分)。实践篇(13~18 章)首先归纳了被国内外专家普遍认可的一些调试规则和方
法(第13 章),然后结合真实的案例,介绍了解决几类难度较大的调试问题的方法和技巧。第14
章介绍了远程调试、RPC 调试等用户态调试任务。第15 章在介绍非常热门的 Windows 内核/驱动
程序调试的同时,还向读者揭示了如何探索Windows 内核的一些技巧。第16 章全面的探讨了著
名的蓝屏崩溃问题。第17 章介绍了如何在没有源代码和文档的情况下定位系统故障。第18 章以
最富挑战性的ACPI 问题为例,探讨了如何利用前面介绍的方法和工具解决软件、硬件、固件相
结合的棘手问题,并总结全书。
归纳一下,首先当时把要写的内容分为如下四篇:基础篇、开发篇、工具篇和实践篇。另外,将书名从ASD改为《软件调试》。现在看来,这一版本的架构与AWD(Advanced Windows Debugging)颇有相通之处,特别是基础篇和实践篇与AWD的第一篇和第二篇是一个思路。
软件调试的最初300页就是按照以上架构来写作的,写的是上面规划中的第2章和第3章。
重构
动笔后,更深的感觉到写书难。本来计划两个月完成的第2章,写到2005年年底也没完成。又因为春节的大块时间用来探索Windows调试子系统,所以2006年3月才完成第2章的第一稿。2006年8月完成了第3章的初稿。这两章完成后,一个明显的问题是这两章的篇幅都很长,第2章有100页,第3章有210多页。这两章的长度让我觉得很不称心,我觉得一章太长,不易于阅读,也不方便编辑和排版。记得当时我还特意找了几本书,在Windows Internals中,有接近和超过100页的两章,第3章系统机制(98页),和第7章内存管理(110页)。
一边写作,一边思考了几周后,我终于下定决心重新组织结构。重构的一个指导思想是更侧重调试原理,将工程实践所需的基础知识和技能融入到原理中去,不再面向问题组织篇章和“就事论事”。 根据这一思想,做了如下几个大的改动:
将原来的2,3,4章升级为篇,以便更好的组织这些原理性和基础性的内容。
砍去实践篇,以便使这本书具有更好的通用性。因为实践篇中本来的内容还是面向问题的,深入讨论其中的每个问题域都可以单独写一本书,如果把这些内容放在同一本书中写,那么很难深入,喜欢一个问题域的读者通常也不需要深入了解另外的问题域。
在工具篇中,只集中讨论调试器,去掉本来安排的10~12章。
根据以上策略调整后,新的架构便是今天的样子,全书分为6篇,30章。
目前的架构
下图中画出了2006年重构后的篇章结构,也就是目前使用的架构。
在新的架构中,2、3、4篇是全书的核心,它们所描述的对象也恰好是计算机系统中的三个核心:即硬件核心CPU,软件核心操作系统和生产软件的核心工具编译器。从调试的角度来看,这三个核心所提供的调试支持是支撑软件调试大厦的三块基石。或者说,上层的很多调试技术都是这三个核心所提供支持的应用。因此,了解这些调试支持是了解调试技术的关键。
另外,从从事计算机相关工作的技术人员(本书读者)的角度来看,了解这三个核心也是至关重要的,对于提高软件开发能力、调试能力和对计算机系统的认知力都非常有益。从这个因素出发,第2、3、4篇的开头一章,即第2章,第8章和第20章,都是介绍这篇所描述核心的基础知识。
2~4篇的总页数为755页,占全书篇幅的70%。因此阅读和消化这三篇的内容是读懂这本书的主要任务。把这三篇内容搞懂了,就掌握了软件调试技术的基础和核心,同时也可以把对CPU、操作系统、编译器这三大核心的理解提高到一个新的水准。以后,我会分别介绍每一篇的构思,给出一些阅读建议。但读到这里,读者应该清楚,全书的重心在2-4篇。在重心之下,是第1篇,即绪论,这是其它5篇完成后,最后写的一篇,目的是把读者带进门。
第5篇可调试性,尽管很短,只有短短的两章,总共52页,但是它的“思想地位”非常高。高效调试是学习和研究软件调试技术的初衷。如何实现高效调试呢?答案是调试过程中涉及的每一个部件都要大力支援、积极配合,至少不要抵触和反抗。2-4篇介绍了计算机系统中的三大核心的调试支持,但如果被调试程序不配合,那么调试效率也会大打折扣。因此第5篇的第一个目的是探讨被调试软件的可调试性,介绍在软件设计和开发过程中就要考虑可调试性,未雨绸缪。第5篇的另一个目的是彰显可调试性这一主题,任何精心设计的系统都应该考虑可调试性,对于CPU、操作系统、编译器这样的基础设施,不仅要考虑自身的可调试性,还要考虑如何支持应用软件的可调试性。
如果说,第1篇是全书内容的第一轮循环,2-5篇是第二轮循环,那么第6篇便是第3轮循环,它以调试器为视角,在介绍调试器的实现方法和使用方法的同时,将前面5篇的内容“复习”了一遍。
调试器是软件调试的最核心工具,是每个软件高手必备的武器,深谙调试器兵法是《软件调试》这本书的核心目标,有人可能想,为什么不直接按照调试器来组织内容呢?我的确考虑过在第1篇就详细介绍调试器。但是后来没有这么做,原因有二。第一,对于今天的大多数调试器来说,它是与系统密切耦合的,夸张一点说,它只不过是底层调试功能的一个用户接口。因此即使把一个调试器的所有代码都分析一遍,那么还有很多东西搞不清楚。以Windows系统的用户态调试为例,调试器是建立在调试API之上的(《软件调试》第18章),很多调试功能不过是一个API调用就进入到系统代码了。以内核调试为例,WinDBG不过是内核调试引擎(KD)的一个客户端(《软件调试》第18章)。第二,调试器有很多种,如果正面围绕调试展开,那么选择哪种调试器呢?如果选WinDBG,那么整本书就变为《WinDBG调试器XXX》,这是我不希望的,不符合写作这本书的一般性原则。
在现在的架构中,调试器被安排在最后一篇,并不是降低它的地位,而是让其坐享前面的基础。对于读者,有了前面的基础再理解调试器会觉得很自然,有水到渠成之感。对于作者,写作这一篇时,也非常轻松,不时感觉到前面发散的内容在这里收敛了。另外,把调试器安排在其它5篇的上面也与它的“接口”身份更吻合。
归纳一下,《软件调试》的架构经历了三个版本。第一个版本侧重系统调试,书名为ASD。第二个版本将写作范围扩大,书名推而广之为《软件调试》,内容划分为基础、开发、工具四篇。第三个版本提高第二版本中基础篇的位置,将重心明确在一般原理和具有共性的知识技巧上,缩减开发篇、工具和实践篇的内容。纵观这三个版本,版本1到版本2是扩张,版本2到版本3是精选和淘汰,前两个版本中的核心内容被细化,非原理性和适用面狭窄的内容被去掉了。特别是实践篇被去除了,其中的有些内容被融入到其它篇中,比如蓝屏崩溃被放入到第3篇,纳入到错误提示机制中讨论;栈溢出和内存泄漏被放入到第4篇,与编译器的有关支持一起讨论。而目标读者较窄的RPC调试和ACPI调试只好放弃了。放弃这些内容的一个长远规划是,在完成《软件调试》后,分专题写一系列实战性的短篇,自称为调试战役系列。概而言之,《软件调试》的着眼点是软件调试的一般原理,其目标是为所有喜欢调试技术的读者打下一个宽广而且坚实的基础,这个基础对于做调试是有用的,对于做开发也是有用的,对于解决迫在眉睫的问题有用,对于长远的职业发展也有用。为了实现通用性,那么只能多写具有共性的基础内容,舍弃细枝末节和具体问题,让读者掌握这些基础后,自己来举一反三,也就是常说的“授之以渔”。希望读者阅读《软件调试》时,能想起作者的这一良苦用心,这将有助于您理解书中的内容。