4. GDB基本命令:run、continue、next、step、list、print、quit
调试器这东西,说白了就是让你能「看」着程序跑。GDB 最核心的几条命令,我用了十几年,每天还在用。今天咱们就把它们捋清楚。
4.1 启动与运行:run 和 quit
run 是启动程序的命令。在 GDB 里加载完可执行文件后,敲 run 就开始跑了。我个人习惯在 run 后面直接带参数,比如:
(gdb) run arg1 arg2 < input.txt
这等价于在 shell 里执行 ./a.out arg1 arg2 < input.txt。嗯,这里有个小坑——如果你忘了加参数,程序跑起来发现不对劲,还得 quit 退出去重来。
quit 就是退出 GDB。没什么好说的,但注意:如果你正在调试一个卡住的程序,直接 quit 可能会让进程变成僵尸。我建议先 kill 掉再退。
run 启动后,程序崩溃了,GDB 会自动停在崩溃点。这时候别急着 quit,先看看堆栈。
4.2 断点后的控制:continue、next、step
这三个命令是调试的核心。它们决定了程序怎么「走」。
continue(简写 c):让程序继续跑,直到遇到下一个断点或结束。我在项目中遇到过一种情况:一个死循环里打了断点,每次 continue 都停在同一行。后来发现是循环条件写错了——continue 只是「放行」,不是「跳过」。
next(简写 n):单步执行,但遇到函数调用会「跨过去」。说白了,它把整个函数调用当成一步。举个例子:
int main() {
int a = foo(); // 断点停在这里
int b = bar();
return 0;
}
如果你用 next,GDB 会直接执行完 foo() 并停在下一行。你不需要关心 foo 内部发生了什么。
step(简写 s):单步执行,但遇到函数调用会「钻进去」。还是上面那个例子,用 step 就会进入 foo() 函数内部。我曾经用 step 追过一个诡异的段错误,发现是某个库函数里传了个空指针——要是用 next 就漏掉了。
finish 可以跳出当前函数。
4.3 查看代码与变量:list 和 print
list(简写 l):显示源代码。默认显示当前行附近的 10 行。你可以指定行号或函数名:
(gdb) list 42 // 显示第42行附近
(gdb) list main // 显示main函数
(gdb) list foo.c:10 // 显示foo.c的第10行
我个人习惯用 list 快速定位上下文。调试时看到断点停在哪,敲个 list 就能看到周围代码,比切回编辑器快多了。
print(简写 p):打印变量或表达式的值。这是最常用的命令,没有之一。基本用法:
(gdb) print x
$1 = 42
(gdb) print &x
$2 = (int *) 0x7fffffffe4a0
(gdb) print x + y
$3 = 100
你想想看,print 还能打印结构体、数组、指针。我调试网络程序时经常用 print *p 看指针指向的内容。有一次发现一个链表节点指向了野地址,就是靠 print 一步步查出来的。
4.4 命令速查表
| 命令 | 简写 | 作用 | 常用场景 |
|---|---|---|---|
| run | r | 启动程序 | 开始调试 |
| continue | c | 继续执行 | 断点后放行 |
| next | n | 单步跳过函数 | 不关心函数内部 |
| step | s | 单步进入函数 | 追踪函数内部逻辑 |
| list | l | 显示源代码 | 查看上下文 |
| p | 打印变量/表达式 | 检查变量值 | |
| quit | q | 退出 GDB | 结束调试 |
4.5 知识体系图
下面这张图展示了本章命令之间的逻辑关系:
4.6 避坑指南
- 我曾经用
next调试一个多线程程序,结果发现 next 只影响当前线程,其他线程还在跑。嗯,多线程调试得用thread apply all配合。 - 我曾经在
print一个超大数组时,GDB 卡了十几秒。后来才知道可以用print *array@10只打印前 10 个元素。 - 注意:
list显示的是源代码,如果编译时没加-g选项,你只能看到汇编。所以编译时一定要加-g。
list 看上下文,然后 print 关键变量,再用 next 或 step 一步步走。遇到可疑的函数调用,果断 step 进去看看。
说白了,这七个命令就是 GDB 的「七种武器」。run 和 quit 管开始结束,continue、next、step 管流程控制,list 和 print 管信息查看。掌握了它们,你已经能解决 80% 的调试问题了。