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

Posted

技术标签:

【中文标题】什么是调试器,它如何帮助我诊断问题?【英文标题】:What is a debugger and how can it help me diagnose problems? 【发布时间】:2022-01-04 04:42:29 【问题描述】:

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

这个问题涵盖了三类更具体的问题:

当我运行我的程序时,它不会产生我期望的输出作为我给它的输入。 当我运行我的程序时,它会崩溃并给我一个堆栈跟踪。我有examined the stack trace,但我仍然不知道问题的原因,因为堆栈跟踪没有为我提供足够的信息。 当我运行我的程序时,它由于分段错误 (SEGV) 而崩溃。

【问题讨论】:

干得好——最好有一个关于调试技术的相关“转到”问答,例如使用调试器、其他调试工具(例如 valgrind)、策略性 printfs、压力测试、分而治之等。 我同意@PaulR,FAQ 应该包含这样的内容。 此问题被标记为“与语言无关”,但它包含指向特定于 Java 编程语言的问题的链接。恐怕这个链接可能会导致更多的混乱而不是帮助,因为大多数阅读这个问题的人可能不了解 Java。 【参考方案1】:

调试器是可以在程序运行时检查程序状态的程序。 technical means it uses for doing this 对于了解如何使用调试器的基础知识并不重要。当程序到达代码中的特定位置时,您可以使用调试器暂停程序的执行,然后检查程序中变量的值。您可以使用调试器非常缓慢地运行程序,一次一行代码(称为单步执行),同时检查其变量的值。

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

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

调试器如何帮助您

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

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

使用调试器的一般说明

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

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

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

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

大多数modern debuggers are part of an IDE 并为您提供方便的 GUI 用于检查程序的源代码和变量,以及用于设置断点、运行程序和单步执行的点击式界面。

除非您的程序可执行文件或字节码文件包含调试符号信息和对源代码的交叉引用,否则使用调试器可能会非常困难。您可能必须compile (or recompile) your program slightly differently 以确保信息存在。如果编译器执行大量优化,这些交叉引用可能会变得混乱。因此,您可能需要recompile your program with optimizations turned off。

【讨论】:

这是不完整的,因为它错过了最重要的调试器,它有可能非常显着地减少 *** 上的问题数量(我预测至少 20%) - javascript 调试器:firebug 、Chrome、Firefox、IE9+集成调试器、IE8-Visual Studio等 也适用于 node.js - 节点检查器。但是 node.js 程序员不会像一般的 javascript 程序员那样问那么多基本的和/或修复我的代码的问题。 为基本调试想法添加用例可能会很有用,例如设置断点、观察变量和不同类型的步骤,以及详细说明您实际调查问题所遵循的一般过程一个调试器。目前这似乎更像是“你应该学习使用调试器”而不是“这是你如何使用调试器”。【参考方案2】:

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

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

在所有这些情况下,让您的程序突然停止可能会导致最终结果不同,或者手动逐步搜索导致错误的一行太麻烦了。无论您的错误是错误行为还是崩溃,这同样可能发生。例如,如果内存损坏导致崩溃,那么当崩溃发生时,它距离内存损坏第一次发生的位置太远了,并且没有留下任何有用的信息。

那么,有哪些替代方案?

最简单的就是记录和断言。在不同的点将日志添加到您的程序中,并将您获得的内容与您的期望进行比较。例如,查看您认为存在错误的函数是否首先被调用。看看方法开头的变量是否是你认为的那样。与断点不同,有许多日志行没有什么特别的事情发生是可以的。之后您可以简单地搜索日志。一旦您遇到与您期望的不同的日志行,请在同一区域添加更多内容。将它越来越窄,直到它小到可以记录错误区域中的每一行。

断言可用于在错误值出现时捕获它们,而不是在它们对最终用户产生影响时捕获。你越快捕捉到错误的值,你就越接近产生它的行。

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

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

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

【讨论】:

这将是一个很好的答案......另一个问题。 this 问题的答案不好。也许您应该问这个问题并将其发布为回复。 实际问题被描述为“帮助遇到程序问题的新程序员”、“它没有产生我期望的输出”和“我已经检查了堆栈跟踪,但我仍然这样做不知道问题的原因”。所有这些都得到了这个答案的帮助。此外,在解释调试器做什么时,解释它做什么同样重要。 很好的答案。我一直使用调试器作为主要工具来查找错误。但是现在我正在一个项目中工作,其中一个巨大的基础设施组件正在使用许多线程和大量网络代码(客户端/服务器),并注意到调试器是帮助我的最后一件事。您提到了很多事情,您应该真正使用不同的工具,而不是依赖您的旧调试器(最重要的是:日志记录)。 “你可以看出你的 bug 是由到达函数 X 的错误值引起的,但你不知道这些值来自哪里” 这尤其难以调试。你通常如何解决这样的问题? @Ayxan 在某种程度上,如果您设法使函数在断言上中断,则可以使用调用堆栈来获取调用者。但仅此一项并不能为您提供值的来源,因为该值很可能来自较早的行。您基本上必须通过它所经历的各种变量来跟踪该值。如果您对数据的路径有一个很好的了解,您可以创建一堆日志打印,并尝试缩小它“出错”的位置。如果没有,您基本上需要为每一步后退单独运行程序(重现错误)。

以上是关于什么是调试器,它如何帮助我诊断问题?的主要内容,如果未能解决你的问题,请参考以下文章

我需要帮助使用 MS 调试诊断工具来分析 CPU 使用率高的 C# 应用程序

如何禁用诊断工具?

如何诊断/调试 nodejs/expressjs 的 500 内部服务器错误?

深入理解python虚拟机:调试器实现原理与源码分析

Arthas在线java进程诊断工具 在线调试神器

Linux下性能测量和调试诊断工具Systemtap