Fault Handlers
A fault is an exception generated by the processor to indicate an error. If an associated exception is enabled, the exception handler will be called to report, resolve, or recover the system from the fault.
Last update: 2022-06-29
Table of Content
STM32-Tutorials F411RE_Fault_Handlers.zip
Fault exception#
A fault is an exception generated by the processor to indicate an error.
When there is something violates the design rules of the processor, a fault is triggered.
Whenever a fault happens, internal processor registers will be updated to record the type of fault, the address of instruction at which fault happened, and if an associated exception is enabled, the exception handler will be called.
The fault handler can report, resolve, or recover the system from the fault.
For example:
Dividing a number by zero causes DIVBYZERO fault, which will invoke Usage Fault Handler in which you can get rid of the problem such as closing the problematic task.
The Vector Interrupt Table defines different types with priority of handling order as below:
Exception Number | IRQ Number | Exception Type | Priority | Function |
---|---|---|---|---|
3 | -13 | Hard Fault | -1 | All faults that hang the processor |
4 | -12 | Memory Fault | Configurable | Memory issue |
5 | -11 | Bus Fault | Configurable | Data Bus issue |
6 | -10 | Usage Fault | Configurable | Instruction/State/Access issue |
By default, not all exceptions and interrupts are enabled to be handled.
Exception | Default state | Handling behavior |
---|---|---|
Hard Fault | Always enabled, can be masked | - |
Memory Fault | Disabled by default | Synchronous |
Bus Fault | Disabled by default | Synchronous |
Usage Fault | Disabled by default | Synchronous |
The Vector Interrupt Table is implemented in assembly code in the startup file of MCU startup_*.s
.
g_pfnVectors:
.word _estack /* MSP value */
.word Reset_Handler /* Reset routine */
.word NMI_Handler /* No-Maskable Interrupt */
.word HardFault_Handler /* System faults */
.word MemManage_Handler /* Memory access issues */
.word BusFault_Handler /* Bus access issues */
.word UsageFault_Handler /* Instruction/State issues */
...
Fault types#
Fault | Handler | Bit name | Fault status register |
---|---|---|---|
Bus error on a vector read | HardFault_Handler | VECTTBL | Hard fault status register (HFSR) |
Fault escalated to a hard fault | FORCED | ||
MPU or default memory map mismatch: | MemManage_Handler | - | Memory management fault address register (MMFSR, MMFAR) |
– on instruction access | IACCVIOL | ||
– on data access | DACCVIOL | ||
– during exception stacking | MSTKERR | ||
– during exception unstacking | MUNSKERR | ||
– during lazy floating-point state preservation | MLSPERR | ||
Bus error | BusFault_Handler | - | Bus fault address register (BFSR, BFAR) |
– During exception stacking | STKERR | ||
– During exception unstacking | UNSTKERR | ||
– During instruction prefetch | IBUSERR | ||
– During lazy floating-point state preservation | LSPERR | ||
Precise data bus error | PRECISERR | ||
Imprecise data bus error | IMPRECISERR | ||
Attempt to access a coprocessor | Usage fault | NOCP | Configurable fault status register (CFSR ; UFSR+BFSR+MMFSR) |
Undefined instruction | UNDEFINSTR | ||
Attempt to enter an invalid instruction set state | INVSTATE | ||
Invalid EXC_RETURN value | INVPC | ||
Illegal unaligned load or store | UNALIGNED | ||
Divide By 0 | DIVBYZERO |
Fault escalation and hard faults
All faults exceptions except for hard fault have configurable exception priority, as described in System handler priority registers (SHPRx). Software can disable execution of the handlers for these faults.
Usually, the exception priority, together with the values of the exception mask registers, determines whether the processor enters the fault handler, and whether a fault handler can preempt another fault handler.
In some situations, a fault with configurable priority is treated as a hard fault. This is called priority escalation, and the fault is described as escalated to hard fault. Escalation to hard fault occurs when:
- A fault handler causes the same kind of fault as the one it is servicing. This escalation to hard fault occurs when a fault handler cannot preempt itself because it must have the same priority as the current priority level.
- A fault handler causes a fault with the same or lower priority as the fault it is servicing. This is because the handler for the new fault cannot preempt the currently executing fault handler.
- An exception handler causes a fault for which the priority is the same as or lower than the currently executing exception.
- A fault occurs and the handler for that fault is not enabled.
If a bus fault occurs during a stack push when entering a bus fault handler, the bus fault does not escalate to a hard fault. This means that if a corrupted stack causes a fault, the fault handler executes even though the stack push for the handler failed. The fault handler operates, but the stack contents are corrupted.
Only Reset and NMI can preempt the fixed priority hard fault. A hard fault can preempt any exception other than Reset, NMI, or another hard fault.
Lockup state
The processor enters a lockup state if a hard fault occurs when executing the NMI or hard fault handlers. When the processor is in lockup state it does not execute any instructions.
The processor remains in lockup state until either:
- It is reset
- An NMI occurs
- It is halted by a debugger
If lockup state occurs from the NMI handler a subsequent NMI does not cause the processor to leave lockup state.
Example#
This example enables all configurable fault exceptions, implement fault exceptions handlers, and trigger faults by following methods:
- Execute an undefined instruction
- Divide by Zero
- Execute instruction from peripheral region
- Execute SVC inside the SVC Handler
- Execute SVC inside an interrupt handler whose priority is same or less than SVC priority
Step 0: Create a new project
You should create a bare-metal project which just has a few files including a linker and a main.
Step 1: Enable all fault exceptions
In the document PM0214: STM32 Cortex®-M4 MCUs and MPUs programming manual, look at the below section:
4.4 System control block (SCB The System control block (SCB) provides system implementation information, and system control. This includes configuration, control, and reporting of the system exceptions.
The System handler control and state register (SHCSR) is at the address
0xE000ED24
4.4.7 Configuration and control register (CCR)
The CCR controls entry to Thread mode and enables:
- The handlers for NMI, hard fault and faults escalated by FAULTMASK to ignore bus faults
- Trapping of divide by zero and unaligned accesses
- Access to the STIR by unprivileged software
4.4.9 System handler control and state register (SHCSR)
The SHCSR enables the system handlers, and indicates:
- The pending status of the bus fault, memory management fault, and SVC exceptions
- The active status of the system handlers
If you disable a system handler and the corresponding fault occurs, the processor treats the fault as a hard fault.
We can use direct memory access method to configure this SHCSR register:
// enable handlers
uint32_t* pSHCSR = (uint32_t *)0xE000ED24;
*pSHCSR |= (1 << 16); // Memory Fault
*pSHCSR |= (1 << 17); // Bus Fault
*pSHCSR |= (1 << 18); // Usage Fault
// enable Divide by Zero Trap
uint32_t* pCCR = (uint32_t*)0xE000ED14;
*pCCR |= (1 << 4); // Div by Zero
Step 2: Implement Fault Handlers
The names of handlers are defined in the Interrupt Vector Table:
g_pfnVectors:
.word _estack /* MSP value */
.word Reset_Handler /* Reset routine */
.word NMI_Handler /* No-Maskable Interrupt */
.word HardFault_Handler /* System faults */
.word MemManage_Handler /* Memory access issues */
.word BusFault_Handler /* Bus access issues */
.word UsageFault_Handler /* Instruction/State issues */
We can directly override those functions in the main file, for example:
void UsageFault_Handler() {
printf("Exception: Usage Fault\n");
while(1);
}
Step 3: Trigger Usage Fault
We will try to call a function at a location where there is invalid instruction:
/* Fill a meaningless value */
*(uint32_t*)0x20010000 = 0xFFFFFFFF;
/* Set PC with LSB being 1 to indicate Thumb State */
void (*pFunc)(void) = (void*)0x20010001;
/* call function */
pFunc();
Compile and run the program, you will get Usage Fault exception:
To find out which line of code caused the exception, you can refer to the Fault Analyzer tool. Note that this tool dumps all saved registers during Stacking of Context Switching.
Note that the LR
register save the address of the next instruction of what was being executed.
In our example, if LR
contains 0x80004b7
, we can find the address 0x80004b7
or 0x80004b6
in the disassembly file. The previous instruction of the found instruction at 0x80004b6
mostly the hot spot which caused the fault.
Exercise
- Try to cause Divide by Zero exception
- If disable Usage Fault in
SHCSR
register, which Fault Exception will be raised?
Fault Handler#
We can not plug a debugger all the time to catch the state of system after a Fault happened. A good method is to capture the system state to file or memory for later analysis.
We know that when exception occurs, CPU automatically saves some context registers before jumping to a Fault Handler. We can implement a way to dump those saved registers.
Naked function to capture Stack Frame
A normal function call always has Prologue and Epilogue sequences added by compiler. In the Prologue, some line of code is added to prepare the stack for the function. However, that action will change the Stack Pointer value. Therefore, a naked function should be used to keep the Stack Pointer value.
__attribute__ ((naked)) void UsageFault_Handler(void) {
// get current Stack Pointer
__asm volatile("MRS R0, MSP");
__asm volatile("B UsageFault_Handler_main");
}
This naked function will save the MSP
register to R0
, and pass R0
to the actual handler:
void UsageFault_Handler_main(uint32_t* pMSP) {
printf("Exception: Usage Fault\n");
DumpExceptionRegister(pMSP);
uint32_t* pUFSR = (uint32_t*)0xE000ED2A;
printf("UFSR = 0x%lx\n", *pUFSR & 0xFFFF);
while(1);
}
Helper function to dump Stack Frame
We can write a general dumper to print out the Stack Frame:
void DumpExceptionRegister(uint32_t* pMSP)
{
printf(" MSP = %p\n", pMSP);
printf(" R0 = 0x%lx\n", pMSP[0]); // May have argument of function
printf(" R1 = 0x%lx\n", pMSP[1]); // May have argument of function
printf(" R2 = 0x%lx\n", pMSP[2]); // May have argument of function
printf(" R3 = 0x%lx\n", pMSP[3]); // May have argument of function
printf(" R12 = 0x%lx\n", pMSP[4]); // IP holds an intermediate value of a calculation
printf(" LR = 0x%lx\n", pMSP[5]); // Address of the next instruction before the exception
printf(" PC = 0x%lx\n", pMSP[6]); // CPU was executing the instruction at PC
printf("xPSR = 0x%lx\n", pMSP[7]); // Status of system before execution at PC completes
}
You can use any technique to redirect the Standard IO from printf
to UART terminal, or SWD Terminal.