When a Cortex-M0 processor encounters a fault or exception, it pushes information onto the stack to create a stack frame. This stack frame contains key processor registers that help identify the cause of the fault. The HardFault exception is the catch-all fault handler on Cortex-M0 devices, so understanding its stack frame is crucial for debugging crashes.
Cortex-M0 Exception Stack Frame
On the Cortex-M0, all exceptions have a standard stack frame layout. This includes reset, NMI, hard fault, and configurable exceptions like SVCall. The stack grows downwards on Cortex-M0 devices. When an exception occurs, the processor pushes the stack frame in this order:
- xPSR – Processor state register containing interrupt/exception number and execution state
- PC – Program counter value when exception occurred
- LR – Link register value when exception occurred
- R12 – General purpose register 12
- R3 – General purpose register 3
- R2 – General purpose register 2
- R1 – General purpose register 1
- R0 – General purpose register 0
The xPSR, PC, LR, and R0-R3 registers contain crucial context to identify the root cause of most exceptions. The HardFault handler can analyze them to determine where code crashed and why.
Analyzing the HardFault Stack Frame
When HardFault occurs, the stack frame has additional information beyond the standard exception layout. Two extra registers get pushed: R12 and LR. R12 holds the Stack Frame Pointer (SFP) of the interrupted code. LR contains the program counter value where the fault occurred.
Here are the key steps to analyze a Cortex-M0 HardFault stack frame:
- Locate xPSR to identify the exception number as HardFault
- Read LR to find the crash program counter address
- Check CFSR register for BusFault, MemManageFault, or UsageFault details
- Use R12 SFP to traverse back through call stack
- Analyze crash location source code for bug causes
Let’s go through each of these in more detail…
1. Identify as HardFault Exception
The xPSR contains the IPSR field which identifies the exception number. For HardFault, this value is 0. So check if xPSR & IPSR == 0 to confirm HardFault occurred.
2. Read LR Crash Program Counter
The LR register contains the program counter value when the crash happened. This PC address points to the exact instruction that triggered the HardFault. With symbol information, you can determine the C function and source code line.
3. Check CFSR for Fault Details
The CFSR (Configurable Fault Status Register) provides details on what specifically caused the HardFault. The MMFSR, BFSR, and UFSR fields within CFSR flag memory faults, bus faults, and usage faults respectively. Analyzing these can identify if the crash was due to an invalid memory access, bus error, or unsafe instruction execution.
4. Traverse Call Stack via R12 SFP
R12 contains the Stack Frame Pointer (SFP) value when HardFault occurred. This points to the previous stack frame from the interrupted code. You can traverse backwards through the call stack by following these SFP links to reconstruct the sequence of function calls before the fault.
5. Analyze Source Code for Root Cause
With the LR program counter and call stack context, you can pinpoint the exact statement where HardFault originated. Examining this line of code and those around it will help uncover the root issue. Common causes include dereferencing bad pointers, stack/heap corruption, and race conditions.
Cortex-M0 Register Values During HardFault
Beyond the stack frame, other Cortex-M0 registers contain useful information when debugging a HardFault:
- PC: Holds the address of the HardFault handler, not the fault location
- LR: Points to 0xFFFFFFFF due to exception entry
- SP: Stack pointer reset to the exception stack frame
- PSR: xPSR value saved in the stack frame
- BFAR: Bus fault address register (if enabled)
- CFSR: Configurable fault status register details
The PC and LR registers get overwritten from the values saved in the stack frame. BFAR requires enabling bus fault exceptions. CFSR gives the most specific root cause information.
Enabling Debugging after HardFault
By default, a Cortex-M0 device will get stuck inside the HardFault handler after a crash. To enable debugging, you need to trap processor state during the fault handler.
Common techniques for this include:
- Enable debugging early before HardFault handler
- Enter infinite loop to stop execution
- Write data values to memory to inspect later
- Enable core debug halt
Adding an infinite loop inside HardFault_Handler() will stop the crash and let you analyze the state. Or you can enable debug early via:
void HardFault_Handler(void) {
__asm volatile(
"tst lr, #4 \n"
"ite eq \n"
"mrseq r0, msp \n"
"mrsne r0, psp \n"
"ldr r1, [r0, #24] \n"
"ldr r2, handler2_address_const \n"
"bx r2 \n"
"handler2_address_const: .word prvGetRegistersFromStack");
}
This assembly code snippets loads LR and the stack pointer, then calls a separate handler function to enable debugging.
Cortex-M0 Stack Overflow Detection
Stack overflows are a common source of crashes on embedded devices. The Cortex-M0 provides features to help detect stack overruns:
- PSP Limit Register: Sets upper stack boundary
- Stack Alignment Checking: Enforces 8 byte alignment
- Stack Limit Checking: Triggers fault on overflow
- Memory Protection Unit: Watch for stack/memory violations
The PSP limit register causes a HardFault on stack overflow. Stack alignment and limit checking help catch issues early. And using the MPU to guard the stack region can also detect violations.
These features are not enabled by default though. You need to configure them properly to benefit during development and testing.
Cortex-M0 Stack Overflow Protection
To protect against stack overflows in production code, techniques like stack canaries can be used. A random value gets placed between stack frames, and checked on function exit.
Example in C:
uint32_t stack_canary;
void foo() {
uint32_t canary = stack_canary;
// Function that uses stack
if (canary != stack_canary) {
// Stack smash detected!
HardFault_Handler();
}
}
The HardFault handler could then restart the system or trigger a safe shutdown. This adds runtime checking without hardware features.
Debugging HardFaults on Cortex-M0
Debugging HardFault crashes involves both static and dynamic techniques:
- Stack analysis – Parse stack frames to find crash location
- Register inspection – Check CFSR, BFAR, LR, PC
- Source code – Analyze crash site code for root cause
- Run-time debugging – Halt on fault, step through handler
- Memory dumping – Inspect stack/heap contents
- Code instrumentation – Add logpoints, assertions, and other checks
Static techniques like stack tracing give you crash context. Dynamic debugging through a debugger lets you re-create the crash and follow code execution.
It’s best to combine both static and dynamic debugging. Use stack and register inspection to find the issue area first. Then debug locally or on-target with JTAG/SWD hardware to step through code execution.
JTAG vs SWD Debugging
Cortex-M0 chips support both JTAG and SWD debugging. JTAG uses a 4-pin interface, while SWD uses only 2 pins. But JTAG may give access to more chip internals during low level debugging.
Debugging Crashes Remotely
For remote or post-mortem HardFault debugging, you can send stack frames and register dumps over the network or log them to file. This allows analyzing crashes without a debugger, though it provides less context than live debugging.
Solving Specific HardFault Causes
While the general debugging process is similar for HardFaults, the specific root cause will determine how you solve it.
Null Pointer Dereference
Check for NULL pointers before dereferencing in code. Use assertions or static analysis to catch bugs early.
Stack Corruption
Use stack overflow protection and enforce correct stack management in functions. Watch for buffer overruns.
Heap Corruption
Validate dynamic memory handling – malloc/free usage, out of bounds access, double frees.
Floating Point Exception
Make sure to enable FPCA in CPACR to use floating point. Or avoid floating point entirely.
Conclusion
The Cortex-M0 HardFault handler provides key information through the stack frame and registers. Carefully analyzing this data can reveal the root cause of crashes from illegal memory access, bus errors, stack overflows, and more. Combine static analysis with dynamic run-time debugging via JTAG/SWD to fully diagnose HardFaults.
Enable Cortex-M0 stack overflow and memory protection features in hardware. And utilize debug techniques like stack canaries and logging to strengthen fault handling. Following structured debug and remediation practices will lead to robust and reliable embedded applications on Cortex-M0 processors.