如何使用时间旅行调试更快地调试 Linux C/C++ 应用程序





0/5 (0投票)
本教程演示了时间旅行调试的工作原理。
您是否觉得在调试上花费了太多时间?
我们似乎坚持使用日志记录、printf、核心转储分析或 gdb 进行调试;但这些传统技术严重依赖于猜测和在脑海中连接各个点。整个调试过程仍然繁琐且效率低下:我们必须重复运行程序,设置新的断点,缓慢地逐步执行代码 - 我们陷入该循环,直到获得所需的信息。
如果我们可以简单地向后追溯到问题的根源,而不是逐步向前执行代码呢?
值得庆幸的是,时间旅行调试提供了一种与传统调试完全不同的工作流程 - 这种流程使我们能够更快地修复软件错误,并 100% 确定地找到错误的根本原因。
本教程演示了时间旅行调试的工作原理。
此示例应用程序(cache calculate)崩溃,并显示一条模糊的错误消息。
让我们使用 UDB 打开该应用程序,以分析其执行情况并诊断此故障。 UDB 是 Undo 的扩展版 GDB,支持时间旅行调试。
让我们从 start
开始,在 main()
处设置断点并运行到该点。
这是相当标准的 GDB 用法,因此让我们继续,现在到达失败点。
与普通 GDB 一样,我们可以获取回溯以查看应用程序中失败的位置(或者在本例中为 assert()
谓词失败的位置)。
使用普通 GDB,我们可以在此时检查堆栈帧;但使用 UDB,我们能够反向运行应用程序的执行。
首先,我们可以使用 reverse-finish 命令在调用堆栈中反向移动到 assert()
。
请注意,在 UDB 中 - 就像在 GDB 中一样 - 在空行上按 Enter 键会重复上一个命令,因此这里需要 4 个 reverse-finish 命令才能返回到 main()
。
回到 main()
中,我们可以检查局部变量,以确认错误值在 sqroot_cache
中(应该是 15,因为 255 的整数平方根是 15)。
到目前为止,这基本上可以用 GDB 实现,查找调用堆栈并检查帧;但使用 UDB,反向执行使我们能够做更多的事情,而无需设置许多断点并迭代地重新运行应用程序(可能很多次才能到达根本原因)。
我们看到第 73 行是设置 sqroot_cache
变量的位置,因此我们可以使用 reverse-next
和 reverse-step
命令进一步反向执行,并 reverse-step
进入 cache_calculate()
函数。
这使我们能够直接到达此函数返回不正确值时的执行点,这是该应用程序中第 2011 次执行。 如果这是一个非确定性错误,那么以传统方式建立和设置合适的断点到达那里可能需要付出更多的努力。
在该函数中,我们可以看到不正确的返回值来自 cache[]
数组,因此该数组在过去的某个时间点被设置为不正确的值。
同样,传统的调试器需要多次迭代和步骤才能尝试跟踪此数组是如何损坏的。 使用 UDB,这要简单得多。 我们可以对数组中的此值设置观察点,然后使用另一个反向命令,这次是 reverse-continue
,继续向后运行应用程序,直到命中观察点。
如前所述,这里我们设置了观察点,所以让我们执行 reverse-continue
向后运行,直到此数组元素设置为值 0。
就是这么简单:reverse-continue
已经向后运行,并将我们带到了将错误值写入 cache[]
数组的点。
现在我们可以查看局部变量(并且可以从断点看到 cache_calculate()
函数的参数(number
变量)为 0)。
最后,我们有足够的根本原因来分析这次失败。 sqroot2
变量在第 45 行被写入,并且被设置为 sqrt(number2)
,即负 1 的平方根(在整数中无法表示)。
此应用程序失败最终发生的原因是未检查 for
循环迭代以填充缓存的值范围。 这导致 cache_calculate()
函数尝试使用负 1 的平方根填充 cache[]
数组。
注意:无符号字符值中的 -1 会环绕,因此 number
元素设置为 255。
想在自己的程序上尝试吗? Undo 在其网站上提供 UDB 的免费试用版。