Debugging Hard Faults on ARM Cortex-M

It is as bad as this: my application stopped in an unhandled interrupt service routine:

Cpu_Interrupt

Cpu_Interrupt

That does not tell much. I’m using Processor Expert generated code, and with this all my ‘unhandled’ vectors are pointing the same handler:

Default Handlers in Vectors.c

Default Handlers in Vectors.c

Vectors.c and Default Handlers

That vectors.c is generated by Processor Expert, but I can change it so it generates a different handler for each interrupt. This is configured in the Build options tab of the CPU properties:

Own Handler for every unhandled interrupt

Own Handler for every unhandled interrupt

With this my vector table changes to use a dedicated handler for each vector:

Own Handlers in Vector Table

Own Handlers in Vector Table

And now I see what is causing my problem: a Hard Fault:

Hard Fault

Hard Fault

The question is now: what is causing that hard fault? Answers to this are behind this link. As for simple example, a NULL function pointer call like this will likely cause such a hard fault:

void (*f)(void);
void call_null_pointer_function(void) {
  f(); /* will execute code at address zero */
}

Executing code at address zero is not something wrong, but there is the vector table and likely the instructions there might be illegal instructions.

Another example is the one below which tries to write 10 to the address zero: on most ARM Cortex the vector table at address zero is in FLASH memory, so writing to that ROM is likely to fail and to cause a hard fault too:

void write_to_rom(void) {
  *((int*)0x0) = 10; /* tries to write to address zero */
}

The problem is: how to find the offending position in the code? The Hard Core handler does not provide any help yet. But this application note link gives more details and explains that a lot of information is stored in the system about the fault itself.

What makes things a lot easier is to use a custom handler.

Simple PC Handler

A very minimalistic handler just provides the offending PC (Program Counter position). I’m using here the syntax for ARM gcc (as used with CodeWarrior for MCU10.3 and the KL25Z Freedom board), but can be easily changed to any other compiler.

An easy method is to replace the Processor Expert generated code fro the Hard_Fault handler with the following one:

__attribute__((naked))
PE_ISR(Cpu_ivINT_Hard_Fault)
{
  __asm volatile (
    " movs r0,#4       \n"
    " movs r1, lr      \n"
    " tst r0, r1       \n"
    " beq _MSP         \n"
    " mrs r0, psp      \n"
    " b _HALT          \n"
  "_MSP:               \n"
    " mrs r0, msp      \n"
  "_HALT:              \n"
    " ldr r1,[r0,#20]  \n"
    " bkpt #0          \n"
  );
}

The assembly code checks which stack we are using (MSP or PSP), and then loads the offending PC position on the stack into the register R1. So R1 will contain the code address where the problem happened:

R1 contains the offending PC

R1 contains the offending PC

Entering that address in the Disassembly View jumps to that position. I just need to keep in mind that the program counter is *after* the problem, and that the program counter has an odd address for ARM Thumb code. So for my example here the problem is caused by the instruction at address 0x608:

Problem at 0x608

Problem at 0x608

Extended Handler

The handler can be extended so it shows as well the other registers stored on the stack:

/**
 * HardFaultHandler_C:
 * This is called from the HardFault_HandlerAsm with a pointer the Fault stack
 * as the parameter. We can then read the values from the stack and place them
 * into local variables for ease of reading.
 * We then read the various Fault Status and Address Registers to help decode
 * cause of the fault.
 * The function ends with a BKPT instruction to force control back into the debugger
 */
void HardFault_HandlerC(unsigned long *hardfault_args){
  volatile unsigned long stacked_r0 ;
  volatile unsigned long stacked_r1 ;
  volatile unsigned long stacked_r2 ;
  volatile unsigned long stacked_r3 ;
  volatile unsigned long stacked_r12 ;
  volatile unsigned long stacked_lr ;
  volatile unsigned long stacked_pc ;
  volatile unsigned long stacked_psr ;
  volatile unsigned long _CFSR ;
  volatile unsigned long _HFSR ;
  volatile unsigned long _DFSR ;
  volatile unsigned long _AFSR ;
  volatile unsigned long _BFAR ;
  volatile unsigned long _MMAR ;

  stacked_r0 = ((unsigned long)hardfault_args[0]) ;
  stacked_r1 = ((unsigned long)hardfault_args[1]) ;
  stacked_r2 = ((unsigned long)hardfault_args[2]) ;
  stacked_r3 = ((unsigned long)hardfault_args[3]) ;
  stacked_r12 = ((unsigned long)hardfault_args[4]) ;
  stacked_lr = ((unsigned long)hardfault_args[5]) ;
  stacked_pc = ((unsigned long)hardfault_args[6]) ;
  stacked_psr = ((unsigned long)hardfault_args[7]) ;

  // Configurable Fault Status Register
  // Consists of MMSR, BFSR and UFSR
  _CFSR = (*((volatile unsigned long *)(0xE000ED28))) ;

  // Hard Fault Status Register
  _HFSR = (*((volatile unsigned long *)(0xE000ED2C))) ;

  // Debug Fault Status Register
  _DFSR = (*((volatile unsigned long *)(0xE000ED30))) ;

  // Auxiliary Fault Status Register
  _AFSR = (*((volatile unsigned long *)(0xE000ED3C))) ;

  // Read the Fault Address Registers. These may not contain valid values.
  // Check BFARVALID/MMARVALID to see if they are valid values
  // MemManage Fault Address Register
  _MMAR = (*((volatile unsigned long *)(0xE000ED34))) ;
  // Bus Fault Address Register
  _BFAR = (*((volatile unsigned long *)(0xE000ED38))) ;

  __asm("BKPT #0\n") ; // Break into the debugger
}

__attribute__((naked))
PE_ISR(Cpu_ivINT_Hard_Fault)
{
  __asm volatile (
    " movs r0,#4       \n"
    " movs r1, lr      \n"
    " tst r0, r1       \n"
    " beq _MSP         \n"
    " mrs r0, psp      \n"
    " b _HALT          \n"
  "_MSP:               \n"
    " mrs r0, msp      \n"
  "_HALT:              \n"
    " ldr r1,[r0,#20]  \n"
    " b HardFault_HandlerC \n"
    " bkpt #0          \n"
  );
}

This will store all the stacked registers into variables I can inspect:

Stacked Registers

Stacked Registers

Summary

With a custom hard fault handler in place, things get a lot easier to solve. So I’m adding that custom handler to my Processor Expert projects to find out what is causing the problem. The only small issue with above approach is that Processor Expert will overwrite my handlers/modifications in Cpu.c, if I do not disable code generation for it. That problem could be solved with a custom handler in the Processor Expert settings. If there is interest about how to do this: post a comment 🙂

Happy Faulting 🙂

34 thoughts on “Debugging Hard Faults on ARM Cortex-M

  1. Pingback: ARM Cortex-M0+ Interrupts and FreeRTOS | MCU on Eclipse

  2. Pingback: A Processor Expert Component to Help with Hard Faults | MCU on Eclipse

  3. Pingback: A new Freedom Board: FRDM-KL05Z | MCU on Eclipse

  4. Pingback: Tutorial: DIY Kinetis SDK Project with Eclipse – Board Configuration | MCU on Eclipse

  5. Hi Erich.
    I have problems of Hard Fault with a KL04.
    I use the kinesit Design Studio 1.1.1.
    Using this post, the problem is in function “zero_fill_bss()”.

    I don’t know how fix this problem.
    Can you help me?
    Thank’s.

    Like

  6. Hi Erich,

    Im trying to follow your steps but im facing some problems that i can’t find out how to solve. I just copied the suggested code to be generated by processor expert for the function PE_ISR(Cpu_ivINT_Hard_Fault), but the compiler says the labels _MSP and _HALT are already defined in the file ccTv4L8N.s, which is not possible to find a source file in my project. Could you please help me to solve that issue? Btw, My codewarrior is 10.5.

    Thanks in advance

    Like

  7. Nice info. But my registers tab is blank, what could cause this? I looked at the registers tab before, no problems. Strange…

    Like

  8. I figured out the problem above (not seeing my registers). But now the problem I’m having is the inline assembly code that you put above is not being executed. The debugger just skips over it. I

    Like

  9. Pingback: Debugging ARM Cortex-M Hard Faults with GDB Custom Command | MCU on Eclipse

  10. Firstly, thanks for an excellent article. A great resource of information.

    I’ve included the full custom hardfault handler but (as noted in the ‘Summary’), I’m now having this issue:

    > The only small issue with above approach is that Processor Expert will overwrite my handlers/modifications in Cpu.c, if I do not disable code generation for it. That problem could be solved with a custom handler in the Processor Expert settings. If there is interest about how to do this: post a comment

    How do we get around this?
    I’ve tried changing the processor expert settings, but when I recreate the PE code the function of course reverts back to the default. I’ve tried altering the code but if I put a function call in to the ISR that will then affect the registers that I want to inspect.
    Any guidance you can offer is gratefully received.

    Like

  11. Pingback: ARM Cortex-M, Interrupts and FreeRTOS: Part 1 | MCU on Eclipse

  12. Pingback: Debugging ARM Cortex-M0+ HardFaults | MCU on Eclipse

  13. Pingback: Cortex-M – Debugging runtime memory corruption – LB9MG

  14. Hi Erich,

    This write up is super helpful. I am not clear with the assembly code used to call the default handler . Especially the below four lines:

    ” movs r0,#4 \n”
    ” movs r1, lr \n”
    ” tst r0, r1 \n”
    ” beq _MSP \n”

    From what I understand LR is checked for 3rd bit set(4 byte alignment). How does this tell us which stack(PSP/ MSP) we are using ?

    Thanks

    Like

  15. Hi, I see you are reading from offset 20 in the pushed stack frame. This is the pushed LR. Why don’t you read from offset 24, which is the pushed PC?

    Like

  16. Pingback: Tutorial: Adding FreeRTOS to where there is no FreeRTOS | MCU on Eclipse

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.