1 内核错误
Cortex-M系列内核有多个专用的故障状态寄存器,可以在发生故障时进行分析,以追踪问题所在。
1.1 错误状态寄存器
1.1.1 Configurable Fault Status Registers (CFSR) - 0xE000ED28
该寄存器包含了导致异常的故障的摘要。该寄存器由三个不同的状态寄存器组成 -- UsageFault、BusFault 和 MemManage:
注意:如果发生多个故障,则可能会设置与多个故障相关的位。 这些字段只能通过系统重置或写入 1 来清除。
1.1.2 BusFault Status Register (BFSR) - 0xE000ED29
该寄存器报告了与指令预取或内存访问失败相关的故障
-
BFARVALID - 表示BFAR(总线故障地址寄存器)的值是否有效
-
LSPERR - 表示FP lazy状态保存期间发生了BusFault故障
-
STKERR - 表示在中断压栈(进入)时发生了BusFault故障
-
UNSTKERR - 表示在中断出栈(退出)时发生了BusFault故障
-
IMPRECISERR - 表示发生了一个非精确(不能得到确切的位置)的数据访问错误。
-
PRECISERR - 表示发生了一个精确(可以得到确切的位置)的数据访问错误。
1.1.3 MemManage Status Register (MMFSR) - 0xE000ED28
该寄存器报告内存保护单元故障。
通常,仅有MPU启用时才会触发MPU故障。但是有时一些内存访问错误也会导致MemManage故障,例如尝试从系统地址范围(0xEXXXXXXX)执行代码
-
MMARVALID - 表示MMFAR(故障地址寄存器)的值是否有效
-
MLSPERR - 表示FP lazy状态保存期间发生了MemManage故障
-
MSTKERR - 表示在中断压栈(进入)时发生了MemManage故障
-
MUNSTKERR - 表示在中断出栈(退出)时发生了MemManage故障
-
DACCVIOL - 表示数据访问触发了MemManage故障
-
IACCVIOL - 表示尝试执行指令时触发了MPU或者从不执行(XN)故障
1.1.4 UsageFault Status Register (UFSR) - 0xE000ED2A
该寄存器报告了与内存访问失败无关的故障,例如执行无效指令或尝试进入无效状态。
-
DIVBYZERO - 执行了除法指令,其中除数为0,该故障可配置。
-
UNALIGNED - 发生了未对齐的访问操作。例如访问了为4字节对齐的uint32_t。低于4字节的未对齐访问是否生成故障是可配置的。
-
NOCP - 发出了协处理器指令,但协处理器不可用。
-
INVPC - EXC_RETURN 发生完整性检查错误
-
INVSTATE - 处理器执行具有无效Execution Program Status Register (EPSR) 值的指令,比如加载pc指令时,地址bit0的值不为1。
-
UNDEFINSTR - 执行了未定义的指令
有些UsageFault可以通过 Configuration and Control Register (CCR)-0xE000ED14 来配置
-
Bit 4 (DIV_0_TRP) - 控制除以零是否会触发故障。
-
Bit 3 (UNALIGN_TRP) - 控制未对齐访问是否始终会生成错误。
1.1.5 HardFault Status Register (HFSR) - 0xE000ED2C
该寄存器报告了触发HardFault异常的原因
-
DEBUGEVT - 表示在调试子系统未启用时发生了调试事件(即执行断点指令)
-
FORCED - 表示UsageFault, BusFault,MemManage升级为HardFault(如果上面的中断没有使能)
-
VECTTBL - 指示由于从向量表中的地址读取问题而发生故障。 这是非常不典型的,但如果向量表中存在错误地址并且发生意外中断,则可能会发生这种情况。
1.2 异常处理实例
1.2.1 恢复Call Stack
为了修复错误,我们需要确定错误发生时正在运行哪些代码。 为了实现这一点,我们需要得到异常进入时的寄存器状态。
当异常进入时,一些寄存器将始终自动保存在堆栈中。 根据是否使用 FPU,硬件将推送基本堆栈帧或扩展堆栈帧。
下面的例子我们仅观察基本堆栈帧,我们可以定义一个结构体:
typedef struct
{
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t psr;
}Exception_Type;
然后我们需要在startup.S文件中添加汇编的中断处理函数,并将该函数从defaultISR中去除
.align 2
.thumb_func
.weak HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
tst LR, #4
ite eq
mrseq r0, msp
mrsne r0, psp
B Hardfault_Process
其中Hardfault_Process为c函数,内容如下:
void Hardfault_Process(Exception_Type *pFrame)
{
SCB->CFSR |= (0x1UL << 9);
pFrame->pc += 2; //PC指针跳过出错地址,往后执行
pFrame->psr = 1 << 24; //重置psr状态,仅保留thumb instruction interworking位
}
这样,虽然产生了hardfault,但是我们的程序不会宕机啦,另外可以在这里对hardfault产生的栈信息进行记录,便于后期排查。
参考资料: