一、概述
相信大家都遇到过 HardFault 的问题,这个问题最锻炼人心态的地方是不知道具体是程序哪里的问题导致的,如果不能确定问题产生的地方,就只能靠不停的推测、验证、再推测、再验证…… 这样无疑会大大拉低效率,那么有没有什么办法能定位到程序的具体语句呢?当然有,各 IDE 和烧录器们可不是吃素的。
二、HardFault 问题定位
2.1 HardFault 简介
由于一些软件或外部原因引起处理器上运行的程序执行失败会产生错误,如供电不稳、非法外部输入、任务堆栈溢出、非法存储器访问等。所有的错误默认都会触发 HardFault 异常。错误原因大致可分为四类,如下:
- 存储器管理错误 Memory management (MemManage) faults
- 总线错误 Bus faults
- 使用错误 Usage faults
- HardFaults
2.2 HardFault 发生时 MCU 做了什么?
当异常产生且被处理器接受时,压栈流程会将寄存器压入栈中,压栈操作中的栈可以为主栈(使用主栈指针,MSP)或者进程栈(使用进程栈,MSP)。若处理器运行在线程模式且使用 MSP (CONTROL 寄存器的第 1 位为 0,默认配置),则压栈操作在执行时使用主栈 MSP,如下图所示。
图 1. 使用主栈的异常栈帧
若处理器运行在线程模式且使用 进程栈(CONTROL 寄存器的第 1 位为 1),则压栈操作在执行时使用进程栈 PSP,如下图所示。
图 2. 使用进程栈的异常栈帧
对于未使用 OS 的多数应用,只会用到主栈指针MSP,若应用使用了 PSP,则需检查连接寄存器 LR 的第二位来确定实际使用的 SP,如下图。
图 3. 栈跟踪流程定位栈帧和压栈寄存器
2.3 HardFault 错误分析
现在我们知道了 HardFault 异常发生后会把进入异常前的 R0-R3、R12、LR、PC、PSR 寄存器值栈入 MSP 或 PSP(取决于异常发生时使用的哪个栈)。进入异常后链接寄存器 LR 中存放异常返回值 EXC_RETURN,若其 bit 2 = 0 则使用 MSP,若 bit 2 = 1,则使用 PSP。
图 4. 压栈顺序
接下来我们就可以利用这些信息去定位 HardFault 产生的位置了。
(1)在 HardFault_Handler(void) 中断里第一条语句打断点,当程序停在断点时,查看 LR 寄存器的值。例如下图,LR = 0xfffffffd,说明这里使用 PSP,记下 PSP 的值为 0x2013ef60。
图 5. 查看 LR 确认使用栈
(2)根据 MSP 或 PSP 的指针查看使用堆栈的内存,可以在 memory 窗口查看,根据 R0~R3、R12、LR、PC、XPSR 的压栈顺序找到 LR 的值,即第 6 个值。例如下图,输入刚刚记下的 PSP 的值,往后数 6 个可以下查找到 LR = 0x0835ab73,这是发生异常前 PC 要执行的下一条指令的地址。
图 6. 找到 PC 值
(3)在反汇编窗口输入 刚刚的 LR 的值,可以看到 PC 要执行的下一条指令。
图 7. 查看反汇编
现在我们就定位到 HardFault 的具体位置了,是不是方便了很多?
2.4 其他方法
当然,如果在 Debug 状态下,还有更方便的办法,直接通过 Call Stack 窗口查看调用代码,双击可直接跳转到对应语句,如下图。
图 8. Debug 时可查看 Call Stack 窗口
另外,SEGGER 也提供了自己的 HardFault 调试组件,在 SEGGER 官网的 Application Notes 页面下可找到该组件的源码和文档,如下图。网址:https://www.segger.com/downloads/application-notes#HardFaultHandler
图 9. SEGGER HardFault 调试组件资料
具体使用方法在文档中有介绍,将 .c 添加到工程后,发生 HardFault 时会进入 SEGGER 的 HardFault_Handle() 函数,程序会停到 while (_Continue == 0u) ,如下图,这里记录了很多相关寄存器的信息,比如错误类型以及各种状态,将 _Continue 添加到 watch 窗口并写入任意值后,代码将继续往后跑,出 HardFault_Handle() 后的第一条语句就是 HardFault 前的下一条语句,对于分析错误原因十分方便。
图 10. SEGGER HardFault_Handle()
三、参考资料
(1)《ARM Cortex-M3与Cortex-M4权威指南》
(2)SEGGER 应用笔记 《AN00016_AnalyzingHardFaultsOnCortexM》,网址如下:
https://www.segger.com/downloads/application-notes#HardFaultHandler
(3)博客《Cortex-M 处理器 hardfault 定位方法和步骤(基于Keil mdk)》,网址如下: