3. 断点进阶

断点这东西,说白了就是让程序在指定位置停下来。但实际调试中,你不可能每次都手动敲 b main 然后一步步走。真正的高手,会用条件断点、临时断点、硬件断点,再配合 commands 命令列表,让调试效率翻倍。

我个人习惯是:能用条件断点解决的,绝不用手动跳过。能用临时断点的,绝不留垃圾断点。嗯,咱们一个一个来看。

条件断点:只在满足条件时停下

条件断点,就是给断点加个「门禁」。只有条件为真,程序才停。否则直接忽略。

我在项目中遇到过一个大坑:一个循环跑了 10 万次,第 99999 次才出 bug。如果不用条件断点,你得手动按 99999 次 continue…… 想想就崩溃。

语法很简单:

(gdb) break main.c:42 if i == 99999
(gdb) b func if x > 0 && y < 100

你也可以先设普通断点,再追加条件:

(gdb) break main.c:42
(gdb) condition 1 i == 99999

这里的 1 是断点编号。用 info break 可以查看所有断点及其编号。

小技巧: 条件表达式里可以用函数调用,但小心副作用。比如 if foo() == 0,每次断点命中都会调用 foo(),如果 foo() 有全局变量修改,调试结果可能不准。

临时断点:用完即焚

临时断点,就是一次性的。命中一次后自动删除。非常适合「我就想看一眼这里,看完就走」的场景。

语法:

(gdb) tbreak main.c:42
(gdb) tb func

我曾经在调试一个中断服务函数时,只想确认它是否被触发一次。用 tbreak 设好,命中后自动消失,省得手动清理。你想想看,如果忘了删普通断点,下次调试时突然停住,那得多懵。

临时断点也支持条件:

(gdb) tbreak main.c:42 if count == 5

命中一次,条件满足,断点自动消失。干净利落。

硬件断点:对付 Flash 和只读内存

普通断点是通过修改内存指令实现的(把指令替换成 trap)。但有些区域是只读的,比如 Flash、ROM、或者被 MPU 保护的内存。这时候就得用硬件断点。

硬件断点依赖 CPU 的调试寄存器,数量有限(通常 4-8 个)。但好处是:不修改内存,不影响代码执行。

用法:

(gdb) hbreak main.c:42
(gdb) hbreak func if flag == 1
注意: 硬件断点数量有限。我曾经在调试一个多核系统时,每个核用了 2 个硬件断点,结果第 5 个死活设不上。后来才发现是硬件资源耗尽了。用 info break 可以查看哪些是硬件断点(类型显示为 "hw breakpoint")。

什么时候必须用硬件断点?

  • 代码在 Flash 或 ROM 中运行
  • 调试 Bootloader 或早期初始化代码
  • 代码段被 MPU/MMU 设置为只读
  • 需要调试自修改代码(self-modifying code)

断点命令列表(commands):让断点自动干活

这是我最喜欢的功能。没有之一。你可以给断点绑定一组命令,命中后自动执行。比如:自动打印变量、自动修改变量、自动继续运行。

语法:

(gdb) break main.c:42
(gdb) commands
> print x
> print y
> continue
> end

这样每次命中断点,GDB 自动打印 x 和 y,然后继续运行。你完全不用手动操作。

我在项目中用过最爽的场景:调试一个内存泄漏,我在 malloc 和 free 上各设一个断点,用 commands 记录调用栈和地址,然后自动继续。跑完一轮后,用脚本分析日志,直接定位到未匹配的 malloc。

commands 里可以写什么?

  • 任何 GDB 命令:print、set、continue、step 等
  • 条件判断:用 if 控制执行路径
  • 调用外部脚本:python print("hello")
  • 静默模式:silent 可以抑制断点命中的默认输出

举个例子,我想在循环中只打印第 5 次到第 10 次的值:

(gdb) break loop.c:10
(gdb) commands
> silent
> if i >= 5 && i <= 10
>   print i
>   print array[i]
> end
> continue
> end

silent 让输出更干净,只显示你关心的内容。

核心要点: commands 是断点自动化的灵魂。配合条件断点,你可以实现「只在特定条件下打印特定变量,然后自动继续」。调试效率直接翻倍。

知识体系图

下面这张图总结了断点进阶的四个核心概念及其关系:

断点进阶 条件断点 break ... if 条件 临时断点 tbreak / tb 硬件断点 hbreak (用于Flash/ROM) commands 自动执行命令列表 使用场景总结 条件断点 → 循环/特定值 | 临时断点 → 一次性检查 | 硬件断点 → 只读内存 | commands → 自动化调试

避坑指南

我曾经在调试一个实时系统时,用了条件断点,结果每次命中都要评估表达式,导致系统时序被打乱,中断响应超时。后来改用硬件断点 + commands 的 silent 模式,才解决问题。

还有一次,我在 commands 里写了 continue,但忘了加 silent,结果每次命中都打印一堆信息,终端刷屏刷了 10 分钟…… 嗯,从那以后我养成了习惯:只要 commands 里有 continue,第一行一定是 silent。

我的建议: 调试前先规划好策略。如果只是看一眼,用 tbreak。如果要在循环中找特定值,用条件断点。如果代码在 Flash 里,用 hbreak。如果需要自动化,用 commands。别一股脑全用普通断点,那是新手干的事。

最后说一句:条件断点和 commands 的组合,是 GDB 调试的「核武器」。你花 10 分钟写好 commands,可能省下 2 小时的手动操作。这笔账,怎么算都划算。