4. GDB断点实战:设置断点、删除断点、启用/禁用断点、条件断点、临时断点
断点是调试的灵魂。说实话,我见过太多人只会用 b main 然后一路 n 到底。这其实浪费了 GDB 最强大的能力。今天我们就来彻底搞懂断点的各种玩法。
4.1 设置断点:从入门到精通
设置断点最基础的方式就是 break 命令,简写为 b。我个人习惯用简写,因为调试时敲命令越快越好。
(gdb) b main # 在 main 函数入口
(gdb) b 42 # 在当前文件的第42行
(gdb) b foo.c:100 # 在 foo.c 的第100行
(gdb) b *0x400520 # 在内存地址 0x400520 处(汇编级)
这里有个小细节:如果你有多个同名函数(比如重载),GDB 会列出所有匹配项让你选。我记得有一次调试 C++ 项目,b foo 弹出了 5 个候选,当时我就懵了。后来学乖了,直接用 b 'foo(int, char*)' 加引号指定完整签名。
4.2 删除断点:清理战场
断点设多了,调试窗口会变得很乱。这时候就需要清理。用 delete 命令,简写 d。
(gdb) info breakpoints # 查看所有断点,注意编号
(gdb) delete 2 # 删除编号为2的断点
(gdb) delete # 删除所有断点(会确认)
(gdb) clear main # 删除 main 函数处的断点
(gdb) clear 42 # 删除第42行的断点
我曾经犯过一个低级错误:调试到一半,手快敲了个 d 没加编号,结果所有断点全没了。嗯,那感觉就像写了一天代码没保存。所以我现在有个习惯——重要断点先用 save breakpoints 保存到文件。
(gdb) save breakpoints my_bp.txt
(gdb) source my_bp.txt # 下次调试时直接加载
4.3 启用/禁用断点:临时开关
有时候你不想删除断点,只是暂时不想让它触发。这时候 enable 和 disable 就派上用场了。
(gdb) disable 2 # 禁用编号2的断点
(gdb) enable 2 # 重新启用
(gdb) disable # 禁用所有
(gdb) enable once 2 # 只触发一次,然后自动禁用
disable 这个断点,等循环跑过关键次数后再 enable。或者直接用条件断点——下面会讲。
4.4 条件断点:精准打击
条件断点是我最常用的功能之一。说白了,就是让断点只在特定条件下触发。语法很简单:break [位置] if [条件]。
(gdb) b 42 if i == 100 # 当 i 等于100时在第42行停下
(gdb) b foo if ptr == NULL # 当指针为空时在 foo 函数停下
(gdb) b main if argc > 2 # 当参数个数大于2时在 main 停下
你想想看,如果没有条件断点,你要手动 c 100 次才能到第100次循环。有了条件断点,一句搞定。
条件断点还有一个进阶用法:用 $_ 变量。比如 b foo if $_caller_is("main"),只在 main 函数调用 foo 时触发。这个我在分析调用链时经常用。
4.5 临时断点:用完即弃
临时断点就是「一次性断点」。触发一次后自动删除。命令是 tbreak。
(gdb) tbreak 42 # 在第42行设临时断点
(gdb) tbreak main # 在 main 设临时断点
什么时候用?举个例子:你正在调试一个深层调用,想先跳到某个函数,但不想在那永久停住。设个 tbreak,触发一次后自动消失,干净利落。
我个人习惯在 r(运行)之前设 tbreak main,这样程序启动后停在 main,然后我再用 b 设其他断点。省得每次手动删。
4.6 断点管理:info 和 批量操作
断点多了怎么管理?info breakpoints 是你的好帮手。它会列出所有断点的编号、类型、位置、状态、触发次数等信息。
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400520 in main at test.c:10
breakpoint already hit 1 time
2 breakpoint keep y 0x0000000000400540 in foo at test.c:42
stop only if i == 100
3 hw watchpoint keep y local_var
breakpoint already hit 5 times
看到那个 Disp 列了吗?keep 表示永久断点,del 表示临时断点(触发后删除),dis 表示禁用。一目了然。
d 1-5 删除编号1到5的断点,用 enable 2 4 6 启用多个断点。别一个个敲,浪费时间。
4.7 知识体系图
下面这张图总结了断点的核心操作和关系。我建议你把它记在脑子里,调试时就能信手拈来。
4.8 实战避坑指南
最后分享几个我踩过的坑:
- 断点位置偏移:代码修改后重新编译,断点可能跑到错误行。我习惯在修改代码后
d掉所有断点重新设。 - 优化编译器的坑:用
-O2编译时,断点可能不会停在预期位置。因为编译器重排了指令。这时候要么用-O0 -g,要么用汇编级断点b *addr。 - 条件断点性能:在热循环里设条件断点,程序会慢得像蜗牛。我曾经在 10 万次循环里设条件断点,等了 30 秒才触发。后来改用
ignore命令:b 42; ignore 1 99999,跳过前 99999 次,第 100000 次才停。 - 多线程断点:默认所有线程都会触发断点。如果只想让特定线程停,用
thread apply [id] break。这个后面章节会细讲。