《调试九法:软硬件错误的排查之道》读书笔记

第一章

本书针对查找bug的根源并修复
两个重要的事情:

  1. 如果查找一个bug花费了大量的时间,那么原因可能是忽略了某个最基本的、最重要的规则,一旦应用了那条规则,很快就会找到问题。
  2. 擅于快速调试的人已经深刻理解并应用了这些规则,而那些很难理解或使用这些规则的人则很难找到bug。

第二章 总体规则

  1. 理解系统
  2. 制造失败
  3. 不要想,而要看
  4. 分而治之
  5. 一次只改一个地方
  6. 保持审计跟踪
  7. 检查插头
  8. 获得全新观点
  9. 如果你不修复bug,它将依然存在

第三章 理解系统

例如一个系统,外部设备通过芯片和处理器相连。在发生问题(信号不能通过芯片传给处理器)后,有必要理解芯片的工作原理(读技术文档),研究细节。

阅读手册

应该先读手册,再去设计和实现。不要相信自己的记忆,要经常翻阅手册。

如果在调试自己公司的产品,需要读内部手册。读一下功能说明以及所有设计规范,研究一下图表、时序图和状态机。分析它们的代码,还要读一下注释。一定要检查设计,以及查明构建它的工程师打算用它来做什么。

理解系统还有一个好处就是当你找到bug时,可以在不破坏其他地方的前提下修复它们。

逐字逐句阅读整个手册

要深入挖掘和理解相关的函数及其参数。

知道什么是正常的

检查系统时必须知道系统正常工作状态。比如发现数据没有马上写入内存,其实是因为缓存。这个方面通过阅读《深入理解计算机系统》很有帮助。作为工程师,必须掌握一些你所工作的技术领域的基础知识。

知道工作流程

找bug时需要知道查找路线。首先就要猜测在哪里把系统分隔开,以便隔离问题。所以就需要对系统功能划分的了解,至少大致上知道所有模块和接口都是做什么的。

应该知道系统中所有API和通信接口是用来交换什么数据的。还应该知道每个模块或程序如何处理它们通过这些接口收发的数据。

如果系统有部分是黑盒子,就该知道它们如何与其他部分交互,这样可以判断出来问题是在内部还是在外部。

了解你的工具

调试系统非常关键,要做到正确选择工具,正确使用工具,正确解释结果。

第四章 制造失败

为什么要制造失败:

  1. 可以观察错误
  2. 可以专心查找原因
  3. 可以判断是否已修复问题

从头开始

要注意机器在执行这些步骤时的状态。

引发失败

可尽量通过自动化解决。

不要模拟失败

要引发失败而不是模拟失败,要模拟那些导致失败发生的条件,但不要试图模拟失败机理本身。猜测容易错误,或者制造了新的bug。总之不要用一个看似完全相同(实际上不同)的环境来代替并希望看到相同的错误。

处理间歇性bug

尽量控制所有的条件。对于软件,查找未初始化的数据、随机数据输入、时序误差、多线程同步和外部设备。

永远不要丢掉调试工具

可以自己设计并且实现一种易于维护和升级的调试工具,将其加入到版本控制系统中。

第五章 不要想,而要看

注:这个规则以前没有留意,须重点训练。

“在没有事实作为参考以前妄下结论是个很大的错误。主观臆断的人总是为了套用理论而扭曲事实,而不是用理论来解释事实。”

福尔摩斯,《波西米亚丑闻》

不要猜测失败是如何发生的,而要亲眼看到底层的失败。当你的错误猜测一一被否定后,精疲力尽但仍然必须找到bug。需要做的工作仍然和先前一样多,唯一的不同是你的时间变少了。

对于软件,观察意味着设置断点、添加调试语句、监视程序值以及检查内存。

观察失败

必须仔细观察,找到足够多的问题细节,才能调试它。观察往往比猜测能够更快地找到问题。

查看细节

每次为了发现故障而观察系统,都会了解更多与失败有关的信息。这将帮助你确定应该进一步观察哪些地方以获取更多细节。最后,你会得到足够多的细节,这是才可以根据这些细节来查看设计并找到问题的原因。

观察到什么程度?一直观察,直到把问题的原因锁定在几种可能性之内。

对系统进行插装

比如以调试模式编译,收集状态消息。可以使用#ifdef来标记所有的调试语句。

猜测只是为了确定搜索的重点目标

在尝试修复问题之前,仍需要再次看到失败,以便确认猜测是正确的。某些问题比其他问题更容易出现,或更容易修复,因此应该首先解决这类问题。

第六章 分而治之

缩小搜索范围

借助二分法的思想。

确定范围

可以将整个系统或者某个模块作为范围,总之初始范围应当尽量大,否则可能最后定位到不是问题的问题上。

插入易于识别的模式

注意不要因为设置了新的条件而改变bug。

从有问题的支路开始查找

从错误的一端开始向上追查,把分支点作为测试点。

修复已知bug

如果同时出现了多个问题,如果确实查明了其中一个问题时,应该立即修复,然后再查找其他问题。有时两个问题可能是同一个bug。
此外,如果修复某个问题对其他的问题有影响,一定要先修复它之后再测试其他的问题。

首先消除噪声干扰

留意那些导致系统问题的干扰因素,但是对无足轻重的问题不要过于极端,也不要为了追求完美而修改所有的地方。

第七章 一次只改一个地方

有时你可能会想改变系统的不同部分,以便看看是否对问题有影响,这是个危险信号,因为你正在猜测而不是使用插装工具来观察。

第八章 保持审计跟踪

  1. 记下每步操作、顺序和结果
  2. 魔鬼隐藏在细节之中: 审计跟踪的细节非常关键,例如bug出现在什么条件下,出现的程度如何等。
  3. 用于设计的审计跟踪: git等版本控制系统的使用。

记录

用计算机记录下来,好记性不如烂笔头。

第九章 检查插头

一些显而易见的假设往往是错误的。假设错误通常是最容易修复的错误。

  1. 质疑你的假设:是否运行了正确的代码?
  2. 从头开始:是否正确地对内存进行了初始化?
  3. 对工具进行测试:是否运行了正确的编译器?

第十章 获得全新观点

向他人寻求帮助。

可以通过向别人解释问题使自己对问题有全新的认识,也可以借鉴别人的经验。

报告时需要报告症状,而不是讲你的理论,否则会把别人拉入你的思维定式中。

第十一章 如果你不修复bug,它将依然存在

  1. 检查问题确实已被修复
  2. 检查确实是修复措施解决了问题
  3. bug从来不会自己消失
  4. 从根本上解决问题
  5. 对过程进行修复