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.
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 */.wordReset_Handler/* Reset routine */.wordNMI_Handler/* No-Maskable Interrupt */.wordHardFault_Handler/* System faults */.wordMemManage_Handler/* Memory access issues */.wordBusFault_Handler/* Bus access issues */.wordUsageFault_Handler/* Instruction/State issues */...
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.
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
Configuration and control register (CCR)
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.
System handler control and state register (SHCSR)
We can use direct memory access method to configure this SHCSR register:
// enable handlersuint32_t*pSHCSR=(uint32_t*)0xE000ED24;*pSHCSR|=(1<<16);// Memory Fault*pSHCSR|=(1<<17);// Bus Fault*pSHCSR|=(1<<18);// Usage Fault// enable Divide by Zero Trapuint32_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 */.wordReset_Handler/* Reset routine */.wordNMI_Handler/* No-Maskable Interrupt */.wordHardFault_Handler/* System faults */.wordMemManage_Handler/* Memory access issues */.wordBusFault_Handler/* Bus access issues */.wordUsageFault_Handler/* Instruction/State issues */
We can directly override those functions in the main file, for example:
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:
Usage Fault: Undefined Instruction
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.
Use Fault Analyzer to find the executing instruction
Exercise
Try to cause Divide by Zero exception
If disable Usage Fault in SHCSR register, which Fault Exception will be raised?
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))voidUsageFault_Handler(void){// get current Stack Pointer__asmvolatile("MRS R0, MSP");__asmvolatile("B UsageFault_Handler_main");}
This naked function will save the MSP register to R0, and pass R0 to the actual handler:
We can write a general dumper to print out the Stack Frame:
voidDumpExceptionRegister(uint32_t*pMSP){printf(" MSP = %p\n",pMSP);printf(" R0 = 0x%lx\n",pMSP[0]);// May have argument of functionprintf(" R1 = 0x%lx\n",pMSP[1]);// May have argument of functionprintf(" R2 = 0x%lx\n",pMSP[2]);// May have argument of functionprintf(" R3 = 0x%lx\n",pMSP[3]);// May have argument of functionprintf(" R12 = 0x%lx\n",pMSP[4]);// IP holds an intermediate value of a calculationprintf(" LR = 0x%lx\n",pMSP[5]);// Address of the next instruction before the exceptionprintf(" PC = 0x%lx\n",pMSP[6]);// CPU was executing the instruction at PCprintf("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.