Cortex-M3/M4/M7 芯片 Fault 分析原理与实战

news2024/11/25 0:50:16

目录

  • 一、简介
    • 1、异常类型
    • 2、异常优先级
    • 3、同步异步问题
    • 4、异常具体类型
  • 二、Fault exception registers
    • 1、Control registers
      • 1.1 CCR
      • 1.2 SHP
      • 1.3 SHCSR
    • 2、Status and address registers
      • 2.1 HardFault Status Register——HSFR
      • 2.2 Configurable Fault Status Register——CFSR
      • 2.3 MemManage Fault Status and Address Registers (MMFSR; MMFAR)
        • 2.3.1 MemManage Status Register——MMFSR
        • 2.3.2 MemManage Address Register——MMFAR
      • 2.4 BusFault Status and Address Register (BFSR; BFAR)
        • 2.4.1 BusFault Status Register——BFSR
        • 2.4.2 BusFault Address Register——BFAR
      • 2.5 UsageFault Status Register——UFSR
      • 2.6 Auxiliary Bus Fault Status Register——ABFSR (Cortex-M7 only)
  • 四、Fault Handlers 的实现
  • 五、补充
  • 六、实例讲解
    • 1、观察出入栈
    • 2、Fault 演示
      • 2.1 非法读取
      • 2.2 非对齐访问
      • 2.3 非法写入
      • 2.4 非法函数状态
      • 2.5 除 0 的问题


一、简介

1、异常类型

HardFault(硬件错误)是一类在嵌入式系统开发中较为常见的系统异常,优先级仅低于复位和 NMI(不可屏蔽中断)。当系统运行过程中遇到了某些错误时程序就会跳转至 HardFault_Handler 函数中,引发程序故障进而影响程序的正常运行。

一般而言,我们遇到的错误有如下几种:

  • HardFault(硬件故障):默认异常,由于异常处理过程中的错误或由于任何其他异常机制无法管理异常而触发。
  • MemManage(内存管理故障):检测内存管理单元(MPU)中定义的区域的内存访问违规;例如,在一个只有读/写访问权限的内存区域中执行代码。
  • BusFault(总线故障):在指令取指、数据读/写、中断向量取指和寄存器入栈(保存/恢复)中断(进入/退出)时检测内存访问错误。
  • UsageFault(使用故障):检测未定义指令的执行,加载/存储多个未对齐的内存访问。启用时,将检测除 0 和其他非对齐内存访问。

2、异常优先级

每个异常都有一个相关联的异常号和一个相关联的优先级号。为简化软件层,CMSIS 只使用 IRQ 编号,因此对中断以外的异常使用负值。下表按优先级顺序列出故障异常:

ExceptionException Number优先级IRQ Number激活方式
HardFault3-1-13-
MemManage fault4可配置-12同步
BusFault5可配置-11精确时同步,不精确时异步
UsageFault6可配置-10同步

由这张表可以看出,HardFault 异常总是启用的,并且具有固定的优先级(高于其他中断和异常,但低于不可屏蔽中断 NMI)。因此,在禁用故障异常或在执行故障异常处理程序期间发生故障的情况下,将执行 HardFault 异常。通过下面的例子来理解一下这段话。

stm32f4xx_it.c 文件中有如下几个中断服务程序:

void NMI_Handler(void);
void HardFault_Handler(void);
void MemManage_Handler(void);
void BusFault_Handler(void);
void UsageFault_Handler(void);
void SVC_Handler(void);
void DebugMon_Handler(void);
void PendSV_Handler(void);
void SysTick_Handler(void);

MemManage_Handler 为例,如果你没有使能这个中断的话,那它肯定也不会进入这个中断,而是会进入 HardFault_Handler,也就是硬件异常!这也是下面要讲的优先级提升

而所有其他故障异常(MemManage faultBus FaultUsage Fault)都具有可编程优先级。重置后,这些异常被禁用,可以在系统或应用软件中使用系统控制块(SCB)中的寄存器启用。

通常,异常优先级和异常掩码寄存器的值一起确定处理器是否进入错误处理程序,以及错误处理程序是否可以抢占另一个错误处理程序。在某些情况下(例如刚才的例子),具有可配置优先级的故障被视为 Hard Fault。这被称为优先级提升,即故障升级为 HardFault。升级到 HardFault 发生在:

  1. 当一个 Fault 触发时,并再次触发相同的 Fault。之所以会升级到 HardFault,是因为处理程序不能抢占自己(它必须具有与当前优先级相同的优先级)。
  2. 当一个 Fault 执行时,有一个优先级比它低或者同级的 Fault 被触发。因为新错误的处理程序不能抢占当前执行的错误处理程序。
  3. 当一个(普通的)中断在执行的时候,有一个优先级比它低或者同级的(普通的)中断触发了 Fault。
  4. 发生了 Fault,但未启用该错误的处理程序(上面介绍过的例子)。

如果在入栈过程中进入 BusFault handler 时发生 BusFaultBusFault 不会升级为 HardFault。这意味着,如果崩溃的栈导致了错误,即使处理程序的入栈失败,也会执行错误处理程序。错误处理程序正常运行,但栈内容已损坏。

注意:只有 Reset(复位中断) 和 NMI(不可屏蔽中断) 可以抢占固定优先级的 HardFaultHardFault 可以抢占除 Reset、NMI 或其他 HardFault 之外的任何异常。如下图所示:

3、同步异步问题

BusFault 错误处理程序可以使用 BFSR 来确定错误是异步的(IMPRECISERR,不精确的)还是同步的(PRECISERR,精确的)。

同步总线故障也被称为精确总线故障。何谓精确?就是总线触发异常之后,我们可以找到它触发异常的地址。如果同步总线故障发生在 NMI 或 HardFault 处理程序内,则会升级为锁定。缓存维护操作也可能触发总线故障。调试访问也可能触发总线故障。调试器的加载或存储访问是同步的,并且仅对调试器接口可见。

异步总线故障被称为为不精确总线故障,同理,就是总线触发异常之后,我们不能找到它触发异常的地址。它可能发生在处理器设计中存在写缓冲时。因此,在观察到总线错误响应之前,处理器流水线会继续执行后续的指令。当异步总线故障被触发时,总线故障异常被挂起。如果另一个高优先级的中断事件同时到达,则首先执行高优先级的中断处理程序,然后发生 BusFault。如果总线故障处理程序未启用,则挂起 HardFault。由异步总线故障引起的硬故障不会升级为锁定。异步错误通常是不可恢复的,因为你不知道是哪段代码导致了错误。

现代的芯片都是多级流水线执行的,它触发的时候,多级流水线人在执行,我们无法锁定是哪一级出了问题,也就无法找到精确的错误地址。

4、异常具体类型

下表中列出了 Fault 类型、Fault Handler、Fault status register 和故障发生的寄存器位名:

  • HardFault - HFSR
Fault typeBit Name
Bus error on a vector read errorVECTTBL
Fault that is escalated to a hard faultFORCED
Fault on breakpoint escalationDEBUGEVT
  • MemManage - MMFSR
Fault typeBit Name
Fault on instruction accessIACCVIOL
Fault on direct data accessDACCVIOL
Context stacking, because of an MPU access violationMSTKERR
Context unstacking, because of an MPU access violationMUNSTKERR
During lazy floating-point state preservationMLSPERR
  • BusFault - BFSR
Fault typeBit Name
During exception stackingSTKERR
During exception unstackingUNSTKERR
During instruction prefetching, preciseIBUSERR
During lazy floating-point state preservationLSPERR
Precise data access error, precisePRECISERR
Imprecise data access error, impreciseIMPRECISERR
  • UsageFault - UFSR
Fault typeBit Name
Undefined instructionUNDEFINSTR
Attempt to enter an invalid instruction set stateINVSTATE
Failed integrity check on exception returnINVPC
Attempt to access a non-existing coprocessorNOCPC
Illegal unaligned load or storeUNALIGNED
Stack overflowSTKOF
Divide By 0DIVBYZERO

下面就来看一下这些寄存器。

二、Fault exception registers

1、Control registers

系统控制块(SCB)提供系统实施信息和系统控制。这包括系统异常的配置、控制和报告。它的一些寄存器用于控制 Fault 异常。

这里有三个寄存器:

  • CCRThe Configuration and Control Register,配置和控制寄存器),控制 Usage Fault 的除零和非对齐内存访问的行为
  • SHPThe System Handler Priority Registers,系统处理程序优先级寄存器),控制异常优先级
  • SHCSRThe System Handler Control and State Register,系统处理程序控制和状态寄存器),使能系统处理程序,表示Bus FaultMemManage fault 和 SVC异常的待处理状态。
Address / AccessRegisterReset ValueDescription
0xE000ED14
RW privileged
CCR0x00000000包含捕获与 UsageFault 的除零和非对齐访问的启用位
0xE000ED18
RW privileged
SHP[12]0x00控制异常处理器的优先级
0xE000ED24
RW privileged
SHCSR0x00000000表示硬故障原因的位

1.1 CCR

  • DIV_0_TRP:在处理器执行除数为 0 的 SDIVUDIV 指令时启用 Usage Fault
    • 0:除 0 不触发;除以 0 得到的商是 0
    • 1:除 0 触发
  • UNALIGN_TRP:当对非对齐地址进行内存访问时,启用 Usage Fault
    • 0:不捕获非对齐的半字和字访问
    • 1:捕获非对齐的半字和字访问;非对齐访问会产生 Usage Fault
    • 请注意,使用 LDMSTMLDRDSTRD 指令的非对齐访问总是会产生 Usage Fault即使 UNALIGN_TRP 设置为 0

1.2 SHP

SHP 寄存器设置异常处理程序的优先级。故障异常通过以下方式控制:

  • SHP[0]:内存管理故障的优先级
  • SHP[1]:总线故障的优先级
  • SHP[2]Usage Fault 的优先级

对于编程中断和异常优先级,CMSIS 提供了 NVIC_SetPriorityNVIC_GetPriority 函数。故障异常的优先级可以修改如下:

NVIC_SetPriority (MemoryManagement_IRQn, 0x0F);
NVIC_SetPriority (BusFault_IRQn, 0x08);
NVIC_SetPriority (UsageFault_IRQn, 0x01);
...
UsageFault_prio = NVIC_GetPriority (UsageFault_IRQn);

函数原型如下:

__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if((int32_t)IRQn < 0) {
    SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
  else {
    NVIC->IP[((uint32_t)(int32_t)IRQn)]               = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
}

__STATIC_INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn)
{

  if((int32_t)IRQn < 0) {
    return(((uint32_t)SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] >> (8 - __NVIC_PRIO_BITS)));
  }
  else {
    return(((uint32_t)NVIC->IP[((uint32_t)(int32_t)IRQn)]               >> (8 - __NVIC_PRIO_BITS)));
  }
}

1.3 SHCSR


SHCSR 寄存器的以下几位属于异常:

  • MEMFAULTACT:Memory Management Fault 激活位,如果异常被激活则为1。
  • BUSFAULTACTBus Fault 激活位,如果异常被激活则为 1。
  • USGFAULTACTUsage Fault 激活位,如果异常被激活则为 1
  • USGFAULTPENDEDUsage Fault 待定位,如果异常待定,则为1
  • MEMFAULTPENDEDMemory Management Fault 待定位,如果异常待定,则为1。
  • BUSFAULTPENDEDBus Fault 待定位,如果异常待定,则为1。
  • MEMFAULTENAMemory Management Fault 启用位,设置为 1 表示启用;设置为 0 表示禁用。
  • BUSFAULTENABus Fault 使能位,设为 1 为使能;设置为 0 表示禁用。
  • USGFAULTENAUsage Fault 启用位,设置为 1 表示启用;设置为 0 表示禁用。

虽然可以写入 SHCSR 寄存器的所有位,但在大多数软件应用程序中,只有向启用位写入才有意义。Memory Management FaultBus FaultUsage Fault 可以通过以下语句启用:

SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk
		    | SCB_SHCSR_BUSFAULTENA_Msk
		    | SCB_SHCSR_MEMFAULTENA_Msk; //enable Usage-/Bus-/MPU Fault

2、Status and address registers

下表列出了故障状态寄存器和故障地址寄存器的名称,并列出了每个寄存器的内存地址:

HandlerStatus RegisterAddress RegisterAddressDescription
HardFaultHFSR0xE000ED2CHardFault Status Register
MemManageMMFSRMMFAR0xE000ED28
0xE000ED34
MemManage Fault Status Register
MemManage Fault Address Register
BusFaultBFSRBFAR0xE000ED29
0xE000ED38
BusFault Status Register
BusFault Address Register
UsageFaultUFSR0xE000ED2AUsageFault Status Register
AFSR0xE000ED3CAuxiliary Fault Status Register. Implementation defined content
ABFSR0xE000ED3CAuxiliary BusFault Status Register. Only for Cortex-M7

2.1 HardFault Status Register——HSFR

  • VECTTBL:表示在异常处理期间读向量表的总线故障:
    • 0:向量表读无总线故障
    • 1:向量表读取时的总线故障
    • 当这个比特位被设置时,异常返回的 PC 值指向被异常抢占的指令。这种错误总是一个 Hard Fault
  • FORCED:指示强制硬故障,由具有可配置优先级的故障升级由于优先级或已禁用无法处理而生成:
    • 0:无强制 Hard Fault
    • 1:强制 Hard Fault
    • 在设置该比特位时,Hard Fault Handler 必须读取其他故障状态寄存器,以查找故障的原因。
  • DEBUGEVT:保留用于调试。当写入寄存器时,必须将该位写入 0,否则行为将不可预测。

2.2 Configurable Fault Status Register——CFSR

CSFR 寄存器可以分为三种状态寄存器:Usage FaultBus FaultMemory Management Fault

2.3 MemManage Fault Status and Address Registers (MMFSR; MMFAR)

2.3.1 MemManage Status Register——MMFSR

  • IACCVIOL:非法指令访问标志:
    • 0:无指令访问违规故障
    • 1:处理器试图从不允许执行的位置取指令。
    • 异常返回时入栈的 PC 值指向故障指令。处理器不会向 MMFAR 写入故障地址。在任何尝试取指令到 XN (eXecute Never)区域时都会发生此故障,即使 MPU 被禁用或不存在。潜在的原因如下:
      1. 跳转到 MPU 中未定义或定义为不可执行的区域。
      2. 由于栈内容损坏而无效返回。
      3. 异常向量表中不正确的条目。
  • DACCVIOL:非法数据访问标志:
    • 0:无数据访问违规故障
    • 1:处理器试图读取或存储到不允许操作的位置。
    • 异常返回时入栈的 PC 值指向故障指令。处理器会将出错的访问地址记录到 MMFAR。
  • MUNSTKERR:对返回的异常进行出栈时发生的 MemManage 错误:
    • 0:无出栈故障
    • 1:对异常返回的出栈导致了一个或多个访问违规。
    • 此错误链接到处理程序,这意味着原始返回堆栈仍然存在。处理器没有从失败的返回中调整 SP,也没有执行新的保存。处理器未向 MMFAR 写入故障地址。潜在的原因:
      1. 栈指针崩溃
      2. 栈的 MPU 区域在异常处理程序执行期间发生变化。
  • MSTKERR:异常项入栈时的 MemManage 错误:
    • 0:无入栈故障
    • 1:异常项入栈导致了一个或多个访问违规。
    • SP 仍然被调整,但栈上上下文区域的值可能是不正确的。处理器未向 MMFAR 写入故障地址。潜在的原因:
      1. 栈指针崩溃或未初始化
      2. 栈到达一个未被 MPU 定义为读/写内存的区域。
  • MLSPERR:浮点 lazy state 保存期间的 MemManage 故障(仅适用于带有 FPU 的 Cortex-M4):
    • 0:浮点 lazy state 保存过程中没有发生错误
    • 1:浮点 lazy state 保存时发生故障

lazy state:有时候,浮点寄存器为了加速操作,它有些时候可以选择不主动入栈,有时候可以自动入栈

  • MMARVALIDMemManage Fault 地址寄存器(MMFAR)有效标志:
    • 0:SCB->MMFAR 中的值不是有效的故障地址
    • 1:SCB->MMFAR 保存的是有效的故障地址。
    • 如果发生 MemManage Fault,并且由于优先级的原因升级为 Hard Fault,HardFault 处理程序必须将该比特位设置为 0。这防止了在返回入栈的 active MemManage 故障处理程序时,SCB->MMFAR 值被覆盖的问题。
2.3.2 MemManage Address Register——MMFAR

BFAR 地址与精确数据访问总线故障关联。只允许特权访问。非特权访问产生总线错误

  • ADDRESSMemManage Fault 的数据地址。该寄存器被更新为产生 MemManage Fault的位置的地址。MMFSR 显示故障原因。该字段仅在MMFSR 时有效。设置 MMARVALID。在没有唯一 BFAR 和 MMFAR 寄存器的实现中,如果是 BFSR,则该寄存器的值是未知的。设置BFARVALID

2.4 BusFault Status and Address Register (BFSR; BFAR)

2.4.1 BusFault Status Register——BFSR

总线故障状态寄存器(BFSR)显示由指令获取和数据访问导致的总线错误的状态,并指示在总线操作期间检测到的内存访问故障。只允许特权访问。非特权访问将产生总线故障

  • IBUSERR:指令总线错误。记录指令预取是否发生总线故障。
    • 0:没有指令总线错误
    • 1:指令总线错误。
    • 处理器在预取指令时检测到指令总线错误,但只有在试图发出错误指令时,它才将 IBUSERR 标志设置为 1。当处理器设置该比特位时,它不会向 BFAR 写入故障地址。潜在的原因:
      1. 跳转到无效的内存区域,例如由不正确的函数指针(比如野指针)引起的。
      2. 由于栈指针崩溃或栈内容损坏而无效返回(返回地址出错)。
      3. 无效的中断向量表条目(地址)。
  • PRECISERR:精确的数据总线错误:
    • 0:没有精确的数据总线错误
    • 1:一个数据总线错误已经发生,并且为异常入栈的 PC 值返回到导致故障的指令。
    • 当处理器设置该位时,它将异常地址(不是数据地址)写入 BFAR。
  • IMPRECISERR:不精确的数据总线错误:
    • 0:没有不精确的数据总线错误
    • 1:数据总线发生了错误,但栈帧中的返回地址与导致错误的指令无关。
    • 当处理器设置该比特位时,它不会向 BFAR 写入故障地址。这是一个异步错误。因此:
      1. 如果在当前进程的优先级高于总线故障优先级时检测到总线故障,则总线故障变为待决状态,只有在处理器从所有高优先级进程返回时才变为活动状态。
      2. 如果在处理器进入不精确总线故障的处理程序之前发生了精确故障,那么处理程序将同时检测 IMPRECISERR 设为 1 和精确故障状态位中的一个设为 1。
  • UNSTKERR:返回异常时出栈引发的 Bus Fault:
    • 0:无出栈故障
    • 1:返回异常时出栈导致一个或多个 Bus Fault
    • 这个错误被链接到处理程序。这意味着,当处理器设置该比特位时,原始的返回栈仍然存在。处理器不会从失败的返回中调整 SP,不会执行新的保存,也不会向 BFAR 写入故障地址。
  • STKERR:异常项入栈引发的总线故障:
    • 0:无入栈故障
    • 1:异常项的入栈导致了一个或多个 Bus Fault
    • 在处理器设置该比特位时,SP 仍然会调整,但栈上上下文区域的值可能是不正确的。处理器没有向 BFAR 写入故障地址。潜在的原因:
      1. 栈指针崩溃或未初始化
      2. 栈到达一个未定义的内存区域。
  • LSPERR:浮点 lazy state 保存期间的 Bus Fault(仅当 FPU 存在时):
    • 0:浮点 lazy state 保存过程中没有发生错误
    • 1:浮点 lazy state 保存时发生故障
  • BFARVALID:总线故障地址寄存器(BFAR)有效标志:
    • 0:BFAR 中的值不是有效的故障地址
    • 1:BFAR 保存了有效的故障地址。
    • 在已知地址的 Bus Fault 发生后,处理器设置该比特位。其他错误可能会将该比特位设置为 0,例如稍后发生的 MemManage Fault。如果发生总线故障并由于优先级升级为 Hard Fault,则 Hard Fault Handler 必须将该位设置为 0。
2.4.2 BusFault Address Register——BFAR

BFAR 地址与精确数据访问总线故障关联。只允许特权访问。非特权访问产生总线错误。

  • ADDRESS:精确总线故障的数据地址。用产生总线故障的位置的地址更新该寄存器。BFSR 表示故障原因。该字段仅在 BFSR 时有效。设置 BFARVALID。在没有唯一 BFAR 和 MMFAR 寄存器的实现中,如果是 MMFSR,则该寄存器的值是未知的。设置 MMARVALID。

2.5 UsageFault Status Register——UFSR


Usage Fault 状态寄存器 UFSR包含一些指令执行故障和数据访问的状态。只允许特权访问。非特权访问产生总线错误。

该寄存器分配了下列比特位:

  • UNDEFINSTR:未定义指令。
    • 0:未定义指令
    • 1:处理器试图执行一条未定义的指令。
    • 当该位被设置时,异常返回的 PC 值将指向未定义的指令。未定义指令是处理器无法解码的指令。潜在的原因:
      1. 使用 Cortex-M 设备不支持的指令。(比如在 M3 内核访问浮点指令就会报这个错)
      2. 错误或损坏的内存内容。
  • INVSTATE:无效的状态:
    • 0:没有无效状态
    • 1:处理器试图执行一条指令,非法使用了执行程序状态寄存器(EPSR)。
    • 当设置此位时,异常返回的 PC 值将指向试图非法使用 EPSR 的指令。潜在的原因:
      1. 加载分支目标地址到 PC, LSB=0。(现在使用的 M 内核都是 Thumb2 指令集,最后一位(LSB)一定是 1)
      2. Stacked PSR 在异常或中断处理期间损坏。
      3. 向量表包含一个 LSB=0 的向量地址。
  • INVPC:无效的 PC 加载错误,由无效的 EXC_RETURN 值引起:
    • 0:没有无效的PC负载
    • 1:由于无效的上下文切换,处理器试图向 PC 加载一个非法的 EXC_RETURN 值。
    • 当该位被设置时,异常返回的 PC 值将指向试图执行非法加载 PC 的指令。潜在的原因:
      1. 由于栈指针、链接寄存器(LR)或栈内容损坏而导致无效返回。
      2. PSR 中的 ICI/IT 位对指令无效。

EXC_RETURN 其实就是 LR 寄存器(R14)。它在中断之外(比如函数调用)表示返回地址;而在中断中表示 EXC_RETURN,即异常返回。EXC_RETURN 的位段含义见下表所示,之后该数值会在异常处理结束时触发异常返回。

位段含义
31:4EXC_RETURN 标识符,默认全为 1
4栈帧类型,1(8字)或0(26字)
30(返回进处理模式)
1(返回进线程模式)
20(返回后使用 MSP(主堆栈指针))
1(返回后使用 PSP(任务堆栈指针))
1保留,默认为 0
00(返回ARM状态)
1(返回 Thumb 状态)
  • NOCP:没有协处理器。处理器不支持协处理器指令:
    • 0:no UsageFault,因为尝试访问协处理器而导致
    • 1:处理器试图访问一个不存在的协处理器。
  • UNALIGNED:非对齐访问 UsageFault:
    • 0:no unaligned access fault,或 unaligned access trap not enabled
    • 1:处理器进行了非对齐的内存访问。(使用硬件浮点后,硬件浮点的数据地址,一定是四字节对齐,否则会报这个错)
    • 通过设置 CCR 中的 UNALIGN_TRP 位来启用对非对齐访问的捕获。Unaligned LDM、STM、LDRD 和 STRD 指令总是错误,而不管UNALIGN_TRP 位的设置。
  • DIVBYZERO:UsageFault:
    • 0:无除零故障,或除零陷阱未启用
    • 1:处理器执行了一条除数为 0 的 SDIVUDIV 指令。
    • 当处理器将该位设置为 1 时,异常返回的 PC 值将指向执行除数为 0 的指令。通过将 CCR 中的 DIV_0_TRP 比特位设置为 1 来启用除零的捕获。

注意,UsageFault 状态寄存器的位是粘滞的。这意味着,当一个或多个异常发生时,相关的比特位设置为 1。设置为 1 的比特位只能通过向该比特位写入 1 或复位来清除为 0。

2.6 Auxiliary Bus Fault Status Register——ABFSR (Cortex-M7 only)

辅助总线故障状态寄存器(ABFSR)存储了异步总线故障源的信息。如果发生了总线故障,故障处理程序可以读取该寄存器以确定哪个总线接口触发了故障,如果源是 AXIM 接口,则接收到哪个错误类型。ABFSR[4:0] 字段在用任何值写入 ABFSR 之前都是有效的。ASBFSR 比特分配如下:

  • AXIMTYPE:AXIM接口的故障类型。该值仅在 AXIM=1 时有效。
    • 0b00 = OKAY
    • 0b01 = EXOKAY
    • 0b10 = SLVERR
    • 0b11= DECERR
  • EPPB:EPPB接口异步故障
  • AXIM:AXIM 接口上的异步故障
  • AHBP:AHBP 接口异步故障
  • DTCM:DTCM 接口异步故障
  • ITCM:ITCM 接口异步故障

注意:这些接口可能不会出现在你的实现中。

四、Fault Handlers 的实现

错误处理程序有多种用法。它们可以用来安全关闭系统,通知用户遇到的问题,或者触发整个系统的自测。

符合 CMSIS 规范的启动文件(startup_device.s)定义了设备的所有异常和中断向量。这些向量定义了异常或中断处理程序函数的入口地址。下列代码清单显示了一个典型的向量表:

__Vectors DCD __initial_sp 	     ; Top of Stack
	   	  DCD Reset_Handler      ; Reset Handler
		  DCD NMI_Handler        ; NMI Handler
		  DCD HardFault_Handler  ; Hard Fault Handler
		  DCD MemManage_Handler  ; MPU Fault Handler
		  DCD BusFault_Handler   ; Bus Fault Handler
		  DCD UsageFault_Handler ; Usage Fault Handler
		  DCD 0 				 ; Reserved
......

在调试期间,错误处理程序可能只是导致调试器停止的 BKPT(断点)指令。一般来说,所有错误都会升级为 HardFault,因此在 HardFault 处理程序中添加断点指令就足够了。当使用 MDK 和符合 CMSIS 规范的设备包含文件时,可以用自己的代码覆盖(例如 stm32f4xx_it.c 中的) HardFault 处理程序。

五、补充

下面使用到了 Segger 调试工具,下载地址:HardFaultHandler.zip

里面只有一个 .c 文件和一个 .s 文件,直接移植到你的项目工程即可。本质上就是实现了一个 Hard Fault Handler,并保存了上面提到的一系列寄存器,方便我们分析。

下面介绍一下它的原理。

LR 寄存器在上面刚讲过,在中断中它用作 EXC_RETURN。而它的第二位就是判断使用的是 MSP 还是 PSP。

右边的那个栈保存的是 R0、R1、R2、R3、R12、LR(R14)、PC(R15)、XPSR。这几个寄存器会自动地硬件入栈,入栈之后,进入中断服务程序,然后做进一步处理。而其它的寄存器需要用户来处理,由于我们使用的是 C 编译器,所以这些工作就有编译器来处理,也就是不需要我们管了。

六、实例讲解

1、观察出入栈

例程说明:

下面就进入 Keil 的调试模式:

到这里,在触发 PendSV 中断前,记住这几个会自动入栈的寄存器:

单步执行,然后直接跳转到 PendSV_Handler 里,这里我加了一个断点指令(__BKPT):

现在主栈指针 MSP 的值为 0x20000458,我们看一下这个地址的内容:

正如前面所言,这几个寄存器会自动入栈,不过这里 LR、PC 的值似乎对不上。因为它们会在退出时,重新赋值:

2、Fault 演示

在接下来的例程中,我将对以下五种情况的 Fault 进行演示:

  1. 非法读取
  2. 非对齐访问
  3. 非法写入
  4. 非法函数状态
  5. 除 0 的问题

在调试的时候,还要打开如下窗口:

可以看到,这就是我们前面所讲到的寄存器:

核心代码如下:

switch (UART_RX_BUF_BIN[CNT])
{   
	/* 非法读取 */
	case '1': 
		{
			volatile unsigned int* p;
			unsigned int n;
			p = (unsigned int*)0xCCCCCCCC;
			n = *p;
			
			(void)n;
		}
		break;
	
	/* 非对齐访问 */
	case '2': 
		{
			float *ptr;
                     float fTest;
                     
                     ptr = (float *)(0x20010002);
                     *ptr = 0.2f;
                     *ptr +=0.1f;
                     
                     fTest = *ptr;
                     (void)fTest;
		}
		break;
	
	/* 非法写入 */
	case '3': 
		{
			volatile unsigned int* p;
                     unsigned int n;
                     p = (unsigned int*)0xCCCCCCCC;
                     *p = 100;
                     
                     (void)n;
		}
		break;
		
	/* 非法函数状态 */
	case '4': 
		{
			typedef void (*t_funcPtr)(void);
                     t_funcPtr MyFunc = (t_funcPtr)(0x0001000 | 0x0); /* LSB不能是0*/                                              
                     MyFunc();
		}
		break; 
                 
             /* 除以0的问题 */
	case '5': 
		{
                     int r;
                     volatile unsigned int a;
                     volatile unsigned int b;
                     
                     a = 1;
                     b = 0;
                     r = a / b;
                     
                     (void)r;
		}
		break;
		
	default: printf("Error Num!\r\n");
}

主要是从串口读入一个数字,然后进行相应的操作。

2.1 非法读取

volatile unsigned int* p;
unsigned int n;
p = (unsigned int*)0xCCCCCCCC;
n = *p;

(void)n;

0xCCCCCCCC 没有别的含义,主要用作演示。因为它的最低位是 0,所以会出错——非法读取(地址)。

现在点击全速运行,并通过串口助手发送一个 ‘1’ 给 STM32:

然后进入到 Segger 中实现的 HardFaultHandler
在这里插入图片描述

下面在 Watch 窗口看一下 HardFaultRegs 的值:

HardFaultRegsSEGGER_HardFaultHandler.c 文件中的一个结构体,保存了一系列寄存器的值,非常好用


既然已经发生了错误,所以接下来的工作就是锁定发生错误的位置。

Keil 有一个很简单且方便的方法,见下:

首先在窗口打开 Call Stack Window

然后,在 Call Stack Window 面板里的 HardFaultHandler 右键单击,选择 Show Caller Code

然后就直接跳转到这个地方:

这也这是发生非法读取错误的地方!

通过 Segger,还有另一种办法:

回到这个

LR 记录的是进入中断前的最近一次 LR 寄存器的返回值,而 PC 记录的是进入中断前下一条指令的地址。

现在复制 PC 的值,然后在汇编指令面板中右键,选择:


输入刚才复制的 PC 的值,现在程序跳转到:


而它上一条指令也正是发生错误的那条语句。

2.2 非对齐访问

float *ptr;
float fTest;

ptr = (float *)(0x20010002);
*ptr = 0.2f;
*ptr +=0.1f;

fTest = *ptr;
(void)fTest;

主要是这句:ptr = (float *)(0x20010002);,这里开启了硬件浮点。前文中提到,开启了硬件浮点,则地址必须是四字节对齐。显而易见,这里并不是四字节对齐,也就触发了非对齐访问错误。

和一类似,进入到异常处理程序:

从右边的 Fault Reports 就可以看出这里触发的是非对齐访问异常。而 FORCED 则是由于我的程序中没有开启 Usage Fault,所以就强制升级为 Hard Fault

这里的分析方法和一类似,就不过多介绍了。

2.3 非法写入

volatile unsigned int* p;
unsigned int n;
p = (unsigned int*)0xCCCCCCCC;
*p = 100;

(void)n;

触发中断:


这里就比较特殊了,因为这是一个不精确的总线异常,前面介绍过,这种异常是找不到错误位置的,刚才讲到的方法也都用不上了。

这里就只能细心一点,逐一排查了。

2.4 非法函数状态

typedef void (*t_funcPtr)(void);
t_funcPtr MyFunc = (t_funcPtr)(0x0001000 | 0x0); /* LSB不能是0*/                                              
MyFunc();

再通过刚才的方法去找一下错误位置:

出来的却是这个:

看下 PC 指针:

这是表明这段代码地址是无效的。Cortex-M 内核只有 Thumb 状态,而没有 ARM 状态,如果我们强行让地址执行 ARM 状态,它就会触发异常,就像这里一样。

我们就只能通过这里的 0x00001000 来锁定程序出错的位置:t_funcPtr MyFunc = (t_funcPtr)(0x0001000 | 0x0);

2.5 除 0 的问题

int r;
volatile unsigned int a;
volatile unsigned int b;

a = 1;
b = 0;
r = a / b;

(void)r;

注意,触发除 0 的错误需要单独使能,否则就会如前面所说的得到商为 0 的结果而不会报错!

SCB->CCR = 0x210;  /* 使能除 0 的错误 */

和一二类似,通过提到的两种方法可以找到发生除 0 错误的地址:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2195456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Linux从小白到高手》进阶实操篇:用户及权限有关的实际工作场景应用

List item 本篇为《Linux从小白到高手》进阶实操篇的第一篇&#xff0c;主要介绍分享一些用户及权限有关的实际工作场景应用。 场景1&#xff1a; 实际工作中你一定会碰到如下图所示的情景&#xff1a;本部门有5个组&#xff0c;分别为&#xff1a;①Root组&#xff1a;用户…

Python中对象obj类型确定最pythonic的方式——isinstance()函数

python中确定对象obj的类型&#xff0c;isinstance函数最是优雅&#xff0c;type、issubclass等函数也可以&#xff0c;但终究“曲折”。 (笔记模板由python脚本于2024年10月07日 19:42:38创建&#xff0c;本篇笔记适合喜欢python的coder翻阅) 【学习的细节是欢悦的历程】 Pyth…

Vue2电商项目(七)、订单与支付

文章目录 一、交易业务Trade1. 获取用户地址2. 获取订单信息 二、提交订单三、支付1. 获取支付信息2. 支付页面--ElementUI(1) 引入Element UI(2) 弹框支付的业务逻辑(这个逻辑其实没那么全)(3) 支付逻辑知识点小总结 四、个人中心1. 搭建二级路由2. 展示动态数据(1). 接口(2).…

【Kubernetes】常见面试题汇总(六十)

目录 131. pod 一直处于 pending 状态&#xff1f; 132. helm 安装组件失败&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 69-113 属于…

企业经营异常怎么解除

经营异常是怎么回事&#xff1f;是什么意思&#xff1f;了解异常原因&#xff1a;我们到所属工商营业执照异常的具体原因。原因可能包括未按时提交年报、未履行即时信息公示义务、公示信息隐瞒真实情况或弄xu作jia、失联等。纠正违规行为&#xff1a;查到了异常原因&#xff0c…

洛谷P5723、P5728、P1428、P1319 Python解析

P5723 完整代码 def is_prime(y):if y < 2:return Falsefor i in range(2, int(y**0.5) 1):if y % i 0:return Falsereturn Truen int(input()) sum_primes 0 x 0if n < 2:print("0") elif n 2:print("2\n1") else:for i in range(2, n 1):i…

计数原理与组合 - 离散数学系列(三)

目录 1. 计数原理的基本概念 加法原理&#xff08;Rule of Sum&#xff09; 乘法原理&#xff08;Rule of Product&#xff09; 2. 排列与组合 排列&#xff08;Permutation&#xff09; 组合&#xff08;Combination&#xff09; 日常生活中的例子 3. 二项式定理 4. 实…

Mysql锁机制解读(敲详细)

目录 锁的概念 全局锁 表级锁 表锁 元数据锁 意向锁 锁的概念 全局锁 表级锁 表锁 元数据锁 主要是对未提交事务&#xff0c;修改表结构造成表结构混乱&#xff0c;进行控制。 在不涉及表结构变化的情况下,元素锁可以忽略。 意向锁 避免有行级锁影响加表级锁&#xff0…

Mysql(六) --- 聚合函数,分组和联合查询

文章目录 前言1.聚合函数1.1.常用的函数1.2.COUNT()1.3.SUM()1.4.AVG()1.5.MIN()、MAX() 2.GROUP BY 分组查询2.1.语法2.2.示例2.3.HAVING 子句 3.联合查询3.1.为什么要进行联合查询3.2.那么是如何进行联合查询的3.3.示例&#xff1a;一个完整的联合查询的过程3.4.内连接3.5.外…

Error:WPF项目中使用oxyplot,错误提示命名空间中不存在“Plot”名称

在OxyPlot中&#xff0c;<oxy:PlotView>和<oxy:Plot>都是用来显示图表的控件&#xff0c;在WPF项目中使用oxyplot之前&#xff0c;先通过NuGet安装依赖包&#xff1a;OxyPlot.Wpf。 <oxy:PlotView>和<oxy:Plot>使用示例&#xff1a; <oxy:PlotVie…

【算法】双指针(续)

一、盛最多水的容器 11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多…

OJ在线评测系统 微服务 OpenFeign调整后端下 nacos注册中心配置 不给前端调用的代码 全局引入负载均衡器

OpenFeign内部调用二 4.修改各业务服务的调用代码为feignClient 开启nacos注册 把Client变成bean 该服务仅内部调用&#xff0c;不是给前端的 将某个服务标记为“内部调用”的目的主要有以下几个方面&#xff1a; 安全性: 内部API通常不对外部用户公开&#xff0c;这样可以防止…

Nginx05-基础配置案例

零、文章目录 Nginx05-基础配置案例 1、案例需求 &#xff08;1&#xff09;有如下访问 http://192.168.119.161:8081/server1/location1 访问的是&#xff1a;index_sr1_location1.htmlhttp://192.168.119.161:8081/server1/location2 访问的是&#xff1a;index_sr1_loca…

慢接口分析与优化总结

文章目录 1. 慢接口优化的意义2. 接口耗时构成3. 优化技巧3.1. 内部代码逻辑异步执行[异步思想]并行优化拒绝阻塞等待预分配与循环使用[池化思想]线程池合理设计锁粒度避免过粗优化程序结构 3.2. 缓存恰当引入缓存[空间换时间思想]缓存延迟优化提前初始化缓存[预取思想] 3.3. 数…

工具函数(截取文本第一个字为图片)

const subStringToImage (params) > {const { str ,color #FFF,background #4F54FF,size 60,fontSize 20 } paramsif(str.length < 0) return console.error(字符不能为空!)const text str.slice(0, 1)const canvas document.createElement(canvas)const ctx …

github 国内文件加速下载

参看;https://www.cnblogs.com/ting1/p/18356265 在源网址前加上 https://hub.gitmirror.com/ 或https://mirror.ghproxy.com/&#xff0c;例如&#xff1a; https://hub.gitmirror.com/https://github.com/t1m0thyj/WinDynamicDesktop/releases/download/v5.4.1/WinDynamicD…

算法题总结(十)——二叉树上

#二叉树的递归遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer>(); //也可以把result 作为全局变量&#xff0c;只需要一个函数即可。…

公开数据集网站分享

参考链接&#xff1a;常用的医学组织切片细胞图像数据集_细胞分割数据集-CSDN博客文章浏览阅读1.3w次&#xff0c;点赞32次&#xff0c;收藏133次。乳腺癌细胞图像数据集、血细胞图像数据集、HE染色切片、疟疾细胞图像图像识别、分类、分割_细胞分割数据集https://blog.csdn.ne…

Redis list 类型

list类型 类型介绍 列表类型 list 相当于 数组或者顺序表 list内部的编码方式更接近于 双端队列 &#xff0c;支持头插 头删 尾插 尾删。 需要注意的是&#xff0c;Redis的下标支持负数下标。 比如数组大小为5&#xff0c;那么要访问下标为 -2 的值可以理解为访问 5 - 2 3 …

Linux dlsym和直接调用函数地址解析分析

dlsym 函数是 Linux 下动态链接库&#xff08;shared library&#xff09;编程中的一个重要函数。它用于在运行时获取动态链接库中符号的地址&#xff0c;通常用于获取函数指针或变量的地址。 以下是 dlsym 函数的基本用法和示例。 1. 函数原型 void *dlsym(void *handle, c…