- 做者:zzssdd2
- E-mail:zzssdd2@foxmail.com
在使用Cortex-M
內核的MCU進行開發時,有時候會由於對內存錯誤訪問等緣由形成程序產生異常從而進入HardFaultHandler
錯誤中斷。若是程序結構比較複雜,尤爲是運行了RTOS時可能短期內不易定位異常產生的緣由。Segger提供了一種分析CortexM內核芯片HardFault的方法,我在項目中使用後感受該方法比較實用,本文用來記錄該異常分析組件的使用。app
在SEGGER官網的Application Notes頁面下提供了該組件的源碼和文檔ide
下載下來後將源文件添加到工程中,而後將中斷處理文件中的void HardFault_Handler(void)
函數屏蔽掉(和添加的文件中的函數有衝突)就完成添加了。函數
執行如下代碼使程序進入HardFault_Handlerfetch
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)
函數中。this
/********************************************************************* * * 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
結構體中包含了用來分析異常緣由的寄存器scala
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位表示什麼含義在結構體定義中均有說明。debug
將_Continue
變量也添加到Watch窗口,當執行到如下代碼處時在Watch窗口中改變變量值就能夠繼續向下執行。調試
// // Halt execution // If NVIC registers indicate readable memory, change the variable value to != 0 to continue execution. // _Continue = 0u; while (_Continue == 0u) { }
經過對寄存器分析可得出產生異常的緣由。code
Cortex-M故障異常
Cortex-M processors implement different fault exceptions.orm
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
/********************************************************************* * * _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 }
/********************************************************************* * * _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; }
/********************************************************************* * * _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; }
/********************************************************************* * * _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}; // 0xDEAD: UDF #<imm> (permanently undefined) int r; int (*pF)(void); pF = (int(*)(void))(((char*)&_UDF) + 1); // 4B05 ldr r3, =0x08001C18 <_UDF> <- Load address of "RAM Code" instructions // 3301 adds r3, #1 <- Make sure Thumb bit is set r = pF(); // 4798 blx r3 <- Call "RAM Code", will execute UDF instruction and raise exception // 9000 str r0, [sp] return r; }
/********************************************************************* * * _NoThumbFunc() * * Function description * Trigger a UsageFault or HardFault by executing an address without thumb bit set. * * 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 = 0x0002 * INVSTATE = 1 - Instruction execution with invalid state */ static int _NoThumbFunc(void) { int r; int (*pF)(void); pF = (int(*)(void))0x00100000; // 0x00100000-0x07FFFFFF is reserved on STM32F4 // F44F1380 mov.w r3, #0x00100000 <- Note that bit [0] is not set. r = pF(); // 4798 blx r3 <- Branch exchange with mode change to ARM, but Cortex-M only supports Thumb mode. return r; }
/********************************************************************* * * _DivideByZero() * * Function description * Trigger a UsageFault or HardFault by dividing by zero. * * Additional Information * UsageFault is triggered immediately on the divide instruction. * Related registers on hard fault: * HFSR = 0x40000000 * FORCED = 1 - UsageFault escalated to HardFault * UFSR = 0x0200 * DIVBYZERO = 1 - Divide-by-zero fault */ static int _DivideByZero(void) { int r; volatile unsigned int a; volatile unsigned int b; a = 1; // 2301 movs r3, #1 <- Load dividend b = 0; // 2300 movs r3, #0 <- Load divisor r = a / b; // FBB2F3F3 udiv r3, r2, r3 <- divide by 0 raises fault exception return r; }
/********************************************************************* * * _UnalignedAccess() * * Function description * Trigger a UsageFault or HardFault by an unaligned word access. * * Additional Information * UsageFault is triggered immediately on the read or write instruction. * Related registers on fault: * HFSR = 0x40000000 * FORCED = 1 - UsageFault escalated to HardFault * UFSR = 0x0100 * UNALIGNED = 1 - Unaligned memory access */ static int _UnalignedAccess(void) { int r; volatile unsigned int* p; p = (unsigned int*)0x20000002; // 4B04 ldr r3, =0x20000002 <- Not word aligned address r = *p; // 681B ldr r3, [r3] <- Load word from unaligned address raises exception // 9300 str r3, [sp] return r; }
/********************************************************************* * * _IllegalVector() * * Function description * Trigger a HardFault by interrupt with illegal vector table. * * Additional Information * Related registers on fault: * HFSR = 0x00000002 * VECTTBL = 1 - Vector table read fault */ static int _IllegalVector(void) { int r; SCB->VTOR = 0x001000000; // Relocate vector table to illegal address // 4B09 ldr r3, =0xE000ED00 // F04F7280 mov.w r2, #0x1000000 // 609A str r2, [r3, #8] SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; // Trigger PendSV exception to read invalid vector // 4B07 ldr r3, =0xE000ED00 // F04F5280 mov.w r2, #0x10000000 // 605A str r2, [r3, #4] __ISB(); // F3BF8F6F isb <- PendSV exception is to be executed. PendSV vector is tried to be read from illegal address 0x00100038 causes fault exception // BF00 nop __DSB(); // F3BF8F4F dsb sy // BF00 nop return r; }