《Effective Debugging:软件和系统调试的66个有效方法》——第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug

第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug

要想确定问题的来源,通常有两种办法。一种是从问题的具体表现入手,向上追查其来源,还有一种是从应用程序或系统的顶层入手,逐步向下探查,直至找到其根源。对于某种类型的问题来说,其中一种方法的效果通常要比另一种更好,但是如果你在采用某个方法时遇到了困境,那么不妨试试另一个方法。

如果问题表现得很明确,那我们就应该从发生问题的地方入手,向上追查bug。这可以分成三种情况。

第一种情况是程序崩溃。在这种情况下,为了便于排查问题,我们通常可以考虑用调试器来运行程序,也可以在它崩溃的时候把调试器连接到程序上面,或取得内存转储信息(参见第35条)。我们要检查各变量在程序崩溃时的取值,看看有没有null值、损坏的值或未初始化的值,这些都有可能是引发崩溃的原因。对于某些系统来说,我们可以通过0xBAADF00D(代表bad food)这样的特殊字节值来找出尚未初始化的变量。维基百科的Magic Number词条列出了很多这样的特殊值。找到了取值不正确的变量之后,就应该设法查出导致此现象的原因,为此,我们可以试着在程序发生崩溃的这个例程之内探查,也可以沿着调用栈向上寻找不正确的参数或与崩溃有关的其他因素(参见第3条和第32条)。

如果这些办法找不到原因,那么可以用调试器来多次调试程序,每次都在有可能发生运算错误的地方附近设置断点。像这样反复地设置断点并沿着调用序列上移,或许可以帮助我们查出问题的原因。

第二种情况是程序冻结(freeze),这与程序崩溃有所区别,因此我们向上排查所用的办法也稍有不同。我们可以用调试器来运行程序,或将其连接到程序上面,然后用相应的调试器命令来中断其执行过程(参见第30条),或使程序生成内存转储信息(参见第35条)。有时你会发现,程序所执行的某些代码,并不是该程序自身的代码,而是某个程序库中的例程。无论中断发生在何处,我们都可以沿着调用栈向上排查,以便确定导致程序冻结的那个循环。检查该循环的终止条件,并试着找出它永远无法得到满足的原因。

第三种情况是程序在出现问题时发出了错误消息,此时我们首先应该在程序的源代码里找到消息文本的位置。这可以通过fgrep-r命令轻松地实现(参见第22条),该命令能够在任意深度和复杂度的目录结构中快速定位到待搜索的词句。对于当今很多本地化的软件来说,该命令所定位到的内容,通常并不是发出错误消息的那行代码,而是与错误消息相对应的那个字符串资源文件。例如,如果你住在讲西班牙语的地方,并且正在调试Inkscape绘图程序中与“Ha ocurrido un error al procesar el archivo XCF”错误消息有关的问题,那么用fgrep-r命令搜索Inkscape的源代码之后,它就可能会把你引向名为es.po的西班牙语字符串翻译文件:

从字符串翻译文件中,我们可以得知与错误消息相对应的源码位置(对于上例来说,就是share/extensions/gimp_xcf.py文件的第43行)。然后,我们可以在发出错误消息的源代码这里设置断点,或在它之前插入log语句,以检查程序运行到此处所发生的问题。在这种情况下,我们有可能也要后退几行或沿着调用栈向上回溯几层,才能够找到问题的根源。如果你要搜索的是非ASCII文本,那么请确保命令行的locale(区域)设置与源代码所用的文本编码(如UTF-8)相符。

如果无法确定与故障有关的代码到底在哪里,那我们就应该从顶层系统开始,逐步向下查找故障原因。从定义上来说,这种故障通常属于系统的涌现属性(emergent property),也就是无法与某个具体部分直接对应起来的属性,例如,性能问题(软件占用的内存过多或响应时间过长)、安全问题(Web应用程序的页面遭到破坏)以及可靠性问题(软件无法提供预期的Web服务)等。

要想由上而下地排查错误,我们需要把整个程序分成多个部分,然后分别判断每一部分在引发当前故障的各种因素中可能占多大的比例。对于性能问题来说,常见的办法是做profile(性能分析),也就是用一些工具和程序库来帮助我们寻找占用CPU资源及内存过多的例程。对于安全问题来说,我们要检查代码中有哪些地方可能出现常见的安全漏洞,如缓冲区溢出、代码注入以及跨站脚本攻击等。面对这类问题,我们也可以求助于一些代码分析工具(参见第51条)。最后,对于无法提供Web服务的问题来说,我们需要审视内部和外部的各种依赖关系,看看它们有没有在正常地运作。

要点

  • 如果能够明确指出故障的原因,那么应该从下往上查找错误,例如,在程序崩溃、程序冻结以及程序发出错误消息等情况下,就应该如此。
  • 如果故障的原因很难锁定,那么应该从上往下查找错误,例如,在遇到性能问题、安全问题以及可靠性问题的时候,就应该如此。
时间: 2024-09-20 08:07:47

《Effective Debugging:软件和系统调试的66个有效方法》——第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug的相关文章

《Effective Debugging:软件和系统调试的66个有效方法》——导读

前 言 我们在开发软件或对运行软件的系统进行管理的时候,经常会遇到故障.有些故障是因代码问题而引发的编译错误,这种故障可以在短时间内修复:还有一些故障则会使大型系统停机,这将给公司带来每小时数百万的损失(具体货币单位依情况而定).要想成为一名优秀的专业人士,你就必须在发生故障时迅速找出背后的原因并加以修复.这正是调试的意义所在,也是本书所要谈论的主题. 本书是写给有一定经验的开发者看的,而不是一本介绍性质的读物.它假设读者能够理解用各种编程语言所写成的代码片段,并且会使用高级的GUI编程工具以及

《Effective Debugging:软件和系统调试的66个有效方法》一导读

前 言 我们在开发软件或对运行软件的系统进行管理的时候,经常会遇到故障.有些故障是因代码问题而引发的编译错误,这种故障可以在短时间内修复:还有一些故障则会使大型系统停机,这将给公司带来每小时数百万的损失(具体货币单位依情况而定).要想成为一名优秀的专业人士,你就必须在发生故障时迅速找出背后的原因并加以修复.这正是调试的意义所在,也是本书所要谈论的主题. 本书是写给有一定经验的开发者看的,而不是一本介绍性质的读物.它假设读者能够理解用各种编程语言所写成的代码片段,并且会使用高级的GUI编程工具以及

《Effective Debugging:软件和系统调试的66个有效方法》一第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug

第4条:从具体问题入手向上追查bug,或从高层程序入手向下追查bug 要想确定问题的来源,通常有两种办法.一种是从问题的具体表现入手,向上追查其来源,还有一种是从应用程序或系统的顶层入手,逐步向下探查,直至找到其根源.对于某种类型的问题来说,其中一种方法的效果通常要比另一种更好,但是如果你在采用某个方法时遇到了困境,那么不妨试试另一个方法.如果问题表现得很明确,那我们就应该从发生问题的地方入手,向上追查bug.这可以分成三种情况.第一种情况是程序崩溃.在这种情况下,为了便于排查问题,我们通常可以

《Effective Debugging:软件和系统调试的66个有效方法》一第6条:使用软件自身的调试机制

第6条:使用软件自身的调试机制 程序是一种很复杂的东西,因此它们通常都包含内置的调试机制.(至于怎样给自己正在开发的软件里面添加这样的机制,请参见第40条.)这种机制有很多好处,其中包括:我们可以通过禁用后台执行或多线程执行等特性来简化程序的调试工作.我们可以有选择地执行其中某一部分功能,以便通过测试用例来精确地再现相关的故障.程序可以给我们提供与性能有关的报表及其他信息.程序可以把更多的信息记录在日志文件中.因此,我们应该花一些时间,看看自己要调试的这款软件内置了哪些调试机制.想要了解这些机制

《Effective Debugging:软件和系统调试的66个有效方法》一第8条:把工作焦点放在最为重要的问题上

第8条:把工作焦点放在最为重要的问题上 许多大型软件系统都含有数量极其众多的bug(有一些是已知的bug,还有一些则尚未发现).要想高效地进行调试,就必须把应该受到关注的bug与可以忽略的bug明智地区分开.这样做不是为了单纯地缩减事务清单中的未决事务,而是为了帮助我们开发出稳定.易用.可维护而且效率较高的软件,毕竟这才是公司给我们支付薪水的原因.为此,我们要通过事务追踪系统来设定各项事务的优先级(参见第1条),从而使自己能够把工作重心汇聚在优先级较高的那些事务上,并把优先级较低的事务忽略掉.下

《Effective Debugging:软件和系统调试的66个有效方法》——第8条:把工作焦点放在最为重要的问题上

第8条:把工作焦点放在最为重要的问题上 许多大型软件系统都含有数量极其众多的bug(有一些是已知的bug,还有一些则尚未发现).要想高效地进行调试,就必须把应该受到关注的bug与可以忽略的bug明智地区分开.这样做不是为了单纯地缩减事务清单中的未决事务,而是为了帮助我们开发出稳定.易用.可维护而且效率较高的软件,毕竟这才是公司给我们支付薪水的原因.为此,我们要通过事务追踪系统来设定各项事务的优先级(参见第1条),从而使自己能够把工作重心汇聚在优先级较高的那些事务上,并把优先级较低的事务忽略掉.下

《Effective Debugging:软件和系统调试的66个有效方法》——第6条:使用软件自身的调试机制

第6条:使用软件自身的调试机制 程序是一种很复杂的东西,因此它们通常都包含内置的调试机制.(至于怎样给自己正在开发的软件里面添加这样的机制,请参见第40条.)这种机制有很多好处,其中包括: 我们可以通过禁用后台执行或多线程执行等特性来简化程序的调试工作. 我们可以有选择地执行其中某一部分功能,以便通过测试用例来精确地再现相关的故障. 程序可以给我们提供与性能有关的报表及其他信息. 程序可以把更多的信息记录在日志文件中. 因此,我们应该花一些时间,看看自己要调试的这款软件内置了哪些调试机制.想要了

《Effective Debugging:软件和系统调试的66个有效方法》一第5条:在能够正常运作的系统与发生故障的系统之间寻找差别

第5条:在能够正常运作的系统与发生故障的系统之间寻找差别 我们通常都能够同时访问这样两个系统,其中一个是发生故障的系统,另一个是与之相似但却可以正常运行的系统.当我们实现了某项新功能.更新了某些工具或基础组件,或是把系统部署在某个新的平台上面时,就可能会遇到新系统无法正常运行的问题,此时如果旧系统依然正常,那么我们通常可以通过寻找(下面就会讲到如何寻找)或尽量缩小(参见第45条)新旧两个系统之间的差别来锁定问题的原因.之所以能根据新旧系统间的差距来进行调试,其原因在于:尽管各人所经历的问题有所不

《Effective Debugging:软件和系统调试的66个有效方法》——第5条:在能够正常运作的系统与发生故障的系统之间寻找差别

第5条:在能够正常运作的系统与发生故障的系统之间寻找差别 我们通常都能够同时访问这样两个系统,其中一个是发生故障的系统,另一个是与之相似但却可以正常运行的系统.当我们实现了某项新功能.更新了某些工具或基础组件,或是把系统部署在某个新的平台上面时,就可能会遇到新系统无法正常运行的问题,此时如果旧系统依然正常,那么我们通常可以通过寻找(下面就会讲到如何寻找)或尽量缩小(参见第45条)新旧两个系统之间的差别来锁定问题的原因. 之所以能根据新旧系统间的差距来进行调试,其原因在于:尽管各人所经历的问题有所