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驱动,数据发送到一半卡住了。你会怎么做?

  1. 下硬件断点在UART发送函数的入口
  2. 触发后,检查TX FIFO状态寄存器
  3. 如果FIFO满,说明硬件没把数据发出去
  4. 单步执行,看每一步寄存器变化
  5. 发现某个配置位没置对,导致硬件一直忙

嗯,这个流程我用了无数次。断点负责定位入口,单步负责分析路径。

3.5 断点使用避坑指南

我曾经踩过的坑:
  • 在中断服务函数里下软件断点,结果中断嵌套把自己搞死了
  • 在DMA回调里下条件断点,条件表达式调用了printf,导致堆栈溢出
  • 单步调试时没关看门狗,系统反复复位,最后发现是看门狗在捣乱

这些坑其实都有规律:断点本身也会影响系统行为。尤其是实时性要求高的场景,断点一停,时序全乱。所以我的原则是:

  • 先确认断点不会破坏时序
  • 能用硬件断点就不用软件断点
  • 条件断点的表达式越简单越好
  • 单步调试前,先关看门狗和中断(如果允许)

3.6 知识结构图

下面这张图把断点和单步的核心关系画清楚了:

断点与单步执行知识结构 断点与单步执行 硬件断点 软件断点 条件断点 数量有限(通常4个) 不修改内存,支持Flash 数量无限制 修改指令,仅限RAM 表达式为真才停下 表达式尽量简单 单步调试:Step Into / Over / Out 注意:关看门狗,防中断嵌套

这张图把断点的三种类型和单步调试的关系串起来了。硬件断点、软件断点、条件断点各有适用场景,单步调试是它们的延伸工具。记住一个原则:断点负责定位,单步负责分析。