什么是调试器,它如何帮助我诊断问题?

What is a debugger and how can it help me diagnose problems?

提问人:Raedwald 提问时间:8/19/2014 最后编辑:Peter MortensenRaedwald 更新时间:8/6/2023 访问量:24672

问:

这是一个通用问题,旨在帮助程序有问题但不知道如何使用调试器来诊断问题原因的新程序员。

本题涵盖三类更具体的问题:

  • 当我运行我的程序时,它不会为我给出的输入产生我期望的输出。
  • 当我运行我的程序时,它会崩溃并给我一个堆栈跟踪。我已经检查了堆栈跟踪,但我仍然不知道问题的原因,因为堆栈跟踪没有为我提供足够的信息。
  • 当我运行我的程序时,它由于分段错误SEGV) 而崩溃。
调试 与语言无关

评论

6赞 Paul R 8/19/2014
干得不错 - 有一个相关的“去”调试技术问答也很好,例如使用调试器、其他调试工具(例如 valgrind)、战略 printfs、压力测试、分而治之等。
1赞 Nicu Stiurca 10/30/2014
我同意@PaulR,常见问题解答应该包含这样的东西。
1赞 Andreas Wenzel 5/10/2021
此问题被标记为“与语言无关”,但它包含指向特定于 Java 编程语言的问题的链接。恐怕这个链接可能造成的混乱多于帮助,因为大多数阅读这个问题的人可能不懂 Java。
2赞 Christian Vincenzo Traina 9/6/2022
在 2022 年,这个问题只有 2 个答案,没有一个提到堆栈或分段错误。也许是时候编辑问题了
3赞 Peter Mortensen 11/18/2022
那些想在评论中将其用作一般参考或重复目标的人应该首先阅读不具体的答案(例如,其中几乎没有“如何”)。目前有 1,683 个与此相关的问题相关的元问题

答:

102赞 Raedwald 8/19/2014 #1

调试器是一个程序,可以在程序运行时检查程序的状态。它用于执行此操作的技术方法对于理解使用调试器的基础知识不是必需的。当程序到达代码中的特定位置时,可以使用调试器停止程序的执行,然后检查程序中变量的值。可以使用调试器非常缓慢地运行程序,一次运行一行代码(称为单步执行),同时检查其变量的值。

使用调试器是一项预期的基本技能

调试器是一个非常强大的工具,用于帮助诊断程序问题。调试器可用于所有实用的编程语言。因此,能够使用调试器被认为是任何专业或发烧友程序员的基本技能。自己使用调试器被认为是在向他人寻求帮助之前应该自己完成的基本工作。由于此站点是为专业和爱好者程序员准备的,而不是帮助台或指导站点,因此如果您对特定程序的问题有疑问,但尚未使用调试器,则您的问题很可能会被关闭并投反对票。如果你坚持这样的问题,你最终将被阻止发布更多内容。

调试器如何帮助你

通过使用调试器,可以发现变量是否具有错误的值,以及其值在程序中的位置更改为错误的值。

使用单步执行,您还可以发现控制流是否符合预期。例如,分支是否按预期执行。if

有关使用调试器的一般说明

使用调试器的具体细节取决于调试器,在较小程度上取决于所使用的编程语言。

  • 可以将调试器附加到已在运行程序的进程。如果您的程序卡住了,您可能会这样做。

  • 在实践中,从一开始就在调试器的控制下运行程序通常更容易。

  • 通过指示应停止执行的源代码文件和行号,或指示程序应停止的方法/函数的名称(如果要在执行进入方法后立即停止),可以指示程序应停止执行的位置。调试器用于使程序停止的技术方法称为断点,此过程称为设置断点

  • 大多数现代调试器都是 IDE 的一部分,它为您提供了一个方便的 GUI,用于检查程序的源代码和变量,并具有用于设置断点、运行程序和单步执行的点击式界面。

  • 使用调试器可能非常困难,除非程序可执行文件或字节码文件包含调试符号信息和对源代码的交叉引用。您可能需要以略微不同的方式编译(或重新编译)程序,以确保信息存在。如果编译器执行大量优化,这些交叉引用可能会变得混乱。因此,您可能必须在关闭优化的情况下重新编译程序

评论

8赞 slebetman 8/25/2014
这是不完整的,因为它错过了最重要的调试器,它有可能非常显着地减少 Stackoverflow 上的问题数量(我预测至少减少 20%)——javascript 调试器:firebug、Chrome、Firefox、IE9+ 集成调试器、IE8-Visual Studio 等。
3赞 slebetman 8/25/2014
也适用于 node.js - 节点检查器。但是 node.js 程序员不会像一般的 javascript 程序员那样提出那么多的基本和/或修复代码问题。
2赞 Bernhard Barker 3/29/2021
为基本调试想法添加用例(例如设置断点、监视变量和不同类型的步骤)以及详细说明实际调查调试器问题所遵循的一般过程可能很有用。目前,这似乎更像是“你应该学习使用调试器”,而不是“这是你使用调试器的方式”。
0赞 Jesper Juhl 4/12/2023
rr 的链接似乎与此相关。有时,在调试时能够回到过去,并且能够多次重播完全相同的程序运行,这非常有用
69赞 SlugFiller 4/5/2015 #2

我想补充一点,调试器并不总是完美的解决方案,也不应该总是调试的首选解决方案。下面是调试器可能不适合你的几种情况:

  • 程序中失败的部分非常大(也许是模块化程度很差?),而且您不确定从哪里开始单步执行代码。逐步完成所有这些操作可能太耗时了。
  • 您的程序使用了很多回调和其他非线性流控制方法,这使得调试器在单步执行时感到困惑。
  • 您的程序是多线程的。或者更糟糕的是,您的问题是由竞争条件引起的。
  • 包含该 bug 的代码在出现 bug 之前会运行多次。这在主循环中可能特别成问题,或者更糟糕的是,在物理引擎中,问题可能是数字问题。在这种情况下,即使设置断点,也只是让你多次点击它,而错误不会出现。
  • 程序必须实时运行。对于连接到网络的程序来说,这是一个大问题。如果你在网络代码中设置了一个断点,另一端不会等待你单步执行,它只是会超时。依赖系统时钟的程序,例如带有跳帧的游戏,也好不到哪里去。
  • 您的程序执行某种形式的破坏性操作,例如写入文件或发送电子邮件,并且您希望限制运行它所需的次数。
  • 你可以说你的错误是由到达函数 X 的错误值引起的,但你不知道这些值是从哪里来的。必须一遍又一遍地运行程序,将断点设置得越来越远,这可能是一个巨大的麻烦。特别是当函数 X 在整个程序中的许多位置被调用时。

在所有这些情况下,要么让你的程序突然停止,要么导致最终结果不同,要么手动单步执行以寻找导致错误的一行,这太麻烦了。无论您的错误是不正确的行为还是崩溃,这同样会发生。例如,如果内存损坏导致崩溃,则在崩溃发生时,它离内存损坏最初发生的位置太远,并且不会留下任何有用的信息。

那么,有哪些替代方案呢?

最简单的就是日志记录和断言。在不同时间点将日志添加到程序中,并将您获得的内容与预期内容进行比较。例如,查看您认为存在错误的函数是否首先被调用。查看方法开头的变量是否是您认为的变量。与断点不同,可以有许多日志行,其中没有发生任何特殊情况。之后,您可以简单地搜索日志。一旦你击中了与你期望的日志线不同的日志线,就在同一区域添加更多。将范围缩小到越来越远,直到它足够小,能够记录错误区域中的每一行。

断言可用于在错误值发生时捕获错误值,而不是在它们对最终用户可见时捕获它们。您越快捕获不正确的值,您就越接近产生该值的行。

重构和单元测试。如果您的程序太大,则一次测试一个类或一个函数可能是值得的。给它输入,看看输出,看看哪些不是你所期望的。能够将 bug 从整个程序缩小到单个函数可以对调试时间产生巨大影响。

如果发生内存泄漏或内存踩踏,请使用能够在运行时分析和检测这些情况的适当工具。能够检测实际损坏发生的位置是第一步。在此之后,您可以使用日志返回到引入错误值的位置。

请记住,调试是一个向后的过程。你有最终结果 - 一个错误 - 并找到之前的原因。这是关于向后工作,不幸的是,调试器只能向前迈进。这就是良好的日志记录和事后分析可以为您提供更好结果的地方。

评论

13赞 Raedwald 4/7/2015
这将是一个很好的答案......一个不同的问题。这个问题的答案很糟糕。也许你应该问这个问题,并将其作为对它的回复发布。
27赞 SlugFiller 4/7/2015
实际问题被描述为“帮助程序出现问题的新程序员”、“它没有产生我期望的输出”和“我已经检查了堆栈跟踪,但我仍然不知道问题的原因”。所有这些都得到了这个答案的帮助。此外,在解释调试器的作用时,解释它做什么也同样重要。
5赞 Tim Schmelter 11/24/2017
很好的答案。我总是使用调试器作为查找错误的主要工具。但是现在我正在一个项目中工作,其中一个巨大的基础设施组件使用许多线程和大量网络代码(客户端/服务器),并注意到调试器是帮助我的最后一件事。你已经提到了很多事情,你真的应该使用不同的工具,而不是依赖你旧的调试器(最重要的是:日志记录)。
3赞 SlugFiller 1/11/2019
@Ayxan 在某种程度上,如果您设法使函数在断言时中断,则可以使用调用堆栈来获取调用方。但仅凭这一点并不能为您提供值的来源,因为该值很可能来自前面的一行。你基本上必须通过它所经历的各种变量来跟踪这个值。如果你对数据所采用的路径有一个很好的了解,你可以创建一堆日志打印,并尝试缩小它“出错”的范围。如果没有,您基本上需要为每一步后退一步单独运行程序(重现错误)。
5赞 phuclv 7/17/2021
The code that has the bug in it runs many times before it bugs out这很容易解决。所有好的调试器都能够执行条件断点。例如,在 MSVC 中,可以将其设置为在多次命中断点后中断,或者设置为特定条件(如str == "abc" && i > 40000