GDB 调试指南(第 1 章):GDB 快速上手
第 1 章 GDB 快速上手:从编译到第一次调试
1.1 GDB 是什么,什么时候该用它
GDB(GNU Debugger)是 Linux/Unix 下最常用的命令行调试器,能让你在程序运行的过程中“暂停”下来,查看此刻内存里每个变量的真实值、调用栈长什么样、代码到底走到了哪一行。
什么时候该打开 GDB,而不是继续用 printf 满世界加日志?
- 程序崩溃(段错误、断言失败),但不知道崩在哪一行、当时的变量值是什么;
- 程序逻辑“看起来”没错,但结果不对,需要一步步看执行路径和变量变化;
- 需要在不重新编译的前提下,临时改一个变量的值看看后续行为会不会不一样;
- 面对别人的代码/库,想摸清楚它内部到底调用了什么。
【新手提示】
printf调试法不是不能用,但每加一行都要重新编译、重新跑一遍,效率很低,而且发布前还得把这些调试输出删掉。GDB 不需要改代码、不需要重新编译,随时下断点随时看。
1.2 准备工作:编译时必须带上调试信息
GDB 能看到多少东西,取决于编译时有没有带调试信息,以及带了多少。这是新手最容易忽略、也是后续所有问题里最常见的“地基问题”——如果这一步没做对,后面所有命令都会表现得很奇怪(看不到变量、看不到源码行号)。
1.2.1 -g 系列选项
GCC/G++ 通过 -g 系列选项控制写入可执行文件的调试信息多少,级别越高,文件越大,GDB 能看到的东西也越多。
g++ main.cpp -o cmd1 -g1
g++ main.cpp -o cmd2 -g2 # 等价于直接写 -g
g++ main.cpp -o cmd3 -g3
g++ main.cpp -o cmd # 不带 -g,完全不含调试信息
| 选项 | 调试信息详细程度 | 二进制体积 | 包含的关键信息 |
|---|---|---|---|
(不加 -g) |
无 | 最小 | 无调试段,file 命令会提示 not stripped 但没有 with debug_info |
-g1 |
最低 | 较小 | 仅行号、函数名;不含局部变量/参数的调试信息 |
-g2(默认,即直接写 -g) |
中等 | 中等 | 完整的行号、函数、局部变量、参数、结构体布局 |
-g3 |
最高 | 最大 | 在 -g2 基础上,额外包含宏定义、内联函数等信息 |
【新手提示】日常开发直接用
-g(相当于-g2)就够了。只有当你需要在 GDB 里展开宏(macro expand)辅助排查时,才需要-g3。正式发布的生产版本不要带-g,一是体积变大,二是会把函数名、变量名等内部信息暴露给反编译者。
1.2.2 怎么验证一个可执行文件到底有没有调试信息
拿到一个别人编译好的程序,不确定它有没有调试信息?两个办法:
$ file cmd1
cmd1: ELF 64-bit LSB pie executable ... with debug_info, not stripped
$ file cmd
cmd: ELF 64-bit LSB pie executable ... not stripped # 没有 with debug_info
出现 with debug_info 就说明带了调试信息。更细致地,可以用 readelf -S cmd1 -W 看段表,带 -g 编译的文件会多出一批 .debug_* 段(.debug_info、.debug_abbrev、.debug_line、.debug_str 等),这些段就是调试信息的实际载体。
1.2.3 不同级别对调试体验的实际影响
用同一个断点 b main 分别调试 -g1、-g2、-g3 编译出的程序,差异非常直观:
查看局部变量(i locals)
(gdb) # 使用 -g1 编译的 cmd1
(gdb) i locals
No locals. # -g1 没有变量调试信息,即使局部变量真实存在也看不到
(gdb) # 使用 -g2 编译的 cmd2
(gdb) i locals
a = {-8704, 32767, -137661553, 32767, -135812528}
p = 0x6c6f6f705f68652e # -g2 开始能看到真实的局部变量值
查看宏定义(macro expand / info macro)
(gdb) # -g1 / -g2 均无宏调试信息
(gdb) macro expand MAX
expands to: MAX # 无法展开,原样返回,说明 GDB 根本不知道这个宏
(gdb) # -g3 编译的 cmd3
(gdb) macro expand MAX
expands to: (9988) # 正确展开为宏的真实值
1.2.4 【常见误区】改了代码忘记重新编译
调试信息里记录的是“编译那一刻”源码的行号和内容。如果你改了 .cpp 文件但忘了重新 g++ 编译,GDB 会发现源码文件的修改时间比可执行文件新,提示:
warning: Source file is more recent than executable.
这时候你看到的行号、断点位置,对应的其实是改动之前的旧代码,跟你正在看的源码对不上,会造成很大的困惑。看到这条警告,第一反应应该是回去重新编译,而不是怀疑 GDB 出 bug 了。
1.3 第一次启动 GDB:完整走一遍流程
准备好一个带调试信息的可执行文件后,正式开始。
1.3.1 启动 GDB
gdb ./cmd # 直接加载可执行文件
gdb -q ./cmd # -q 静默模式,不打印版权横幅(推荐日常使用)
首次启动时 GDB 可能会询问是否启用 debuginfod(自动从网络下载调试符号):
Enable debuginfod for this session? (y or [n]) n
内网/离线环境建议选 n,并按提示把下面这行写进 ~/.gdbinit,一劳永逸地关掉这个提示:
set debuginfod enabled off
【新手提示】
~/.gdbinit是 GDB 的启动配置文件,每次启动都会自动读取执行里面的命令,适合放一些你每次都要设置的选项(比如关掉 debuginfod 提示、设置set listsize 30等)。不需要专门去学复杂语法,先知道有这个文件、可以往里面扑普通 GDB 命令即可。
若程序没有调试信息,GDB 会在启动时提示:
(No debugging symbols found in ./cmd)
此时仍可以反汇编、看寄存器,但不能用 break 行号、i locals、list 这类依赖源码/符号信息的命令——这也是前面反复强调“一定要带 -g 编译”的原因。
1.3.2 走一遍最小闭环:下断点 → 运行 → 单步 → 看变量 → 继续
下面这组命令是你之后几乎每次调试都会重复的基本动作,先建立整体感觉,细节在后面几章展开:
$ gdb -q ./cmd
Reading symbols from ./cmd...
(gdb) b main # 在 main 函数入口下一个断点
Breakpoint 1 at 0x11e4: file main.cpp, line 91.
(gdb) r # 运行程序,会在刚才的断点处停下
Starting program: /path/to/cmd
Breakpoint 1, main () at main.cpp:91
91 int main() {
(gdb) n # 单步执行一行(不进入被调用函数内部)
92 int a = 123;
(gdb) n
93 char buf[128] = "456";
(gdb) p a # 查看变量 a 当前的值
$1 = 123
(gdb) c # 继续运行,直到下一个断点或程序结束
Continuing.
...
[Inferior 1 (process 12345) exited normally]
(gdb) q # 退出 GDB
逐条解释一下这几个命令在做什么:
b main(break的缩写):在main函数第一行有效代码处打一个断点,程序跑到这里会自动暂停。r(run的缩写):真正启动程序,程序会一直跑到第一个断点才停下来,而不是跑完。n(next的缩写):执行当前这一行,然后停在下一行,不会“钻进”这一行里调用的函数内部。p a(print的缩写):打印表达式a当前的值。a可以是变量名,也可以是更复杂的表达式,比如a + 1、arr[2]。c(continue的缩写):从当前暂停的地方继续往下跑,直到遇到下一个断点,或者程序自然结束。q(quit的缩写):退出 GDB。如果程序还在运行中,会询问是否要连带结束它。
【新手提示】GDB 的命令基本都支持“输入前缀唯一即可”的缩写规则,所以
break/b、run/r、next/n、p、continue/c可以混用,不需要每次都打全称。具体每个命令的标准缩写见第 5 章速查表。
跑完这一遍,你已经摸到了 GDB 最核心的工作模式:用断点把程序“钉”在某个位置,然后用单步 + 查看变量去验证你对代码行为的假设。后面几章分别把“断点”“运行参数”“单步与查看状态”这三块展开讲透。
1.4 小结
- 调试信息是一切的前提:忘记
-g或忘记重新编译,是新手最容易踩、但一旦意识到就很容易避免的坑。 file/readelf -S可以在不打开 GDB 的情况下,先确认一个可执行文件到底有没有调试信息。- GDB 的基本工作循环就是:下断点(
break)→ 运行(run)→ 单步(next/step)→ 查看状态(print等)→ 继续(continue),后面所有章节都是在这个循环上做细化。

浙公网安备 33010602011771号