调试九法:软硬件错误的排查之道-“你知道,现阶段我非常忙,但我打算在晚年倾力写一本书,把所有侦探艺术都集中写在这本书里。”-福尔摩斯《格兰其庄园》
调试规则-“我在这里要讲的理论(可能你认为它们非常荒谬),实际上都是非常实用的,我就是靠着它们挣得我这份面包和奶酪的。”-福尔摩斯《血字的研究》
调试规则:1理解系统-当所有方法都不管用时读读指令,编程指南和API,自己写怀疑可能有bug的注释会使修改bug时更省力,硬件软件处理方法不同,图表不能被忽略因为数据表可能有模糊数据,应用程序可能有特定需求,养成良好的查阅习惯。
2制造失败-有规律地自动化能让错误更快发生,目的有三:观察错误查找线索和确认修复与否,注意测试不正确的序列,内存缓冲器可能装满没清空造成新连接出现异常,杂乱的数据有可能与电话公司的信号流量、附近小镇年轻人打电话的习惯及时间段有关。
3不要想,而要看-调试器的设置和分析器的正确触发, 在思考之前先观察,软件中观察意味着设置断点添加调试语句监视程序值及检查内存,树形地获得错误细节,获取详细信息观察变量、指针、缓冲层次、内存分配、事件时序关系、信号标记和错误标记查看函数调用和退出以及它们的参数和返回值查看命令、数据、窗口消息和网络数据包,直到错误范围足够小时再行动(修复),调试间歇性bug时观察底层的失败细节,在运行时开启状态消息输出调试窗口即在设计时考虑接入插入工具调试监视器和分析过滤器,把错误代码注释掉或加上“#ifdef”标记,无法使用调试器查看内部代码时可以接入调试总线的分析器对机器执行的指令进行反汇编留意十六进制数的加减进位,最易解决的问题先查看修复。
4分而治之-上下游,加入容易识别的溶剂很容易就观察到河流是怎么被污染的,直到查到污染与否的分界处了再查看分支,发现个问题马上解决因为解决了以后才会发现其他问题或者是同一个问题,有些问题(差的线程同步、意外重入例程及未初始化的变量)容易导致其他问题但不要过于极端要综合分析修复问题的难度和是否真的会引起问题。
5一次只改一个地方-调试时只改变一个变量或参数,发现自己的改变没改善问题的时候要记得改回去,查清楚问题所在再动手解决,对比正确和错误的调试日志,当新设计与原系统不兼容或新的条件使原有漏洞暴露时源设计跟踪系统(源代码控制系统SVN根据版本跟踪程序代码)(配置控制系统注意构建程序的工具)可以帮助你查看两个版本之间的所有区别。
6保持审计跟踪-不起眼的事情可能很重要因此必须记录下所有事情、做事的顺序及发生的结果(包括发生问题的准确时间、关键点和没在日志中出现的所有条件和症状),为读者提供足够多的信息让他们能够准确地理解你的体会,怀疑并注意所有细节关联起来(很多bug都是关联症状和时间表后发现的),保存调试日志和跟踪日志并注明相关的事件和影响以及你的推理和修复操作等等。
7检查插头-明确基础问题即怀疑自己的假设,当你看到另外一个世界的问题时应该停下来看看自己是不是还在地球上,一切都要从头检查特别是是否重载了新代码和初始化内存,对工具(默认设置和应用程序的环境)进行测试即上层没问题就要往下层找,工具本身可能也有问题所以要先验证工具没工作在有问题的东西上时也没问题。
8获得全新观点-向有专业知识或经验的人请教,碰到问题时先对自己讲一遍,知识管理系统可以从文档和电子邮件收集信息供查询谁掌握这些知识,问供应商甚至可以给一个修复或临时解决方案,当所有其他方法都失败时再次阅读手册和查询网上资源,不合理的地方也要提出但不要说你理论上认为是什么问题污染别人的思想。
9如果你不修复bug,它将依旧存在-不要假设问题已修复而要测试它,代码修复确认成功起作用要取消修复看问题是否再次出现。
详细小结:
1理解系统-“人们要想掌握本书中所有有用知识也并非完全不可能,事实上我就是尽全力这样做的。”-福尔摩斯《杀人的五个橘核》
阅读手册。它会告诉你在使用除草机时,要在除草头上涂润滑油,这样除草绳就不会被烧化。
仔细阅读每个细节。有关微处理器如何处理中断的详细信息就隐藏在数据手册的第37页。
掌握基础知识。电锯本来就会发出很大的噪声。
了解工作流程。引擎的转速可能和轮胎的转速不同,这是由传送轴造成的。
了解工具。弄清楚体温计的哪一端才是用来测量体温的,弄清楚Glitch-O-Matic逻辑分析器的强大功能是如何使用的。
查阅细节。连爱因斯坦都会去查阅细节,而Kneejerk却盲目相信自己的记忆力。
2制造失败-“什么也比不上直接取得的证据来得重要。”-福尔摩斯《血字的研究》
制造失败。目的是为了观察它,找到原因,并检查是否修复。
从头开始。修车工需要知道汽车车窗在被冻结之前你洗过车。
引发失败。用喷水管向漏雨的那扇窗子喷水。
但不要模拟失败。用喷水管向漏雨的那扇窗子喷水,而不要向另一扇不同的、“类似的”窗子喷水。
查找不受你控制的条件(正是它导致了间歇性失败)。改变能够改变的每件事情,振动、摇晃、扭曲,直到再现失败。
记录每件事情,并找到间歇性bug的特征。我们的绑定系统总是只在呼叫顺序错乱时才会失败。
不要过于相信统计数据。绑定问题看起来与时间段有关,但实际上真正的原因是当地的年轻人占用了电话线路。
要认识到“那”是可能会发生的。甚至冰淇淋的口味也会影响汽车的发动。
永远不要丢掉一个调试工具。自动击球版可能在某一天就会派上用场。
3不要想,而要看-“在没有事实作为参考以前妄下结论是个很大的错误。主观臆断的人总是为了套用理论而扭曲事实,而不是用理论来解释事实。”-福尔摩斯《波希米亚丑闻》
观察失败。高级工程师看到了真是的问题,并且能够找到原因。而初级工程师们认为他们知道错误发生在哪里,结果他们修复的地方根本没有出错。
查看细节。听到水泵似乎发出声音时不要停下来。到地下室查明是哪个水泵。
植入插装工具。使用源代码调试器、调试日志、状态消息、信号灯和臭鸡蛋的气味。
添加外部插装工具。使用分析器、示波器、量表、金属检测仪、心电图仪和肥皂泡。
不要害怕深入研究。虽然它是软件成品,但它出问题了,你必须打开并修复它。
注意海森堡效应。不要让仪器影响了系统。
猜测只是为了确定搜索的重点。大胆地猜测内存时序发生了错误,但在修复之前应该先查看它。
4分而治之-“当你排除了所有的不可能,不管留下了什么,也不管看起来多么不可思议,那必定都是事实。”-福尔摩斯《四签名》
通过逐次逼近缩小搜索范围。猜测1~100内的一个数字,只需7次。
确定范围。如果数字是135而你却认为它在1~100内,那么你必须扩大范围。
确定你位于bug的哪一侧。如果你所在的位置有排放物,则排放管就在上游。如果没有排放物,则排放管就在下游。
使用易于查看的测试模式。从干净、清澈的水开始,以便当排放物进入河流中时很容易看到它。
从有问题的一端开始搜索。如果你验证的是正确的部分,那么需要验证的地方太多了。应该从有问题的地方开始,然后向后追查原因。
修复已知bug,bug互相保护,互相隐藏。因此一旦找到,立即修复它们。
首先消除噪声干扰。注意那些导致系统问题的干扰因素。但对一些无足轻重的问题不要过于极端,也不要为了追求完美而去修改所有地方。
5一次只改一个地方-“有人说天才就是无止境地吃苦耐劳的本领。这个定义下得很不恰当,但是在侦探工作上倒还适用。”-福尔摩斯《血字的研究》
隔离关键因素。如果你在检查日照时间的影响,就不要改变灌溉方案。
用双手抓住黄铜杆。如果你在不知道具体发生了什么问题的情况下就试图去修理核潜艇,可能会引发一次水下的切尔诺贝利爆炸。
一次只改一个测试。我之所以知道我的VGA采集相位被破坏了,就是因为其他东西都没有发生改变。
与正常情况进行比较。如果所有出错的情况都有一些特征,而这些特征是正常情况所没有的,那么你就找到了问题所在。
确定自从上一次正常工作以来你改变了什么地方。我的工友改变了唱机转盘上的唱头,因此这是一个很好的调试起点。
6保持审计跟踪-“在侦探学的所有分支中,没有比足迹学这门艺术更重要而又最易被人忽视的了。”-福尔摩斯《血字的研究》国王说:“那个恐怖的时刻,我永远,永远也不会忘记。”“你会的,”王后回答说,“如果你不记一个备忘录的话。”-刘易斯卡洛尔《镜中世界》
把你的操作、操作的顺序和结果全部记录下来。你上一次喝咖啡是什么时候?你的头痛是什么时候开始的?
要知道,任何细节都可能是重要的。视频压缩芯片的崩溃是由于格子衬衫造成的。
把事件关联到一起。“它发出噪声,从21:04:53开始,持续4秒”比仅仅说“它发出噪声”要好得多。
用于设计的审计跟踪在测试中也非常有用。软件配置控制工具可以告诉你那次修订引入了bug。
把事情记录下来!无论那个时刻多么恐怖,都要把它记到备忘录中,这样你才不会忘记。
7检查插头-“没有什么比一个显而易见的事实更能迷惑人了。”-福尔摩斯《博斯科姆比溪谷秘案》“深信不疑是真理的可怕敌人,甚至比谎言更为可怕。”-弗里德里希尼采
质疑你的假设。是否运行了正确的代码?是不是燃气用完了?插头是否已插好?
从头开始。是否正确地对内存进行了初始化?是否按了除草机上的“primer bulb”按钮?开关是否已打开?
对工具进行测试。是否运行了正确的编译器?燃料油表是否被粘住了?量表是不是没电了?
8获得全新观点-“要想重新理清一个案子的头绪,最好的方法就是把它讲给别人听。”-福尔摩斯《银色马》
征求别人的意见。甚至一个不说话的人体模特也能帮助你认识到你先前没有注意到的事情。
获取专业知识。只有VGA视频采集卡的产商才能够肯定相位功能发生了错误。
听取别人的经验。别人会告诉你车内顶灯的线被挤压出来了。
帮助无处不在。同事、供应商、网络,还有书店,都在等待着为你提供帮助。
放下面子。Bug发生了。以除掉bug而自豪,而不要非得以自己除掉bug才为自豪。
报告症状,而不要讲你的理论。不要把别人拖进你的思维定式中。
你提出的问题不必十分肯定。甚至连“穿了格子衬衫”这样的事情也可以提出来。
9如果你不修复bug,它将依旧存在-“当危险已经离你很近时,拒绝承认它并不是勇敢的表现,而是愚蠢。”-福尔摩斯《最后一案》
查证问题确实已被修复。不要假设是电路问题,而仍然让汽车带着脏的滤油嘴上路。
查证确实是你的修复措施解决了问题。口中大喊“Wubaa!”并不是使计算机打开的窍门。
要知道,bug从来不会自己消失。使用最初导致它失败的方法再次制造失败。如果必须交付产品,那么就在产品中设计一个用于捕捉bug的“陷阱”,以便产品在客户现场发生失败时,把它捉住。
从根本上解决问题。在烧坏另一台变压器之前,先把无用的8音轨磁带卡座扔掉。
对过程进行修复。不要只是擦掉地上的油,而要纠正设计机器的方式。
所有规则总结:
1通过一个案例讲述所有规则-“你了解我的方法。它建立在对琐事的细微观察之上。”-福尔摩斯《博斯科姆比溪谷秘案》:读取脉冲的丢失并不是噪音造成的额外丢失而是电路板改动使芯片错误判断屏蔽读取脉冲。
2“有人发明,就有人能看懂。”-福尔摩斯《跳舞的人》:
2.1老房子的插座接入吸尘器使线路短路反而打开了电灯。
2.2线路过长使内存控制线引脚发出噪声改变了读取的数据。
2.3接口错误设置位数导致帧指示位丢失而呼叫失败,另外一个却错误使用了全部8位而没有发现bug。
2.4编译器把数组放在偶数的地址边界上使内存中两个数组之间留下了一个空位,而算法没有算到这个空位使Y值开始于X值总数之后,造成所有Y值所在位置都比它们原来的靠后一位,读取时编译器读的就是后一个Y值了,造成右边界使用下一行的值结果差很多最后那个甚至是随机值,使触摸屏靠近右侧的准确度很差右下角更差。
3从帮助台得到观点-“总是与匿名者打交道,是一件很难缠的事情。”-福尔摩斯《蓝宝石案》:两边的情况根本不一样,比如电缆中的电路板和CD-ROM被当成杯架。
3.1理解系统。不仅是产品本身的一切内容以及推荐的或可能的配置,而且还有它的帮助台历史——以前报告过的问题和应急之策,另外还有一些与你产品相关的其他内容——连接到本产品的其他产品、运行在本产品中或基于本产品运行的部件、占用本产品所有内存的部件或者其他不利于本产品使用的方面。配置报告工具、第三方工具性能监视工具等能告诉你更多,而电缆接错问题非常多!
3.2制造失败。规律地发现错误消息,从头开始仔细识别正在被操作的是哪些系统、窗口、按钮和字段,并确保你确切地知道故障的具体内容和发生位置。
3.3不要想,而要看。耐心,远程控制程序或web会议服务和日志文件,逻辑分析器非常好用。
3.4分而治之。配置上的bug可以做一个带插装工具的特殊版本发给客户。
3.5一次只改一个地方。一定要保存好原始环境,阻止用户胡乱更换零部件,更改文件、软件模块或硬件组件,然后再观察变化。
3.6保持审计跟踪。指导用户完成或撤销一些操作时让他们完成时告诉你做了什么再进行下一步。日志或系统生成的其他审计跟踪比用户更可靠,要一直发掘和客户现场环境有关信息,比如把软盘用磁铁吸住和通过打字机。
3.7检查插头。不要假设用户如何使用的产品,他们会不知道没电会使文本丢失、Mac计算机上装windows、把文件举到计算机屏幕前想把它传送出去。
3.8获得全新观点。故障检修指南(公司产品的bug历史数据库)和周围同事都可以帮助你刷新自己对问题的理解、知道些没有归档却有用的信息及有建设性的应急办法。
3.9如果你不修复bug,它讲依然存在。为排故障检修数据库提供数据记录,要明确描述症状和解决问题的具体方法。应急措施修复后要尽力使后续产品避免相关问题。关注问题的残留影响或修复的副作用可以赶在发生太多随机变化之前处理问题。
3.10详细小结:
遵循规则。无论用户多么糊涂,都必须找到应用规则的途径。
对行动和结果加以确认。用户会误解你的意思,同时会犯错误。通过确认他们所说和所做的一切可以及早发现这些问题。
使用自动工具。不要让用户参与系统生成的日志和远程监控与控制工具。
即使是最简单的假设也需要确认。是的,有些人就是不知道有电才能使用字处理器。
使用可用的故障检修指南。要处理的很可能就是已知的、好的设计。不要忽略历史。
帮助完善故障检修指南。如果找到了某个已知系统的一个新问题,将解决问题的所有内容进行归档可以帮助下一个支持人员。
4结束语-“我漏掉了什么事情吗?我相信我没有忽略任何因果关系。”-华生医生《巴什克维尔的猎犬》:调试规则网站:http://www.debug-gingrules.com/。在处理完每个调试事件后要进行总结:你的做法是否有效?使用或不使用规则是否影响你的效率,下次你会采取哪些不同的做法?哪个规则应该重点实践?
|