2. GDB基础命令:run、break、next、step、continue、print、quit
好,咱们正式开始动手了。这一章我带你过一遍GDB最核心的七个命令。说白了,这七个命令就是你调试生涯的「七种武器」。我当年刚学GDB时,就靠这七个命令走天下,后来遇到再复杂的bug,也是从它们开始拆解的。
2.1 启动与运行:run
run命令,简写是r。它的作用就是启动你正在调试的程序。你想想看,GDB加载完程序后,程序还没跑起来呢,你得告诉它「开始吧」。
基本用法:
(gdb) run
(gdb) r # 简写形式
(gdb) run arg1 arg2 # 带命令行参数启动
(gdb) run < input.txt # 重定向输入
我个人习惯在启动前先设好断点,然后再run。为什么?因为程序一旦跑起来,可能很快就结束了,你根本没机会打断它。我在项目中遇到过好几次这种尴尬——忘了设断点,程序直接跑完,白忙活一场。
小技巧:如果程序已经跑过一遍了,想重新跑,直接敲run就行。GDB会问你是否重启,回答y即可。或者你也可以用start命令——它相当于run加一个自动断点在main函数入口处。
2.2 设置断点:break
break,简写b。这是调试的灵魂。没有断点,调试就像蒙着眼睛找东西。
| 用法 | 示例 | 说明 |
|---|---|---|
| 按行号 | break 42 |
在当前源文件的第42行停下 |
| 按函数名 | break main |
在main函数入口处停下 |
| 按文件名+行号 | break foo.c:42 |
在foo.c的第42行停下 |
| 条件断点 | break 42 if i == 5 |
当i等于5时才在第42行停下 |
条件断点这玩意儿特别实用。我曾经调试一个循环一万次的代码,每次都在同一个位置断住,手动按continue按到手抽筋。后来学会了条件断点,直接写b 42 if i == 9999,一步到位。
注意:断点设多了会影响程序运行速度。尤其是条件断点,每次循环都要判断条件,开销不小。我建议只设必要的断点,用完就删。
2.3 单步执行:next 与 step
这两个命令经常被搞混。我简单说一下区别:
- next(n):单步执行,遇到函数调用会「跳过」,不进入函数内部。
- step(s):单步执行,遇到函数调用会「进入」函数内部。
举个例子:
// 示例代码
void helper() {
printf("hello\n");
}
int main() {
int a = 10;
helper(); // 假设断点停在这一行
a = 20;
return 0;
}
如果断点停在helper();这一行:
- 敲
next,直接跳到a = 20;那一行,helper函数执行完了你看不到内部细节。 - 敲
step,会跳进helper函数内部,停在printf("hello\n");这一行。
嗯,这里要注意:如果你不小心step进了某个你不关心的函数,可以用finish命令直接跑完当前函数,回到调用处。
2.4 继续执行:continue
continue,简写c。它的意思是「继续跑,直到遇到下一个断点或程序结束」。
这个命令看起来简单,但有个坑:如果你设了断点在循环里,每次continue都会停在同一个断点位置。这时候配合条件断点或者ignore命令会更好用。
实用场景:
(gdb) break 42
(gdb) ignore 1 5 # 忽略断点1的前5次命中
(gdb) continue # 第6次命中时才会停下
我曾经调试一个网络服务的内存泄漏问题,断点设在分配内存的地方,每次continue都停在同一行。用ignore跳过前几百次分配,直接观察最后一次分配后的状态,效率高了很多。
2.5 查看变量:print
print,简写p。调试的核心就是看数据,print就是你的眼睛。
(gdb) print a # 打印变量a的值
(gdb) p &a # 打印变量a的地址
(gdb) p *ptr # 打印指针指向的值
(gdb) p array[5] # 打印数组第6个元素
(gdb) p/x 42 # 以十六进制打印
(gdb) p/t 42 # 以二进制打印
(gdb) p/d 0x2A # 以十进制打印
格式控制符很实用。我调试底层驱动时,经常用p/x看寄存器值,用p/t看位域状态。你想想看,一个32位的状态寄存器,用十进制看根本看不出哪一位是1,用二进制一目了然。
小技巧:print还可以调用函数!比如p strlen(str),GDB会真的去执行这个函数。但要注意,这可能会改变程序状态,慎用。
2.6 退出调试:quit
quit,简写q。这个没什么好说的,退出GDB。如果程序还在运行,GDB会问你是否确认退出。
我个人的习惯是:调试结束后,先用info breakpoints看一眼还有哪些断点没删,然后quit。有时候第二天回来继续调,发现断点还在,省得重新设。
知识体系总览
下面这张图把这七个命令的关系梳理了一下,方便你记忆:
从这张图可以看出,调试的基本流程是:先用run启动程序,用break设好断点,然后用next/step单步跟踪,用continue跳过不关心的部分,用print查看变量值,最后用quit退出。这七个命令串起来,就是一个完整的调试循环。
好了,这一章的内容就这些。记住,调试不是背命令,而是理解程序的执行流。这七个命令你练熟了,后面多线程调试的那些高级技巧才能派上用场。