记一次HardFault调试过程

  1. 1. 起因
  2. 2. 反汇编
  3. 3. 寄存器
  4. 4. 总结

最近在调试过程中遇到了HardFault的问题,在查找这个问题的过程中,没想到却引出了一连串的问题,下面就说一说这次 HardFault 的调试过程。

起因

在调用 STM32 HAL 库的HAL_CAN_AddTxMessage()函数,程序运行至上图所示行时,再执行一行,程序就产生了 HardFault,我寻思着,一句判断的语句怎么可能产生错误呢???但经过我的检查,并没有发现野指针的情况,指针均指向了预期变量。后面我决定查看汇编代码,

反汇编

首先对程序进行反汇编,执行:

1
2
# 此处因输出较多,故将输出重定向至文件
arm-none-eabi-objdump -d ./build/CAN_Slave.elf > disassemble.s

根据调试时的 PC 寄存器的值,找到对应的语句,图中为1813行,可以看出是将 R1 寄存器的值作为地址偏移 8 Bytes 后读取出 1 字(4 Bytes)的数据放至 R4 寄存器,在线调试看一下 R1 寄存器的值,为 pHeader 指针的值,偏移 8 Byte 正好是成员变量 IDE 的地址,这就很迷惑了。

image-20201006173625296

寄存器

接着我翻出了我的ARM Cortex-M3权威指南,寻找有关错误处理的内容。

Cortex-M3架构中,有一个 HardFault 状态寄存器:SCB->HSFR (0xE000ED2C),每位的定义如下表:

名称描述
31DEBUGEVT表明硬件错误由调试事件产生
30FORCED表明硬件错误由于总线错误、存储器管理错误或使用错误产生
29:2--
1VECTBL表明硬件错误由取向量失败产生
0--

在进入HardFault_Handler后,通过 GDB 查看寄存器的值,为0x40000000,即硬件错误由总线错误、存储器管理错误或使用错误产生。接下来还有另一个寄存器,可配置错误状态寄存器:SCB->CFSR (0xE000ED28),事实上,它还可以被分为三个部分,根据地址由低到高依次为:

寄存器大小功能
MemManage 错误状态寄存器(MFSR)字节MemManage 错误的状态信息
总线错误状态寄存器(BFSR)字节总线错误的状态
使用错误状态寄存器(UFSR)半字使用错误的状态

于是我们接着通过 GDB 查看该寄存器的数值,结果为0x00000400,查表发现错误属于总线错误:IMPRECISERR:不精确的数据访问冲突。书中对于这个错误的解释为:在处理器访问指令执行一段时间后才产生的错误异常。说明pHeader->IDE == CAN_ID_STD并不是导致 HardFault 的语句,真正的罪魁祸首还另有其人。于是往上看看,发现了一行*pTxMailbox = (uint32_t)1 << transmitmailbox;,再看看传进来的指针,发现是一个空指针!处理器试图访问一个非法的存储器位置,最终导致了总线错误。

还有一个问题一直困扰着我,也是我难以发现错误的原因,那就是:为什么存储指令在执行了一段时间后才会产生错误?书上的解释为:

总线错误之所以会不精确,是因为处理器总线接口上存在写缓冲。当处理器数据写到可缓冲的地址时,即使本次传输需要几个周期才能完成,处理器也会继续执行下一条指令。

写缓冲可以提高处理器的性能,但也给调试带来了一些难度,这是因为总线错误被触发时,处理器可能已经处理了包括跳转指令在内的多条指令。若跳转目标可以通过多条路径到达,在没有指令追踪(ETM)的情况下,要确定错误的存储器访问发生的位置是非常困难的。为了方便这种情况下的调试,可以使用辅助控制寄存器中的 DISDEFWBUF 位禁止写缓冲。

这就有办法解决了,我便开始尝试禁用写缓冲:

1
SCnSCB->ACTLR |= 1 << 1;

但是在这条语句结束后,寄存器的值并没有发生变化……这时我又想起了STM32F10xxx Cortex-M3 编程指南,在指南中,我找到了SCnSCB->ACTLR寄存器,但上面写着:image-20201006194513800

只适用于 STM32F2 和 STM32 L2 系列 ,淦。在 ST 的社区中也有人提出了这个问题

总结

这次折腾下来,也学到了挺多东西,大致了解了 Arm Cortex-M3 的错误处理,还知道了写缓冲这个能提升 CPU 运行效率但是会给调试过程带来麻烦的东西🙃。

本网站所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。