GNU 调试器是一个发现程序缺陷的强大工具。
如果你是一个程序员,想在你的软件增加某些功能,你首先考虑实现它的方法:例如写一个方法、定义一个类,或者创建新的数据类型。然后你用编译器或解释器可以理解的编程语言来实现这个功能。但是,如果你觉得你所有代码都正确,但是编译器或解释器依然无法理解你的指令怎么办?如果软件大多数情况下都运行良好,但是在某些环境下出现缺陷怎么办?这种情况下,你得知道如何正确使用调试器找到问题的根源。
GNU 调试器 GNU Project Debugger (GDB)是一个发现项目缺陷的强大工具。它通过追踪程序运行过程中发生了什么来帮助你发现程序错误或崩溃的原因。(LCTT 校注:GDB 全程是“GNU Project Debugger”,即 “GNU 项目调试器”,但是通常我们简称为“GNU 调试器”)
本文是 GDB 基本用法的实践教程。请跟随示例,打开命令行并克隆此仓库:
git clone https://github.com/hANSIc99/core_dump_example.git
快捷方式
GDB 的每条命令都可以缩短。例如:显示设定的断点的 info break
命令可以被缩短为 i break
。你可能在其他地方看到过这种缩写,但在本文中,为了清晰展现使用的函数,我将所写出整个命令。
命令行参数
你可以将 GDB 附加到每个可执行文件。进入你克隆的仓库(core_dump_example
),运行 make
进行编译。你现在能看到一个名为 coredump
的可执行文件。(更多信息,请参考我的文章《创建和调试 Linux 的转储文件》。)
要将 GDB 附加到这个可执行文件,请输入: gdb coredump
。
你的输出应如下所示:
返回结果显示没有找到调试符号。
调试信息是 目标文件 object file (可执行文件)的组成部分,调试信息包括数据类型、函数签名、源代码和操作码之间的关系。此时,你有两种选择:
- 继续调试汇编代码(参见下文“无符号调试”)
- 使用调试信息进行编译,参见下一节内容
使用调试信息进行编译
为了在二进制文件中包含调试信息,你必须重新编译。打开 Makefile
,删除第 9 行的注释标签(#
)后重新编译:
CFLAGS =-Wall -Werror -std=c++11 -g
-g
告诉编译器包含调试信息。运行 make clean
,接着运行 make
,然后再次调用 GDB。你得到如下输出后就可以调试代码了:
新增的调试信息会增加可执行文件的大小。在这种情况下,执行文件增加了 2.5 倍(从 26,088 字节 增加到 65,480 字节)。
输入 run -c1
,使用 -c1
开关启动程序。当程序运行到达 State_4
时将崩溃:
你可以检索有关程序的其他信息,info source
命令提供了当前文件的信息:
- 101 行代码
- 语言: C++
- 编译器(版本、调优、架构、调试标志、语言标准)
- 调试格式:DWARF 2
- 没有预处理器宏指令(使用 GCC 编译时,宏仅在 使用 -g3 标志编译 时可用)。
info shared
命令打印了动态库列表机器在虚拟地址空间的地址,它们在启动时被加载到该地址,以便程序运行:
如果你想了解 Linux 中的库处理方式,请参见我的文章 在 Linux 中如何处理动态库和静态库。
调试程序
你可能已经注意到,你可以在 GDB 中使用 run
命令启动程序。run
命令接受命令行参数,就像从控制台启动程序一样。-c1
开关会导致程序在第 4 阶段崩溃。要从头开始运行程序,你不用退出 GDB,只需再次运行 run
命令。如果没有 -c1
开关,程序将陷入死循环,你必须使用 Ctrl+C
来结束死循环。
你也可以一步一步运行程序。在 C/C++ 中,入口是 main
函数。使用 list main
命令打开显示 main
函数的部分源代码:
main
函数在第 33 行,因此可以输入 break 33
在 33 行添加断点:
输入 run
运行程序。正如预期的那样,程序在 main
函数处停止。输入 layout src
并排查看源代码:
你现在处于 GDB 的文本用户界面(TUI)模式。可以使用键盘向上和向下箭头键滚动查看源代码。
GDB 高亮显示当前执行行。你可以输入 next
(n
)命令逐行执行命令。如果你没有指定新的命令,GBD 会执行上一条命令。要逐行运行代码,只需按回车键。
有时,你会发现文本的输出有点显示不正常:
如果发生这种情况,请按 Ctrl+L
重置屏幕。
使用 Ctrl+X+A
可以随时进入和退出 TUI 模式。你可以在手册中找到 其他的键绑定 。
要退出 GDB,只需输入 quit
。
设置监察点
这个示例程序的核心是一个在无限循环中运行的状态机。n_state
变量枚举了当前所有状态:
while(true){
switch(n_state){
case State_1:
std::cout