文章目录
我们在开发嵌入式代码时,经常会遇到处理器无法启动或系统停止响应的情况,引起这种症状的原因可能有很多,其中一些可能是硬件问题(比如供电、时钟等),更常见的是处理器触发了Fault 异常,并停留在Fault 异常处理程序内循环(默认情况下,Fault handler 在启动代码中定义为死循环),我们如何分析产生Fault 异常的原因呢?
一、产生Fault 异常的可能原因?
1.1 Fault 异常类别
为了方便我们分析程序产生Fault 的原因,ARM Cortex-M 提供了系统异常处理机制,当Fault 发生时会执行相应的Fault 异常处理程序,我们可以在Fault 异常处理程序中尝试定位或解决相应的Fault 异常。
Cortex-M 提供了哪些Fault 异常类型呢?我们可以从中断向量表中获知(参见博文:中断向量表):
从上表可知,Cortex-M 的Fault 异常主要分为四类(Debug monitor 异常用于前篇博文谈到的两种调试模式中调试监视器模式,也即当发生调试事件时,系统不暂停而是去执行DebugMonitor_Handler 程序):
- MemManage fault:存储器管理故障,主要由违反MPU 定义的访问规则引起的,比如试图访问不被允许的存储区域、从不允许访问的存储区域取指令或读写数据等;
- BusFault:总线访问故障,主要由内存访问期间从处理器总线接口接收到的错误响应触发,比如处理器尝试访问无效的内存位置、设备尚未准备好接收数据传输等;
- Usage fault:用法错误,主要由错误的处理器操作引起的,比如执行未定义的指令、无效的异常返回码EXC_RETURN、未对齐的内存访问、执行除零操作等;
- Hard fault:硬故障(固定优先级为 -1,即它比除 NMI 以外的所有其它中断或异常具有更高的优先级),可能由MemManage fault、BusFault、Usage fault上访而来(若这些fault 未启用,则会强制进入Hard fault 异常处理程序),也可能因取中断向量失败触发。
Cortex-M 处理器是如何检测到系统出现Fault 异常的呢?当处理器进入Fault 异常处理程序后,我们如何判断系统是因为什么原因、在哪个地方引起的Fault 异常呢?
1.2 Fault 状态寄存器
嵌入式处理器的执行状态一般都是通过相应的寄存器展示的,Cortex-M 为了支持Fault 异常处理机制,也提供了一些状态寄存器及地址寄存器,状态寄存器的每一位表示一种Fault 异常原因,地址寄存器则提供发生Fault 的存储地址:
Fault 状态寄存器表示的故障原因如下(方括号内数字表示相应寄存器的第几位,未列出的位为保留位,暂没有使用):
// MMFSR: MemManage Fault Status Register (lowest byte in SCB->CFSR)
[7] MMARVALID - 如果为1,则表示MMFAR寄存器存储的MemManageFault寻址值有效。
[5] MLSPERR - 如果为1,则表示惰性保存浮点状态时发生错误。
[4] MSTKERR - 如果为1,则表示在中断或异常入栈时企图访问不被允许的区域。
[3] MUNSTKERR - 如果为1,则表示在中断或异常出栈时企图访问不被允许的区域。
[1] DACCVIOL - 如果为1,则表示企图从不允许访问的区域读、写数据。
[0] IACCVIOL - 如果为1,则表示企图从不允许访问的区域取指令。
// MMFAR: MemManage Fault Address Register (0xE000ED34, SCB->MMFAR)
[31:0] ADDRESS - 当MMARVALID 的值为1时,MMFAR 记录在访问哪个地址时发生了MemManage Fault。
// BFSR: Bus Fault Status Register (2nd byte in SCB->CFSR)
[15] BFARVALID - 如果为1,则表示BFAR寄存器存储的BusFault 寻址值有效。
[13] LSPERR - 如果为1,则表示惰性保存浮点状态时发生错误。
[12] STKERR - 如果为1,则表示入栈时发生错误。
[11] UNSTKERR - 如果为1,则表示出栈时发生错误。
[10] IMPRECISERR - 如果为1,则表示不精确的数据总线错误。
[9] PRECISERR - 如果为1,则表示精确的数据总线错误。
[8] IBUSERR - 如果为1,则表示指令提取错误。
// BFAR: Bus Fault Address Register (0xE000ED38, SCB->BFAR)
[31:0] ADDRESS - 当BFARVALID的值为1时,BFAR 记录在访问哪个地址时发生了Bus Fault。
// UFSR: Usage Fault Status Register (Upper half-word in SCB->CFSR)
[25] DIVBYZERO - 如果为1,则表示企图执行除 0 操作。
[24] UNALIGNED - 如果为1,则表示企图执行非对齐访问。
[19] NOCP - 如果为1,则表示企图执行不受支持的协处理器指令。
[18] INVPC - 如果为1,则表示将非法或无效的EXC_RETURN值加载到PC。
[17] INVSTATE - 如果为1,则表示试图切换到 ARM 状态。
[16] UNDEFINSTR - 如果为1,则表示企图执行未定义指令。
// HFSR: Hard Fault Status Register (0xE000ED2C ,SCB->HFSR)
[31] DEBUGEVT - 如果为1,则表示发生了一个调试事件。
[30] FORCED - 如果为1,则表示该Hard Fault 是由MemManage Fault、Bus Fault 或Usage Fault 引起的。
[1] VECTTBL - 如果为1,则表示取中断向量时出错。
// DFSR: Debug Fault Status Register (0xE000ED30 ,SCB->DFSR)
[4] EXTERNAL - 如果为1,则表示因外部调试请求触发了调试事件。
[3] VCATCH - 如果为1,则表示因发生向量捕获触发了调试事件。
[2] DWTTRAP - 如果为1,则表示因数据监测点匹配触发了调试事件。
[1] BKPT - 如果为1,则表示因执行BKPT 指令触发了调试事件。
[0] HALTED - 如果为1,则表示调试器请求处理器进入暂停模式。
// AFSR: Auxiliary Fault Status Register (0xE000ED3C, SCB->AFSR)
[31:0] Implementation Defined - 允许芯片设计人员添加自己的故障状态信息。
了解了各类Fault 状态寄存器每一位指示的fault 原因,我们就可以在程序进入fault 异常处理程序后,通过查看上述Fault 状态寄存器哪一位被置为 1 了,了解到产生fault 的可能原因。我们如何定位产生fault 的具体代码呢?
1.3 Fault 异常初始化配置
在回答这个问题前,我们还有个问题,这些fault 寄存器及其异常处理程序在使用前需要怎么初始化呢?
我们在使用中断或异常之前,通常需要先为其配置优先级并使能中断,处理器才会处理并响应对应的中断信号,前面介绍的四种 fault 异常只有Hard Fault 是固定优先级-1,默认开启,其余三种都是默认未开启状态。如果我们想让MemManage Fault、Bus Fault、Usage Fault、Debug Fault 异常处理程序可以正常响应,需要先为其配置优先级并使能对应的异常,配置方法如下(如果上述异常未配置或未开启,处理器也会上访为Hard Fault,去执行Hard Fault Handler):
#define SCB_BASE 0xE000ED00UL /*!< System Control Block Base Address */
/* Structure type to access the System Control Block (SCB). */
typedef struct
{
......
__IOM uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */
__IOM uint8_t SHP