嵌入式開發筆記——除錯元件SEGGER_HardFaultHandle
阿新 • • 發佈:2020-12-20
# 一、前言
在使用`Cortex-M`核心的MCU進行開發時,有時候會因為對記憶體錯誤訪問等原因造成程式產生異常從而進入`HardFaultHandler`錯誤中斷。如果程式結構比較複雜,尤其是運行了RTOS時可能短時間內不易定位異常產生的原因。**Segger**提供了一種分析CortexM核心晶片HardFault的方法,我在專案中使用後感覺該方法比較實用,本文用來記錄該異常分析元件的使用。
# 二、元件新增
在SEGGER官網的Application Notes頁面下提供了該元件的[原始碼和文件](https://www.segger.com/downloads/application-notes/)
![](https://img2020.cnblogs.com/blog/2193174/202012/2193174-20201220173803639-1940023455.png)
下載下來後將原始檔新增到工程中,然後將中斷處理檔案中的`void HardFault_Handler(void)`函式遮蔽掉(和新增的檔案中的函式有衝突)就完成添加了。
![](https://img2020.cnblogs.com/blog/2193174/202012/2193174-20201220173812088-1137326070.png)
# 三、元件應用
> 執行以下程式碼使程式進入HardFault_Handler
```c
volatile unsigned int* p;
p = (unsigned int*)0x007FFFFF; // 小於0x8000000的地址在STM32中無效
*p = 0x123456;
SEGGER_RTT_printf(0, "p = %d\r\n", *p);
```
在除錯模式下執行程式會停在`SEGGER_HardFaultHandler.c`檔案中的`void HardFaultHandler(unsigned int* pStack)`函式中。
```c
/*********************************************************************
*
* HardFaultHandler()
*
* Function description
* C part of the hard fault handler which is called by the assembler
* function HardFault_Handler
*/
void HardFaultHandler(unsigned int* pStack) {
SEGGER_RTT_printf(0, "system enter hard-fault\r\n");
//
// In case we received a hard fault because of a breakpoint instruction, we return.
// This may happen when using semihosting for printf outputs and no debugger is connected,
// i.e. when running a "Debug" configuration in release mode.
//
if (NVIC_HFSR & (1u << 31)) {
NVIC_HFSR |= (1u << 31); // Reset Hard Fault status
*(pStack + 6u) += 2u; // PC is located on stack at SP + 24 bytes. Increment PC by 2 to skip break instruction.
return; // Return to interrupted application
}
#if DEBUG
//
// Read NVIC registers
//
HardFaultRegs.syshndctrl.byte = SYSHND_CTRL; // System Handler Control and State Register
HardFaultRegs.mfsr.byte = NVIC_MFSR; // Memory Fault Status Register
HardFaultRegs.bfsr.byte = NVIC_BFSR; // Bus Fault Status Register
HardFaultRegs.bfar = NVIC_BFAR; // Bus Fault Manage Address Register
HardFaultRegs.ufsr.byte = NVIC_UFSR; // Usage Fault Status Register
HardFaultRegs.hfsr.byte = NVIC_HFSR; // Hard Fault Status Register
HardFaultRegs.dfsr.byte = NVIC_DFSR; // Debug Fault Status Register
HardFaultRegs.afsr = NVIC_AFSR; // Auxiliary Fault Status Register
//
// Halt execution
// If NVIC registers indicate readable memory, change the variable value to != 0 to continue execution.
//
_Continue = 0u;
while (_Continue == 0u) {
}
//
// Read saved registers from the stack.
//
HardFaultRegs.SavedRegs.r0 = pStack[0]; // Register R0
HardFaultRegs.SavedRegs.r1 = pStack[1]; // Register R1
HardFaultRegs.SavedRegs.r2 = pStack[2]; // Register R2
HardFaultRegs.SavedRegs.r3 = pStack[3]; // Register R3
HardFaultRegs.SavedRegs.r12 = pStack[4]; // Register R12
HardFaultRegs.SavedRegs.lr = pStack[5]; // Link register LR
HardFaultRegs.SavedRegs.pc = pStack[6]; // Program counter PC
HardFaultRegs.SavedRegs.psr.byte = pStack[7]; // Program status word PSR
//
// Halt execution
// To step out of the HardFaultHandler, change the variable value to != 0.
//
_Continue = 0u;
while (_Continue == 0u) {
}
#else
//
// If this module is included in a release configuration, simply stay in the HardFault handler
//
(void)pStack;
do {
} while (1);
#endif
}
```
在`HardFaultRegs`結構體中包含了用來分析異常原因的暫存器
```c
static struct {
struct {
volatile unsigned int r0; // Register R0
volatile unsigned int r1; // Register R1
volatile unsigned int r2; // Register R2
volatile unsigned int r3; // Register R3
volatile unsigned int r12; // Register R12
volatile unsigned int lr; // Link register
volatile unsigned int pc; // Program counter
union {
volatile unsigned int byte;
struct {
unsigned int IPSR : 8; // Interrupt Program Status register (IPSR)
unsigned int EPSR : 19; // Execution Program Status register (EPSR)
unsigned int APSR : 5; // Application Program Status register (APSR)
} bits;
} psr; // Program status register.
} SavedRegs;
union {
volatile unsigned int byte;
struct {
unsigned int MEMFAULTACT : 1; // Read as 1 if memory management fault is active
unsigned int BUSFAULTACT : 1; // Read as 1 if bus fault exception is active
unsigned int UnusedBits1 : 1;
unsigned int USGFAULTACT : 1; // Read as 1 if usage fault exception is active
unsigned int UnusedBits2 : 3;
unsigned int SVCALLACT : 1; // Read as 1 if SVC exception is active
unsigned int MONITORACT : 1; // Read as 1 if debug monitor exception is active
unsigned int UnusedBits3 : 1;
unsigned int PENDSVACT : 1; // Read as 1 if PendSV exception is active
unsigned int SYSTICKACT : 1; // Read as 1 if SYSTICK exception is active
unsigned int USGFAULTPENDED : 1; // Usage fault pended; usage fault started but was replaced by a higher-priority exception
unsigned int MEMFAULTPENDED : 1; // Memory management fault pended; memory management fault started but was replaced by a higher-priority exception
unsigned int BUSFAULTPENDED : 1; // Bus fault pended; bus fault handler was started but was replaced by a higher-priority exception
unsigned int SVCALLPENDED : 1; // SVC pended; SVC was started but was replaced by a higher-priority exception
unsigned int MEMFAULTENA : 1; // Memory management fault handler enable
unsigned int BUSFAULTENA : 1; // Bus fault handler enable
unsigned int USGFAULTENA : 1; // Usage fault handler enable
} bits;
} syshndctrl; // System Handler Control and State Register (0xE000ED24)
union {
volatile unsigned char byte;
struct {
unsigned char IACCVIOL : 1; // Instruction access violation
unsigned char DACCVIOL : 1; // Data access violation
unsigned char UnusedBits : 1;
unsigned char MUNSTKERR : 1; // Unstacking error
unsigned char MSTKERR : 1; // Stacking error
unsigned char UnusedBits2 : 2;
unsigned char MMARVALID : 1; // Indicates the MMAR is valid
} bits;
} mfsr; // Memory Management Fault Status Register (0xE000ED28)
union {
volatile unsigned int byte;
struct {
unsigned int IBUSERR : 1; // Instruction access violation
unsigned int PRECISERR : 1; // Precise data access violation
unsigned int IMPREISERR : 1; // Imprecise data access violation
unsigned int UNSTKERR : 1; // Unstacking error
unsigned int STKERR : 1; // Stacking error
unsigned int UnusedBits : 2;
unsigned int BFARVALID : 1; // Indicates BFAR is valid
} bits;
} bfsr; // Bus Fault Status Register (0xE000ED29)
volatile unsigned int bfar; // Bus Fault Manage Address Register (0xE000ED38)
union {
volatile unsigned short byte;
struct {
unsigned short UNDEFINSTR : 1; // Attempts to execute an undefined instruction
unsigned short INVSTATE : 1; // Attempts to switch to an invalid state (e.g., ARM)
unsigned short INVPC : 1; // Attempts to do an exception with a bad value in the EXC_RETURN number
unsigned short NOCP : 1; // Attempts to execute a coprocessor instruction
unsigned short UnusedBits : 4;
unsigned short UNALIGNED : 1; // Indicates that an unaligned access fault has taken place
unsigned short DIVBYZERO : 1; // Indicates a divide by zero has taken place (can be set only if DIV_0_TRP is set)
} bits;
} ufsr; // Usage Fault Status Register (0xE000ED2A)
union {
volatile unsigned int byte;
struct {
unsigned int UnusedBits : 1;
unsigned int VECTBL : 1; // Indicates hard fault is caused by failed vector fetch
unsigned int UnusedBits2 : 28;
unsigned int FORCED : 1; // Indicates hard fault is taken because of bus fault/memory management fault/usage fault
unsigned int DEBUGEVT : 1; // Indicates hard fault is triggered by debug event
} bits;
} hfsr; // Hard Fault Status Register (0xE000ED2C)
union {
volatile unsigned int byte;
struct {
unsigned int HALTED : 1; // Halt requested in NVIC
unsigned int BKPT : 1; // BKPT instruction executed
unsigned int DWTTRAP : 1; // DWT match occurred
unsigned int VCATCH : 1; // Vector fetch occurred
unsigned int EXTERNAL : 1; // EDBGRQ signal asserted
} bits;
} dfsr; // Debug Fault Status Register (0xE000ED30)
volatile unsigned int afsr; // Auxiliary Fault Status Register (0xE000ED3C), Vendor controlled (optional)
} HardFaultRegs;
```
將`HardFaultRegs`結構體新增到`Watch視窗`中,通過一步步向下執行程式可以看到結構體中各引數狀態,每個暫存器及暫存器bit位表示什麼含義在結構體定義中均有說明。
![](https://img2020.cnblogs.com/blog/2193174/202012/2193174-20201220173835479-1597185694.png)
將`_Continue`變數也新增到Watch視窗,當執行到以下程式碼處時在Watch視窗中改變變數值就可以繼續向下執行。
```c
//
// Halt execution
// If NVIC registers indicate readable memory, change the variable value to != 0 to continue execution.
//
_Continue = 0u;
while (_Continue == 0u) {
}
```
![](https://img2020.cnblogs.com/blog/2193174/202012/2193174-20201220173845053-1618630359.png)
通過對暫存器分析可得出產生異常的原因。
# 四、擴充套件
> ## Cortex-M故障異常
Cortex-M processors implement different fault exceptions.
### HardFault Exception
The HardFault is the default exception, raised on any error which is not associated with another (enabled) exception.
The HardFault has a fixed priority of -1, i.e. it has a higher priority than all other interrupts and exceptions except for NMI. Therefore a HardFault exception handler can always be entered when an error happens in application code, an interrupt, or another exception. The HardFault is exception number 3 in the vector table with IRQ number -13.
### MemManage Exception
The MemManage exception is available with the use of a Memory Protection Unit (MPU) to raise an exception on memory access violations.
The MemManage is exception number 4 in the vector table, IRQ Number -12, and has a configurable priority.
### BusFault Exception
The BusFault exception is raised on any memory access error. E.g. by illegal read, write, or vector catch.
The BusFault is exception number 5 in the vector table, IRQ number -11, and has configurable priority. BusFaults can explicitly be enabled in the system control block (SCB). When BusFault is not enabled, a HardFault is raised.
### UsageFault Exception
The UsageFault exception is raised on execution errors. Unaligned access on load/store multiple instructions are always caught. Exceptions on other unaligned access, as well as division by zero can be additionally enabled in the SCB.
The UsageFault is exception number 6 in the vector table, IRQ number -10, and has configurable priority. When UsageFault is not enabled, a HardFault is raised instead.
> ## 故障狀態暫存器
The Cortex-M System Control Block (SCB) contains some registers which enable configuration of exceptions and provide information about faults.
#### HardFault Status Register (HFSR)
The HFSR is in the SCB at address 0xE000ED2C. It is a 32-bit register.
Bitfields:
```
[31] DEBUGEVT - Reserved for use by debugger/debug probe. Always write 0.
[30] FORCED - If 1, HardFault has been caused by escalation of another exception, because it is disabled or because of priority.
[1] VECTTBL - If 1, a BusFault occurred by reading the vector table for exception processing.
```
#### UsageFault Status Register (UFSR)
The UFSR is a 16-bit pseudo-register, part of the Configurable Fault Status Register (CFSR) at address 0xE000ED28. It can also be directly accessed with halfword access to 0xE000ED2A.
Bitfields:
```
[9] DIVBYZERO - If 1, SDIV or UDIV instruction executed with divisor 0.
[8] UNALIGNED - If 1, LDM, STM, LDRD, STRD on unaligned address executed, or single load or store executed when enabled to trap.
[3] NOCP - If 1, access to unsupported (e.g. not available or not enabled) coprocessor.
[2] INVPC - If 1, illegal or invalid EXC_RETURN value load to PC.
[1] INVSTATE - If 1, execution in invalid state. E.g. Thumb bit not set in EPSR, or invalid IT state in EPSR.
[0] UNDEFINSTR - If 1, execution of undefined instruction.
```
#### BusFault Status Register (BFSR) and BusFault Address Register (BFAR)
The BFSR is a 8-bit pseudo-register in the CFSR. It can be directly accessed with byte access ad 0xE000ED29. The BFAR is a 32-bit register at 0xE000ED38.
Bitfields:
```
[7] BFARVALID - If 1, the BFAR contains the address which caused the BusFault.
[5] LSPERR - 1f 1, fault during floating-point lazy stack preservation.
[4] STKERR - If 1, fault on stacking for exception entry.
[3] UNSTKERR - If 1, fault on unstacking on exception return.
[2] IMPRECISERR - If 1, return address is not related to fault, e.g. fault caused before.
[1] PRECISERR - If 1, return address instruction caused the fault.
[0] IBUSERR - If 1, fault on instruction fetch.
```
#### MemManage Fault Status Register (MMFSR) and MemManage fault Address Register (MMFAR)
The MMFSR is a 8-bit pseudo-register in the CFSR. It can be directly accessed with byte access ad 0xE000ED28. The MMFAR is a 32-bit register at 0xE000ED34.
Bitfields:
```
[7] MMARVALID - If 1, the MMFAR contains the address which caused the MemManageFault.
[5] MLSPERR - 1f 1, fault during floating-point lazy stack preservation.
[4] MSTKERR - If 1, fault on stacking for exception entry.
[3] MUNSTKERR - If 1, fault on unstacking on exception return.
[1] DACCVIOL - If 1, data access violation.
[0] IACCVIOL - If 1, instruction access violation.
```
### Stack Recovery
On exception entry, the exception handler can check which stack has been used when the fault happened. When bit EXC_RETURN[2] is set, MSP has been used, otherwise PSP has been used.
The stack can be used to recover the CPU register values.
### CPU Register Recovery
On exception entry, some CPU registers are stored on the stack and can be read from there for error analysis. the following registers are recoverable:
```
r0 = pStack[0]; // Register R0
r1 = pStack[1]; // Register R1
r2 = pStack[2]; // Register R2
r3 = pStack[3]; // Register R3
r12 = pStack[4]; // Register R12
lr = pStack[5]; // Link register LR
pc = pStack[6]; // Program counter PC
psr.byte = pStack[7]; // Program status word PSR
```
> ## 故障分析示例
The following examples show how/why some faults can be caused, and how to analyze them. A project to test the faults is available [here](https://wiki.segger.com/File:CortexM_FaultTest.zip).
### BusFault Examples
#### Illegal Memory Write
```
/*********************************************************************
*
* _IllegalWrite()
*
* Function description
* Trigger a BusFault or HardFault by writing to a reserved address.
*
* Additional Information
* BusFault is raised some instructions after the write instruction.
* Related registers on fault:
* HFSR = 0x40000000
* FORCED = 1 - BusFault escalated to HardFault (when BusFault is not activated)
* BFSR = 0x00000004
* IMPREISERR = 1 - Imprecise data access violation. Return address not related to fault
* BFARVALID = 0 - BFAR not valid
*/
static int _IllegalWrite(void) {
int r;
volatile unsigned int* p;
r = 0;
p = (unsigned int*)0x00100000; // 0x00100000-0x07FFFFFF is reserved on STM32F4
// F44F1380 mov.w r3, #0x00100000
*p = 0x00BADA55;
// 4A03 ldr r2, =0x00BADA55
// 601A str r2, [r3] <- Illegal write is done here
return r;
// 9B00 ldr r3, [sp]
// 4618 mov r0, r3
// B002 add sp, sp, #8 <- Fault might be raised here
// 4770 bx lr
}
```
#### Illegal Memory Read
```
/*********************************************************************
*
* _IllegalRead()
*
* Function description
* Trigger a BusFault or HardFault by reading from a reserved address.
*
* Additional Information
* BusFault is immediately triggered on the read instruction.
* Related registers on fault:
* HFSR = 0x40000000
* FORCED = 1 - BusFault escalated to HardFault
* BFSR = 0x00000082
* PRECISERR = 1 - Precise data access violation
* BFARVALID = 1 - BFAR is valid
* BFAR = 0x00100000 - The address read from
*/
static int _IllegalRead(void) {
int r;
volatile unsigned int* p;
p = (unsigned int*)0x00100000; // 0x00100000-0x07FFFFFF is reserved on STM32F4
// F44F1380 mov.w r3, #0x00100000 <- The read address. Will be found in BFAR
r = *p;
// 681B ldr r3, [r3] <- Illegal read happens here and raises BusFault
// 9300 str r3, [sp]
return r;
}
```
#### Illegal Function Execution
```
/*********************************************************************
*
* _IllegalFunc()
*
* Function description
* Trigger a BusFault or HardFault by executing at a reserved address.
*
* Additional Information
* BusFault is triggered on execution at the invalid address.
* Related registers on fault:
* HFSR = 0x40000000
* FORCED = 1 - BusFault escalated to HardFault
* BFSR = 0x00000001
* IBUSERR = 1 - BusFault on instruction prefetch
*/
static int _IllegalFunc(void) {
int r;
int (*pF)(void);
pF = (int(*)(void))0x00100001; // 0x00100000-0x07FFFFFF is reserved on STM32F4
// F44F1380 mov.w r3, #0x00100001
r = pF();
// 4798 blx r3 <- Branch to illegal address, causes fetch from 0x00100000 and fault exception
return r;
}
```
### UsageFault Examples
#### Undefined Instruction Execution
```
/*********************************************************************
*
* _UndefInst()
*
* Function description
* Trigger a UsageFault or HardFault by executing an undefined instruction.
*
* Additional Information
* UsageFault is triggered on execution at the invalid address.
* Related registers on hard fault:
* HFSR = 0x40000000
* FORCED = 1 - UsageFault escalated to HardFault
* UFSR = 0x0001
* UNDEFINSTR = 1 - Undefined instruction executed
*/
static int _UndefInst(void) {
static const unsigned short _UDF[4] = {0xDEAD, 0xDEAD, 0xDEAD, 0xDEAD}; // 0xDEA