Cortex-M Fault

Cortex-M CPU 会在系统发生故障时引发异常。非法内存写入和读取、访问未通电的外设、执行无效指令、除以零以及其他问题都可能导致此类异常。通常在所有情况下都会引发 HardFault 异常。对于某些故障,可以启用不同的异常来专门处理这些情况。
在这里插入图片描述

Cortex-M 故障异常

Cortex-M 处理器实现了不同的故障异常。

HardFault 异常

HardFault 是默认异常,在任何与其他(启用的)异常无关的错误上引发。

HardFault 的优先级固定为 -1,即它的优先级高于除 NMI 之外的所有其他中断和异常。因此,当应用程序代码、中断或其他异常中发生错误时,总是可以进入 HardFault 异常处理程序。HardFault 是向量表中的异常编号 3,IRQ 编号为 -13。

内存管理异常

MemManage 异常可利用内存保护单元 (MPU) 来引发内存访问违规异常。

MemManage 是向量表中的异常编号 4,IRQ 编号 -12,并且具有可配置的优先级。

总线故障异常

任何内存访问错误都会引发 BusFault 异常。例如,非法读取、写入或向量捕获。

BusFault 是向量表中的异常编号 5,IRQ 编号为 -11,具有可配置的优先级。BusFault 可以在系统控制块 (SCB) 中明确启用。当 BusFault 未启用时,会引发 HardFault。

UsageFault 异常

执行错误时会引发 UsageFault 异常。加载/存储多个指令上的未对齐访问始终会被捕获。其他未对齐访问以及除以零的异常可在 SCB 中额外启用。

UsageFault 是向量表中的异常编号 6,IRQ 编号为 -10,具有可配置的优先级。当未启用 UsageFault 时,将改为引发 HardFault。

异常处理
发生任何异常时,都会调用从向量表读取的异常处理程序,就像中断一样。异常处理通常在开发和生产固件过程中以不同的方式进行。

开发过程中的异常处理

在固件开发过程中,可能会发生错误。开发人员通常希望分析问题所在以解决错误。

Cortex-M NVIC 提供了各种寄存器来分析崩溃的原因。此外,在大多数情况下,可以恢复崩溃发生位置的调用堆栈和寄存器内容。

调试器可以提供特殊功能来帮助分析。在这种情况下,无需向代码添加任何特殊异常处理程序。调试器只需在向量捕获或断点处中断并进行分析即可。

无需调试器的帮助,异常处理程序可以包含开发代码来收集所需信息,以便轻松地在调试器中使用它们。下面列出的 SEGGER HardFault Handler 读取相应的故障寄存器,并使其在结构中轻松可用。

生产固件中的异常处理

发布固件中也可能出现异常,应该将其捕获。

有些异常可能不是由错误引起的。例如,在未连接调试器的情况下执行 BKPT 指令会导致 HardFault。对于这些异常,固件可以简单地返回并继续执行程序。

系统可能还想从某些错误(但不是崩溃)的情况中恢复。在异常处理程序中,可以分析错误原因并从错误中恢复系统,例如通过终止导致错误的任务。

如果发生崩溃,只需重置系统即可恢复。在更高级的情况下,可以在重置之前生成崩溃转储,以便在重启时发送或保存。

异常分析

可以使用上面描述的 HardFault Handler 分析导致异常的系统状态。此外,一些调试器(例如 Ozone)提供了特殊功能来简化分析。

当 Ozone 检测到目标系统崩溃时,它会自动分析目标状态并提供所有必要的信息。异常窗口显示崩溃的原因以及发生的位置和附加 NVIC 寄存器。调用堆栈窗口还可以从异常中展开,以便轻松导航到错误的位置,即使跨多个异常也是如此。

有关更多信息,请参阅使用臭氧分析 Cortex-M 故障

故障状态寄存器

Cortex-M 系统控制块 (SCB) 包含一些寄存器,可以配置异常并提供有关故障的信息。

硬故障状态寄存器 (HFSR)

HFSR 位于地址 0xE000ED2C 的 SCB 中。它是一个 32 位寄存器。

位域:

[31] DEBUGEVT - 保留供调试器/调试探测器使用。始终写入 0[30] FORCED - 如果为 1,则由于禁用或优先级原因,HardFault 是由另一个异常升级引起的。
[1] VECTTBL - 如果为 1,则在读取异常处理向量表时发生 BusFault。

使用故障状态寄存器 (UFSR)

UFSR 是一个 16 位伪寄存器,是地址 0xE000ED28 处可配置故障状态寄存器 (CFSR) 的一部分。也可以通过半字访问 0xE000ED2A 直接访问它。

位域:

[9] DIVBYZERO - 如果为 1,则使用除数 0 执行 SDIV 或 UDIV 指令。
[8] UNALIGNED - 如果为 1,则在未对齐地址上执行 LDM、STM、LDRD 或 STRD,或者在启用陷阱时执行单个加载或存储。
[3] NOCP - 如果为 1,则访问不受支持的(例如不可用或未启用)协处理器。
[2] INVPC - 如果为 1,则将非法或无效的 EXC_RETURN 值加载到 PC。
[1] INVSTATE - 如果为 1,则在无效状态下执行。例如,EPSR 中的 Thumb 位未设置,或者 EPSR 中的 IT 状态无效。
[0] UNDEFINSTR - 如果为 1,则执行未定义的指令。

总线故障状态寄存器 (BFSR) 和总线故障地址寄存器 (BFAR)

BFSR 是 CFSR 中的一个 8 位伪寄存器。它可以通过从 0xE000ED29 开始的字节访问直接访问。BFAR 是一个位于 0xE000ED38 的 32 位寄存器。

位域:

[7] BFARVALID - 如果为 1,则 BFAR 包含导致 BusFault 的地址。
[5] LSPERR - 1f 1,浮点惰性堆栈保存期间发生故障。
[4] STKERR - 如果为 1,则异常入口的堆栈故障。
[3] UNSTKERR - 如果为 1,则异常返回时出栈故障。
[2] IMPRECISERR - 如果为 1,则返回地址与故障无关,例如之前导致的故障。
[1] PRECISERR - 如果为 1,则返回地址指令导致故障。
[0] IBUSERR - 如果为 1,则指令获取故障。

MemManage 故障状态寄存器 (MMFSR) 和 MemManage 故障地址寄存器 (MMFAR)

MMFSR 是 CFSR 中的一个 8 位伪寄存器。它可以通过从 0xE000ED28 开始的字节访问直接访问。MMFAR 是一个位于 0xE000ED34 的 32 位寄存器。

位域:

[7] MMARVALID - 如果为 1,则 MMFAR 包含导致 MemManageFault 的地址。
[5] MLSPERR - 1f 1,浮点惰性堆栈保存期间发生故障。
[4] MSTKERR - 如果为 1,则异常进入堆栈时发生故障。
[3] MUNSTKERR - 如果为 1,则异常返回时出栈时发生故障。
[1] DACCVIOL - 如果为 1,则数据访问违规。
[0] IACCVIOL - 如果为 1,则指令访问违规。

堆栈恢复

在异常入口处,异常处理程序可以检查发生故障时使用了哪个堆栈。当 EXC_RETURN[2] 位被设置时,使用 MSP,否则使用 PSP。

堆栈可用于恢复 CPU 寄存器的值。

CPU寄存器恢复

在异常进入时,一些 CPU 寄存器存储在堆栈中,可以从那里读取以进行错误分析。以下寄存器是可恢复的:

r0 = pStack[0]; // 寄存器 R0 
 r1 = pStack[1]; // 寄存器 R1 
 r2 = pStack[2]; // 寄存器 R2 
 r3 = pStack[3]; // 寄存器 R3 
 r12 = pStack[4]; // 寄存器 R12 
 lr = pStack[5]; // 链接寄存器 LR 
 pc = pStack[6]; // 程序计数器 PC 
 psr.byte = pStack[7]; // 程序状态字 PSR

故障分析示例

以下示例显示了某些故障是如何/为何发生的,以及如何分析它们。此处提供了一个用于测试这些故障的项目。

BusFault 示例

非法内存写入

/**************************************************************************** 
* 
* _IllegalWrite() 
* 
* 函数说明
* 通过写入保留地址来触发 BusFault 或 HardFault。
* 
* 附加信息
* 写入指令后执行某些指令会引发 BusFault。
* 故障时相关寄存器:
* HFSR = 0x40000000 
* FORCED = 1 - BusFault 升级为 HardFault(当 BusFault 未激活时)
* BFSR = 0x00000004 
* IMPRECISERR = 1 - 不精确的数据访问违规。返回地址与故障无关
* BFARVALID = 0 - BFAR 无效
*/ 
static  int  _IllegalWrite ( void )  {
   
    
  int  r ; 
  volatile  unsigned  int *  p ; 

  r  =  0 ; 
  p  =  ( unsigned  int * ) 0x00100000 ;        // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
  // F44F1380 mov.w r3, #0x00100000 
  * p  =  0x00BADA55 ; 
  // 4A03 ldr r2, =0x00BADA55 
  // 601A str r2, [r3] <- 此处执行了非法写入
  return  r ; 
  // 9B00 ldr r3, [sp] 
  // 4618 mov r0, r3 
  // B002 add sp, sp, #8 <- 此处可能会引发故障
  // 4770 bx lr 
}

非法内存读取

/**************************************************************************** 
* 
* _IllegalRead() 
* 
* 功能说明
* 通过从保留地址读取来触发 BusFault 或 HardFault。
* 
* 附加信息
* 读取指令后立即触发 BusFault。
* 故障时相关寄存器:
* HFSR = 0x40000000 
* FORCED = 1 - BusFault 升级为 HardFault 
* BFSR = 0x00000082 
* PRECISERR = 1 - 精确数据访问违规
* BFARVALID = 1 - BFAR 有效
* BFAR = 0x00100000 - 读取的地址
*/ 
static  int  _IllegalRead ( void )  {
   
    
  int  r ; 
  volatile  unsigned  int *  p ; 

  p  =  ( unsigned  int * ) 0x00100000 ;         // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
  // F44F1380 mov.w r3, #0x00100000 <- 读取地址。将在 BFAR 中找到
  r  =  * p ; 
  // 681B ldr r3, [r3] <- 此处发生非法读取并引发 BusFault 
  // 9300 str r3, [sp] 

  return  r ; 
}

非法函数执行

/**************************************************************************** 
* 
* _IllegalFunc() 
* 
* 功能说明
* 通过在保留地址执行来触发 BusFault 或 HardFault。
* 
* 附加信息
* 在无效地址执行时触发 BusFault。
* 故障时相关寄存器:
* HFSR = 0x40000000 
* FORCED = 1 - BusFault 升级为 HardFault 
* BFSR = 0x00000001 
* IBUSERR = 1 - 指令预取时发生 BusFault 
*/ 
static  int  _IllegalFunc ( void )  {
   
    
  int  r ; 
  int  ( * pF )( void ); 

  pF  =  ( int ( * )( void )) 0x00100001 ;          // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
  // F44F1380 mov.w r3, #0x00100001 
  r  =  pF (); // 4798 blx r3 <- 分支到非法地址,导致从 0x00100000 获取并返回故障异常
  return r ; 
  } 

UsageFault 示例

未定义指令执行

/**************************************************************************** 
* 
* _UndefInst() 
* 
* 函数说明
* 执行未定义的指令时触发 UsageFault 或 HardFault。
* 
* 附加信息
* 在无效地址执行时触发 UsageFault。
* 硬故障的相关寄存器:
* HFSR = 0x40000000 
* FORCED = 1 - UsageFault 升级为 HardFault 
* UFSR = 0x0001 
* UNDEFINSTR = 1 - 执行了未定义的指令
*/ 
static  int  _UndefInst ( void )  {
   
    
  static  const  unsigned  short  _UDF [ 4 ]  =  {
   
    0xDEAD ,  0xDEAD ,  0xDEAD ,  0xDEAD };  // 0xDEAD: UDF #<imm> (永久未定义) 
  int  r ; 
  int  ( * pF )( void ); 

  pF  =  ( int ( * )( void ))((( char * ) & _UDF )  +  1 ); 
  // 4B05 ldr r3, =0x08001C18 <_UDF> <- 加载“RAM Code”指令的地址
  // 3301 添加 r3, #1 <- 确保 Thumb 位已设置
  r  =  pF (); 
  // 4798 blx r3 <- 调用“RAM Code”,将执行 UDF 指令并引发异常
  // 9000 str r0, [sp] 
  return  r ; 
}

非法状态

/**************************************************************************** 
* 
* _NoThumbFunc() 
* 
* 功能说明
* 执行未设置 thumb 位的地址时触发 UsageFault 或 HardFault。
* 
* 附加信息
* 在无效地址执行时触发 UsageFault。
* 硬故障的相关寄存器:
* HFSR = 0x40000000 
* FORCED = 1 - UsageFault 升级为 HardFault 
* UFSR = 0x0002 
* INVSTATE = 1 - 以无效状态执行指令
*/ 
static  int  _NoThumbFunc ( void )  {
   
    
  int  r ; 
  int  ( * pF )( void ); 

  pF  =  ( int ( * )( void )) 0x00100000 ;          // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
  // F44F1380 mov.w r3, #0x00100000 <- 请注意,位 [0] 未设置。
  r  =  pF (); 
  // 4798 blx r3 <- 分支交换,模式更改为 ARM,但 Cortex-M 仅支持 Thumb 模式。
  return  r ; 
}

被零除

/**************************************************************************** 
* 
* _DivideByZero() 
* 
* 函数说明
* 通过除以零触发 UsageFault 或 HardFault。
* 
* 附加信息
* 在除法指令上立即触发 UsageFault。
* 硬故障上的相关寄存器:
* HFSR = 0x40000000 
* FORCED = 1 - UsageFault 升级为 HardFault 
* UFSR = 0x0200 
* DIVBYZERO = 1 - 除以零故障
*/ 
static  int  _DivideByZero ( void )  {
   
    
  int  r ; 
  volatile  unsigned  int  a ; 
  volatile  unsigned  int  b ; 
  a  =  1 ; 
  // 2301 movs r3, #1 <- Load divided into a decimal error 
  b  =  0 ; 
  // 2300 movs r3, #0 <- 加载除数
  r  =  a  /  b ; 
  // FBB2F3F3 udiv r3, r2, r3 <- 除以 0 引发故障异常
  return  r ; 
}

未对齐访问


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑟寒凌风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值