正文
大家好,我是bug菌~
每个嵌入式软件开发工程师的调试方法都各不相同,不过有两个频率非常高的调试手法,可以说是老少皆宜,那就是打日志和打断点的,打日志在嵌入式linux中使用得频率比较高,对于大一点的系统,难以立马复现的系统,是排查问题的重要手段。
而打断点在单片机开发中简直不要太香,那大家是否都有详细了解过为啥打断点就能够让系统在指定的位置停止执行,调试器又是以怎样的一个流程去完成整个过程等等,那么今天bug菌还是以经典的STM32单片机为例带大家再深入的了解单片机中的断点技术。
1
硬件断点
STM32 单片机的硬件断点机制基于 ARM Cortex-M 系列处理器的 CoreSight 调试架构。
该架构包含多个关键调试组件,其中断点单元(Breakpoint Unit,BPU)和数据监视点触发单元(Data Watchpoint Trigger,DWT)是实现硬件断点的核心模块。
BPU 单元包含多个比较器,用于在非易失性存储器(如 Flash)中设置断点。当代码从 Flash 中执行时,BPU 监视指令取指地址,当检测到匹配的断点地址时,会返回一个断点指令,使处理器进入调试模式。不同 Cortex-M 系列的硬件断点数量有所不同。
Cortex-M3/M4 提供 6 个硬件断点比较器,Cortex-M7 提供 8 个硬件断点比较器,所以硬件断点就是数量有限。
硬件断点的工作原理是通过硬件电路直接比较指令地址。调试器将目标断点地址写入 BPU 的比较寄存器中,当 CPU 执行取指操作时,硬件自动将当前程序计数器(PC)的值与所有断点地址进行比较。如果匹配,硬件会立即触发调试异常,使 CPU 停止执行并将控制权交给调试器。这种机制的优势在于不修改原始代码,因此适用于只读存储器(如 Flash)的调试,并且具有零开销的特点。
DWT 单元提供了更复杂的数据监视功能,它不仅可以监视指令地址,还能监视数据地址和数据值的变化。DWT 包含 4 个比较器,可以配置为监视数据读、写或读写操作,当满足预设条件时触发断点或生成跟踪数据。这种灵活性使得 DWT 特别适合用于检测指针错误、数组越界访问等内存相关问题,这个正是一个神器,在以往的工作过程中为bug菌解决挺多疑难杂症的。
硬件断点的配置涉及多个特殊功能寄存器。开发者需要通过调试访问端口(DAP)向这些寄存器写入配置信息。例如,在设置硬件断点时,需要配置 FP_CTRL 寄存器确认可用断点槽数量,然后遍历空闲的 FP_COMPx 寄存器,将目标地址写入并设置 ENABLE 位。这些寄存器位于处理器的私有外设总线(PPB)上,可通过 SWD 或 JTAG 接口进行访问,当然这些你可以不感兴趣的话可以不用深究,这些IDE与调试器都约定好了协议,对工程师来说是透明的。
2
软件断点
软件断点的实现原理与硬件断点截然不同,它通过修改目标代码来插入断点指令。在 ARM Cortex-M 架构中,断点指令通常是 BKPT(Breakpoint)指令,其机器码为 0xBE00(16 位指令),所以不需要硬件支持,你当然可以想设置多少断点就设置多少断点了~
软件断点大致是这样一个过程或者步骤,首先呢,调试器读取目标地址的原始指令并保存;然后,调试器通过 SWD 或 JTAG 接口将断点指令 BKPT 写入该地址;当 CPU 执行到断点指令时,会触发调试异常,使处理器停止运行并将控制权交给调试器;最后,调试器恢复原始指令并继续执行程序,so,easy吧~
这种机制的工作原理是利用了 ARM 架构的异常处理机制。BKPT 指令是一种特殊的 Thumb-2 指令,当处理器执行到 BKPT 指令时,会产生一个调试异常(Debug Monitor 异常),进入调试模式。在调试模式下,调试器可以访问所有寄存器、内存和外设,并可以修改程序状态。调试器通过这种方式实现了对程序执行流程的精确控制。
但是软件断点也存在一些局限性,比如它需要插入异常指令,那就它就需要修改原始代码,因此在只读存储器(如 Flash)中无法直接使用;其次,频繁的代码修改可能影响程序的时序特性;最后,在某些优化级别下,编译器可能会优化掉断点指令附近的代码,导致断点失效。
3
jlink调试
JLink 调试器与 STM32 单片机之间的通信基于 ARM 标准的调试接口协议,主要包括 JTAG 和 SWD两种通信方式。SWJ-DP(Serial Wire/JTAG Debug Port)是 ARM 定义的标准 CoreSight 调试端口,它将 JTAG 接口和 SWD 接口集成在一起,支持两种协议的自动检测。
在断点机制中,JLink 调试器通过这些通信接口与 STM32 的调试单元进行交互。
当设置硬件断点时,JLink 通过 SWD 协议向 BPU 寄存器写入断点地址和控制信息。
当设置软件断点时,JLink 通过接口修改目标代码中的指令。调试器与目标芯片之间的数据传输采用特定的协议格式,包括地址、数据和控制信息,确保断点设置的准确性和可靠性。
JLink 调试器内部集成了 ARM 调试协议的完整实现,包括调试端口访问(DP 访问)和访问端口(AP 访问)。DP 负责与目标芯片的调试硬件通信,AP 则提供对系统内存和外设的访问通道。通过这种分层架构,JLink 可以高效地执行各种调试操作,包括断点设置、寄存器读写、内存访问等等之类的~
最后
好了,今天就跟大家分享这么多了,如果你觉得有所收获,一定记得点个赞~
唯一、永久、免费分享嵌入式技术知识平台~
推荐专辑 点击蓝色字体即可跳转
☞ MCU进阶专辑
☞ 嵌入式C语言进阶专辑
☞ “bug说”专辑
☞ 专辑|Linux应用程序编程大全
☞ 专辑|学点网络知识
☞ 专辑|手撕C语言
☞ 专辑|手撕C++语言
☞ 专辑|经验分享
☞ 专辑|电能控制技术
☞ 专辑 | 从单片机到Linux