HardFault调试
本篇应用笔记主要描述Cortex-M系列MCU Hard Fault中断的调试方法。笔记中会分析Hard Fault中断产生的原因以及如何依据Hard Fault中断信息定位软件中的问题。
Hard Fault简介
Hard Fault 是Cortex-M系列MCU中最为常见的一种故障类中断,Hard Fault属于不可屏蔽中断,并且中断优先级固定为-1,在软件运行过程中如果产生了CPU无法正常处理的故障就会进入此中断,一般来说产生Hard Fault一般有如下几种原因:
- Bus Fault:总线异常是指CPU在进行总线读写操作过程中,总线上返回一个错误状态,比如总线地址不可访问或者有ECC错误等。总线异常可以能产生在CPU取指,数据读写,中断向量表读取,出入栈和中断进出等阶段。
- Memory Management Fault:MPU保护异常,产生原因是系统检测到违反MPU区域配置属性的区域访问操作,比如在一个只是读写的区域执行了代码。
- Usage Fault:这种异常包含CPU取到一个未定义指令,非对齐的数据访问,除零异常等等
- Hard Fault:因为上面几种错误默认是可以关闭,并且可以设定优先级的,如果相应的异常在系统中未使能,或者系统在执行更高优先级的中断中产生了上述异常,该异常就会直接上访成HardFault。另外在CM0内核中并没有上述的单独异常,所以前述的异常都会直接进入Hard Fault。
因为Bus Fault/Memory Management Fault/Usage Fault在默认状态都是关闭状态,所以系统产生上述异常都会直接产生HardFault,这也是我们实际使用中经常碰到的情形。
SCB模块
SCB(System Control Block)包含系统的配置信息,系统控制等相关信息,SCB包含多个寄存器,通过这些寄存器我们可以查看系统当前等运行状态和异常的具体信息,熟悉SCB中寄存器的含义对于异常的分析有很大帮助。
地址 | 名称 | 描述 |
---|---|---|
0xE000E008 | ACTLR | 和系统性能相关的详细配置,开启DISDEFWBUF可以禁用系统buffer,可以捕获更为精确的异常地址,一般无需更改 |
0xE000ED00 | CPUID | 只读的CPUID,ARM的版本信息 |
0xE000ED04 | ICSR | 中断控制和状态寄存器可以查看当前系统悬起了哪些中断,系统正在执行的中断号信息 |
0xE000ED08 | VTOR | 中断向量表偏移地址 |
0xE000ED0C | AIRCR | 应用中断和复位控制寄存器,写入特定值可以产生软件复位 |
0xE000ED10 | SCR | 系统控制寄存器,主要是CPU的低功耗行为控制 |
0xE000ED14 | CCR | 可以查看非对齐访问/除零异常标志,控制系统异常行为 |
0xE000ED18 | SHPR1 | 定义Usage Fault/Bus Fault和MemMange Fault异常的优先级 |
0xE000ED1C | SHPR2 | 定义SVCall异常优先级 |
0xE000ED20 | SHPR3 | 定义SysTick和PendSV异常优先级 |
0xE000ED24 | SHCRS | 可以查看产生了哪些以系统异常,控制开启哪些系统异常,对异常分析比较重要 |
0xE000ED28 | MMSR | MemManage异常状态的具体信息 |
0xE000ED29 | BFSR | BusFault异常的具体信息 |
0xE000ED2A | UFSR | UsageFault异常的具体信息 |
0xE000ED2C | HFSR | HardFault异常的具体信息 |
0xE000ED34 | MMFAR | MemManage异常地址,CM0没有 |
0xE000ED38 | BFAR | BusFault异常地址,CM0没有 |
0xE000ED3C | AFSR | 额外异常状态 |
在实际使用过程中,当系统进入Hard Fault时候,一般只有一个while1的中断,然后系统等待WDG复位。而在调试阶段,调试器一般都支持对于异常的断点分析,也就是调试器默认会在这些异常函数添加断点,这样我们就可以查看异常点所在的现场信息,并依据SCB相关寄存器定位问题。
对于Hard Fault的分析,首先要看SCB相关寄存器找到Hard Fault产生的真正原因,然后再依据堆栈信息获取产生Hard Fault时候正在执行的函数,这样就可以有效的定位到具体的问题代码。
Hard Fault分析
现在我们已经知道具体哪些原因会导致Hard Fault,那么我们可以针对上述的几种异常行为做单独的分析。
Bus Fault
总线异常是一种非常常见的错误,针对这种错误,可以从如下一些方面入手:
- SRAM ECC问题:这个问题具体表现是程序可能在一个随机RAM地址产生Bus Fault,或者MCU重新上电之后运行异常。ECC问题产生的根本原因是SRAM上电是随机值,如果这个时候读SRAM就大概率会产生ECC错误,表现上就是有了Bus Error。因为SRAM 的ECC是基于32bit计算的,所以对于有ECC错误的地址进行byte或者half word(16bit)的写操作,系统写入之前也会有一个读操作,这个操作同样会有ECC错误。对于SRAM的ECC错误,我们只需要在初始化的时候按照32Bit写入任意值即可。
- 访问外设寄存器产生Bus Fault,这个一般是因为外设时钟没有打开导致的,只需要在IPC中开启相应模块的时钟即可。
- 访问了被保护的寄存器,如果使用了PPU模块保护了外设寄存器,那么访问被保护的寄存器就有可能产生Bus Fault
- 外设寄存器访问越界,一般外设模块并不会使用完整的4K空间,这个时候如果访问了外设未定义的寄存器地址也会产生Bus Fault。这种情况一般发生在可以灵活配置的外设模块,比如eTMR中只定义了2个通道,而软件访问了4个通道。
Usage Fault
Usage Fault 一般产生在CPU取到一个错误的指令,造成这种故障一般有以下几种原因:
- 非对齐访问:在CM0内核的MCU中,如果对一个非4byte对齐的地址进行读写操作,则会产生非对齐访问错误,产生这种错误要查找是否有对指针的强制类型转换,有的编译器会将uint8_t 指针转换为uint32_t 视为错误,但如果指针经过计算则不会报错。定位问题的位置一般可以通过堆栈Call Stack获取具体的故障地址。
- 堆栈溢出导致堆栈中的数据被意外修改,比如CPU直接返回到一个异常被修改的地址取指运行
- 除0异常,这种异常一般比较少见,主要是除法运算过程中可能出现除数是0的情况。
- 另外当CPU跳转到RAM中执行程序,而RAM中的程序未正常初始化,或者初始化之后被其它代码改动了RAM中的内容,此时CPU也会取到异常的指令而产生Usage Fault
MemManage Fault
这种异常处理比较简单,因为异常的原因就是MPU保护区域被非法访问,SCB中会直接记录异常产生的具体地址,所以结合数据断点或者Call Stack可以非常迅速的定位到故障点。
Hard Fault
从上述描述中,我们了解到HardFault本质上就是上面三种Fault,HardFault产生实际上有如下几种原因:
- CM0+内核不支持上述三种中断的区分和控制,所以相关故障统一进入HardFault
- 对于支持三种中断控制的CPU,相应的中断没有开启或者优先级不够,比如将Usage Fault的优先级设置为当前执行中断相同的优先级,因为Usage Fault无法抢占当前中断,该异常也会上升到HardFault形式的中断。
Hard Fault假象
有些时候我们通过调试器看到PC好像运行在Hard Fault函数部分,感觉像是产生了Hardfault,但实际上系统只是进了普通的中断。因为SDK环境中会生成一个默认的中断向量表,向量表中所有的函数都是weak类型,实现方式也都是同HardFault一致,所以会有跳转到HardFault的假象。
对应这种问题,首先我们要通过SCB→ICSR
寄存器确定当前执行的中断向量号,这个时候我们就可以知道具体是哪个模块产生了中断。那么系统进入默认中断实际上有两个值得分析的地方:
- 外设中断函数有没有重写,默认启动文件中的中断时weak类型的,如果SDK或者用户软件中定义了相同名称的函数,中断向量表就会使用新的函数。这种情况如果对应函数的C文件没有在编译列表中,系统还是会进默认中断函数。
- 外设打开了中断,但是没有注册新的中断函数,SDK和用户软件还会通过动态装载中断的方式注册中断函数,如果注册函数没有执行,那么系统也会执行默认的中断。
Core Lockup异常
Core Lockup是ARM CPU中比较特别的一个异常类型,它发生的条件是在HardFault中断函数中再次产生HardFault,这个时候系统就会进入Lockup状态,对于这种异常状态只能通过Reset或者调试器介入来恢复,所以在使用YTM32系列MCU时,可以通过CIM中的控制寄存器打开CoreLockup Reset功能。
总结
总的来说对于HardFault的具体原因还是比较容易分析的,直接按照上述步骤进行排查实际可以分析大多数的异常情况。实际应用中一般比较难处理的情况都是HardFault的复现。如果HardFault产生的比较随机,或者难以复现也是非常头疼的一个问题,对于这种情况我们一旦捕获这种异常一定要保护好现场信息,可以将SCB所有信息以及堆栈信息,OS调度情况等进行保留用于后续分析。
另外也可以结合Trace工具对异常产生的具体流程进行详细追踪,从而找到真正的Root Cause。
最后更新于 2023-01-11 13:42:44 并被添加「MCU 嵌入式 调试 内核」标签,已有 2794 位童鞋阅读过。
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。