3. 断点与单步执行:硬件断点、软件断点、条件断点、单步调试技巧
断点这东西,说白了就是让CPU在指定位置停下来。我刚开始做驱动时,总觉得加断点麻烦,喜欢靠printk打日志。后来被一个时序问题折磨了三天,才发现断点才是真正的手术刀。
这一节我们聊聊断点的几种形态,以及怎么用它们快速定位Bug。
3.1 硬件断点 vs 软件断点
先搞清楚一个基本问题:断点是怎么让CPU停下来的?
| 类型 | 原理 | 数量限制 | 适用场景 |
|---|---|---|---|
| 硬件断点 | 利用CPU调试寄存器,比较地址总线 | 通常4个(ARM Cortex-M/A) | ROM/Flash中执行代码、中断向量表 |
| 软件断点 | 将指令替换为BKPT/INT3,触发异常 | 无限制(受内存空间约束) | RAM中执行代码、普通函数 |
硬件断点不修改内存,所以能在Flash或ROM里用。软件断点会改写指令,只能放在可写的内存区域。
注意: 我曾经在调试一个Bootloader时,把软件断点下在了Flash区域,结果CPU跑飞了。因为Flash写操作需要擦除整块,断点工具根本写不进去。后来换成硬件断点,问题立刻解决。
3.2 条件断点:精准打击
普通断点一停就停,但很多时候我们只关心特定条件。比如一个中断处理函数被调用了1000次,只有第500次时出了问题。你总不能按500次继续吧?
条件断点就是干这个的。它会在断点命中后,检查一个表达式,只有表达式为真时才真正停下来。
// 在GDB中设置条件断点
(gdb) break driver_irq_handler if irq_count == 500
// 或者在IDE中设置
// 条件:buffer->len > 1024 && buffer->flags & 0x02
我个人习惯在调试DMA传输时,用条件断点监控传输长度。比如:
(gdb) break dma_complete_callback if transfer_size > expected_max
这样一旦DMA传了异常数据,CPU立刻停下,我就能看到现场。
技巧: 条件断点会拖慢执行速度,因为每次命中都要计算表达式。如果条件太复杂(比如调用了函数),可能让系统跑得比乌龟还慢。我一般只用简单的比较和位运算。
3.3 单步调试:逐行看CPU在干嘛
单步执行分两种:
- Step Into:进入函数内部,一行一行执行
- Step Over:把函数调用当成一步,不进去
- Step Out:执行完当前函数,返回到调用处
你想想看,单步调试就像拿着放大镜看代码。但有个坑:在中断上下文里单步,很容易触发看门狗复位。因为单步时CPU执行得很慢,看门狗以为系统卡死了。
我的经验: 调试中断处理函数时,先关掉看门狗,或者把看门狗超时设得很长。否则你刚单步两步,系统就复位了,白忙活。
3.4 断点与单步的实战组合
光说理论没意思,我们看一个实际场景。假设你在调试一个UART驱动,数据发送到一半卡住了。你会怎么做?
- 下硬件断点在UART发送函数的入口
- 触发后,检查TX FIFO状态寄存器
- 如果FIFO满,说明硬件没把数据发出去
- 单步执行,看每一步寄存器变化
- 发现某个配置位没置对,导致硬件一直忙
嗯,这个流程我用了无数次。断点负责定位入口,单步负责分析路径。
3.5 断点使用避坑指南
我曾经踩过的坑:
- 在中断服务函数里下软件断点,结果中断嵌套把自己搞死了
- 在DMA回调里下条件断点,条件表达式调用了printf,导致堆栈溢出
- 单步调试时没关看门狗,系统反复复位,最后发现是看门狗在捣乱
这些坑其实都有规律:断点本身也会影响系统行为。尤其是实时性要求高的场景,断点一停,时序全乱。所以我的原则是:
- 先确认断点不会破坏时序
- 能用硬件断点就不用软件断点
- 条件断点的表达式越简单越好
- 单步调试前,先关看门狗和中断(如果允许)
3.6 知识结构图
下面这张图把断点和单步的核心关系画清楚了:
这张图把断点的三种类型和单步调试的关系串起来了。硬件断点、软件断点、条件断点各有适用场景,单步调试是它们的延伸工具。记住一个原则:断点负责定位,单步负责分析。