CN / EN
CN / EN

写技术文章

技术分享 | HardFault问题分析

Ping

汇顶员工
2024-02-27 18:04:16

Cortex-M3/4的Fault异常是由于非法的存储器访问(比如访问0地址、写只读存储位置等)和非法的程序行为(比如除以0等)等造成的。常见的4种异常及产生异常的情况如下:

  1. Bus Fault:在fetch指令、数据读写、fetch中断向量或中断时存储恢复寄存器栈情况下,检测到内存访问错误则产生Bus Fault。
  2. Memory Management Fault:访问了内存管理单元(MPU)定义的不合法的内存区域,比如向只读区域写入数据。
  3. Usage Fault:检测到未定义指令或在存取内存时有未对齐。
  4. Hard Fault:上面三种异常发生任何一种异常都会引起Hard Fault,在上面的三种异常未使能的情况下,默认发生异常时进入Hard Fault中断服务程序。

在默认复位初始化时Hard Fault使能,其它三者不使能,因此当程序中出现不合法内存访问(一般是指针错误引起)或非法的程序行为(一般就是数学里面常见的除0)时都将产生Hard Fault中断。

一、Hard Fault问题原因

  1. 内存溢出或者访问越界,通常为数组或结构体访问越界;
  2. 堆栈溢出,可能是由于堆栈设置过小或程序执行时堆栈使用不当引起的;
  3. 数据类型错误,当程序试图访问或操作的数据类型不正确时,可能会导致错误并触发硬件中断;
  4. 堆栈设置错误,错误的堆栈配置可能导致程序跳转到`HardFault_Handler`;
  5. 时钟配置问题,如果程序可能会在不确定位置的地方产生`HardFault_Handler`,检查外部晶振频率、系统初始化相关函数以及晶振频率宏定义,确保时钟设置正确 & 稳定;
  6. 参数类型不匹配:使用与定义类型不同的参数可能导致程序跳转到`HardFault_Handler`,且这种情况下的问题通常是不固定的;
  7. 片上Flash存储问题,如果使用片上Flash存储参数,需要注意参数存储区不要与代码区互相覆盖,否则可能会导致`HardFault_Handler`;

二、Hardfault问题分析方法

2.1 J-LINK调试方法

当系统死机或者出现异常时,首先需要判断是否为 HardFault,然后再判断是哪种 Fault,Goodix BLE芯片默认支持 SWD 协议,因此只要支持 SWD 的仿真器理论上都可以对其进行调试,硬件连接示意图如下。

1. J-Link获取现场信息

J-Link 硬件连接完成后,打开 J-Link Commander,然后键入“connect”, 根据芯片类型进行后续选择,最后成功连接,键入“h”,即可得下图所示信息。

可以看到 IPSR=3, 同时参看下图信息,可知当前进入 Hard Fault 异常处理模式。

2. 线索分析

A. 获取现场信息后,根据LR 寄存器中读到的值,确认当前使用 MSP 还是 PSP,上图示例R14(LR) = 0xFFFFFFE9,根据下图可知,使用的是 MSP。

B. 明确stack 为 MSP 后,读取MSP = 0x0083FF98 地址处最近的 8 个 word(32 Byte)。Cortex-M4在中断发生时的压栈顺序为:xPSR, PC, LR, R12, R3, R2, R1, R0,则读取到的8个word的含义是反过来的,其中第6个word为异常前的 LR,第7个word为异常前的 PC,通过这两个值就可以反推出异常发生前的位置。

按照如下顺序,即可得知: LR=0x01003FEB, PC=0xFFFFFFFE

3. 原因定位分析

通过上述步骤已获取到有效的现场信息,也分析到真实异常的 LR 以及 PC 值,下一步需要结合反汇编文件进行定位,找到大概的出错代码位置。显然地PC=0xFFFFFFFE是一个非法地址,此时 ARM 陷入了访问非法地址的异常,因此只能先看 LR指令,在反汇编文件中搜索 0x01003FE*,如下图所示:

可知具体指令为红框处,因为出问题时指令是 thumb 状态,因此最后一个 bit 是 1,此处要-1。既然是 LR 指令就需要看上一条指令。可以看到具体是BLX r0 这一条命令执行后出的问题,再观察此时 R0 为 0xFFFFFFFF, 然后 PC 跳到一个非法地址,故导致 HardFault。

2.2 Fault Trace模块定位

Fault Trace模块是GR5xx SDK提供用于辅助开发调试阶段定位系统异常问题的模块,该模块支持以下实用功能:

  1. 将异常信息存入NVDS中,方便异常记录与后续分析。
  2. 支持多种方式读取保存的异常信息:使用GRToolBox Ap通过BLE读取,或者GProgrammer工具通过J-Link/串口读取。
  3. 可选的cortex-backtrace组件,该组件可在异常发生时记录增加调用栈与HFSR寄存器所指示的异常原因内容。

如果使用了看门狗,则很有可能因为看门狗复位导致第一现场丢失,此时就需要借助Fault Trace模块的异常记录功能来判断。在出现Hard Fault后,Fault Trace模块会自动将异常信息存入NVDS中。 Fault Trace模块的具体使用方法请参考 GR5xx Fault Trace Module应用说明GR551x Fault Trace Module流程

1. Fault Trace模块使用注意事项

(1) Fault Trace模块依赖APP LOG模块,Assert模块与Error模块,使用时需要确保下面的文件加入到 了工程中,并将它们的所在目录加入到Include Path中:
components\libraries\app_assert\app_assert.c
components\libraries\app_error\app_error.c
components\libraries\app_log\app_log.c
(2) Fault Trace模块不止支持记录HardFault异常信息,也支持记录使用宏 APP_ERROR_CHECK , APP_BOOL_CHECK 与 APP_ASSERT_CHECK 产生的错误信息。
(3) Fault Trace模块默认支持16条错误信息存储,该上限可通过 components\libraries\fault_trace\fault_trace.h 中的宏 FAULT_INFO_RECORDS_MAX 进行 调整,超出上限条数的错误信息会循环覆盖最老的错误信息。
(4) 如果要使用Cortex Backtrace的扩展功能,在 custom_config.h 中将宏 ENABLE_BACKTRACE_FEA 的值置为 1 。

2. Hardfault信息分析

在使用了Fault Trace模块的cortex-backtrace组件的情况下,HardFault时会 自动反推调用栈信息。下面是使用了cortex-backtrace组件后HardFault时的信息输出:

Fault on interrupt or bare metal(no OS) environment
==== Main stack information ====
addr: 2007fe28 data: 00000000
addr: 2007fe2c data: 00207379
addr: 2007fe30 data: 00000301
......
addr: 2007fffc data: 0020b8c5
=========
==== Registers information =====
R0 : 00123456 R1 : deadbeef
R2 : 00000000 R3 : 00000000
R12: 00000000 LR : 00077ded
PC : 00203ca8 PSR: 61000011
=========
Fault reason:
Bus fault: imprecise data access violation
Call stack info : 00203ca8<--00077de9<--00207375<--000002fd<--00207479<-
-00000003<--002073b1<--00000dfd<--002098fd<--0020988d<--00000dfd<--00040edf<-
-0004020f<--0020b8c1<--0020b8c1<--

cortex-backtrace主要打印了4个信息:栈数据,关键寄存器,异常类型和调用栈。其中栈数据可以结合汇编进行场景重现,对于某些由数据错误引起的HardFault调试有帮助(例如除0,空指针等等),关键寄存器包括:
A. 用于传参,存储返回值和临时数据的 R0-R3 寄存器
B. 用于过程间临时传参的 R12 (IP) 寄存器
C. 用于存储返回地址的 LR 寄存器
D. 用于存储当前运行地址的 PC 寄存器
E. 用于存储运算标志位,中断号及运行状态的 PSR 寄存器
通过 PC 寄存器可以知道异常具体发生在哪一个位置,可以通过汇编文件( .s 文件或是 .asm 文件)去查对应地址的汇编指令,也可以使用GCC工具链提供的 addr2line 工具来获取地址对应到哪个源文件中的哪一行。有时候可以看到 PC 的值是一个奇数,这是Cortex-M4核心的Thumb模式导致的,此时把地址值-1即是正确的地址。

异常类型是对Cortex-M4核心的 CFSR (Congifurable Fault Status Register) 寄存器的解析。该寄存器是Cortex-M4核心用于指示HardFault产生原因的寄存器。HardFault又被细分为3种类型:Usage Fault, Bus Fault, Memory Management Fault。具体不同类型以及指示内容的具体含义请参考Cortex-M4 Devices Generic User Guide - 4.3.10 Configurable Fault Status Register。

调用栈是Cortex Backtrace模块通过对寄存器,运行程序以及栈数据的分析和反推得到的调用关系链,和 PC 寄存器的值一样,结合汇编文件或者 addr2line 工具可以对整个调用链条进行定位。

三、案例分析

3.1. 非法地址访问异常案例

1. J-link读取信息如下,错误信息为0xFFFFFFFD—Retuen to Thread mode and use the Process Stack for return,使用PSP。

2. 读取PSP地址附近10 Byte内容,LR=0x0100C385,PC=0x12345678。

3. MAP文件定位0x0100C384位置。

4. 0x12345678地址超过了内存范围。

四、常见问题

Q:反汇编定位不准确

A:如果通过上述方法定位到的函数,发现程序不可能调用到,那么可能是上述方法的过程中有误,比如当前主栈用的是 PSP,但是误认为 MSP,也有可能是将 LR 和 PC 值相反了。

Q:MSP/PSP 不正常

A:如果发现 MSP/PSP 不正常时,则不可再继续用上述方法,一般这种错误是 ARM 上电异常导致的,重点怀疑的还是时钟、晶振、 PLL 等相关参数异常导致。

Q:得到的PC指向了一个莫名其妙的地方是怎么回事?

A: 有以下几种可能:

  1. 可能是调用了非法地址,此时比起 PC 更需要关心 LR 的值,即在跳转之前的位置,例如函数指针为空或为非法值时的跳转;
  2. 可能MSP和PSP搞反了,这样得到反推得到的PC和LR就是错误的值;
  3. Flash 有问题,取值异常导致的,此时重点排查 Flash/Cache 相关的参数;
  4. 对于GR5xx SoC有些代码被固化到ROM中,有些情况下(例如非法参数)可能会导致ROM中的代码产生Hard Fault,此时PC值在 0x00000000 到 0x000FFFFF 之间(不同型号的SoC的ROM地址范围不同,具体的范围请参考对应型号的Datasheet);

Q: 读取不了栈上的数据怎么办?
HARDFAULT CALLSTACK INFO: R0-00123456 R1-DEADBEEF R2-00000000 R3-00000000 R12-
00000000 LR-00077DED PC-00203C00 XPSR-61000011

A: 首先请确保芯片已经被Halt住,即在J-Link连上后需要先输入 h 让芯片停止运行;其次检查所读取的栈指针和所要读取的长度是否在正确的RAM范围内;最后如果上述步骤都不能解决问题,则有可能是芯片存在硬件异常,请检查供电及各个关键电源信号是否符合要求。


参考资料
1.
如何监控内存越界/踩踏的问题--基于GR5515移植DWT_Point

2. CmBacktrace | 基于GR5515移植hardfault追踪库

3. Cortex-M4 Devices Generic User Guide

0收藏

0赞成

您的评论
我们时刻倾听您的声音
联系销售

扫描关注公众号

打开微信,使用“扫一扫”即可关注