Frank's Random Wanderings

Cortex-M3 / M4 Hard Fault Handler

If you’re seeing a Hard Fault exception on your Cortex M3 or Cortex M4 processor, this handler and information may help. I can’t take credit for it – this code was provided by Joseph Yiu on a few different forums, as well as in his book (Definitive Guide to the ARM Cortex M3). I’m simply providing some assistance on how to install and use it.

Hard Fault Handler Installation

These instructions work for an STM32F2xx or STM32F4xx processor using a GNU-based toolchain (eg Yagarto or Sourcery G++). They should work with other processors and toolchains but may require a small tweak – no doubt your compiler will be pleased to tell you if it’s not happy! As always with programming, the following is not the only way to do it – it’s simply the way I did it. If you want to rearrange things or do things a bit differently then feel free.

Joseph’s hard fault handler is in two pieces – a small piece of assembly, and a small piece of C. You need the processor’s hardfault exception vector to jump to the assembly, and then the assembly code will itself call the C code.

Here’s the assembly code. It extracts the location of the stack frame, then passes it as a pointer to the C code, which is named hard_fault_handler_c.

.syntax unified
.cpu cortex-m3
.thumb

.global HardFault_Handler
.extern hard_fault_handler_c

HardFault_Handler:
  TST LR, #4
  ITE EQ
  MRSEQ R0, MSP
  MRSNE R0, PSP
  B hard_fault_handler_c

This assembly needs to be immediately called when the hard fault exception occurs. For the STM32F processors, their vector table is found in the ST-supplied file startup_stm32f2xx.s (or similar). If you look at the vectors list, you’ll see something like this:

g_pfnVectors:
  .word  _estack
  .word  Reset_Handler
  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler
  .word  UsageFault_Handler
  etc

Given that the name in the vector table is HardFault_Handler, we give the assembler code that name (and declare the name as a global so the linker can find it). If your vector table contains a different name, then change the name of the assembler code to suit.

You need to include this bit of assembler in your build. Just save the assembler code in its own .s file (eg: hardfault.s) and then include it in your build, the same way as your other .s files (like that startup file) are.

Now we need to add the C code. Here it is:

// From Joseph Yiu, minor edits by FVH
// hard fault handler in C,
// with stack frame location as input parameter
// called from HardFault_Handler in file xxx.s
void hard_fault_handler_c (unsigned int * hardfault_args)
{
  unsigned int stacked_r0;
  unsigned int stacked_r1;
  unsigned int stacked_r2;
  unsigned int stacked_r3;
  unsigned int stacked_r12;
  unsigned int stacked_lr;
  unsigned int stacked_pc;
  unsigned int stacked_psr;

  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]);

  printf ("\n\n[Hard fault handler - all numbers in hex]\n");
  printf ("R0 = %x\n", stacked_r0);
  printf ("R1 = %x\n", stacked_r1);
  printf ("R2 = %x\n", stacked_r2);
  printf ("R3 = %x\n", stacked_r3);
  printf ("R12 = %x\n", stacked_r12);
  printf ("LR [R14] = %x  subroutine call return address\n", stacked_lr);
  printf ("PC [R15] = %x  program counter\n", stacked_pc);
  printf ("PSR = %x\n", stacked_psr);
  printf ("BFAR = %x\n", (*((volatile unsigned long *)(0xE000ED38))));
  printf ("CFSR = %x\n", (*((volatile unsigned long *)(0xE000ED28))));
  printf ("HFSR = %x\n", (*((volatile unsigned long *)(0xE000ED2C))));
  printf ("DFSR = %x\n", (*((volatile unsigned long *)(0xE000ED30))));
  printf ("AFSR = %x\n", (*((volatile unsigned long *)(0xE000ED3C))));
  printf ("SCB_SHCSR = %x\n", SCB->SHCSR);
  
  while (1);
}

This code goes wherever the existing (previous) Hard Fault Handler went. In our example, the vector table pointed to a function called HardFault_Handler. We are replacing that function with the assembler code, so the original HardFault_Handler function needs to be commented out (otherwise we’ll have two functions with the same name). For the STM32F2xx all exception handlers are found in the file: stm32f2xx_it.c So comment out the function HardFault_Handler() from that C file, and paste the C code for Joseph’s hard_fault_handler_c() into the same file.

That’s it. In summary, you commented out the old hard fault handler, and you added in some assembly code and some C code instead. Try building your project and see what happens.

Note that this code will only work if the main stack pointer hasn’t been badly corrupted prior to the hard fault occurring – if the stack pointer is off in never-never land then the C handler may not work. In my experience this has never been a problem.

Hard Fault Handler Usage

The big thing the above handler gives you is the program counter, which is the address where the processor was executing when the hard fault occurred. You can then look at your listing file (or map file) to see what function and instruction that was. Also useful is LR the Link Register, which contains the return address of the last function call – it can show you where you came from to get to this point.

A few tips.

The typical reason for a hardfault is actually a bus error (which was promoted to a hard fault), because software tried to access an invalid region of memory. There are a couple of common ways of doing this.

One is a bad pointer. It might be uninitialised, or not properly bounded (you ran off the end of an array for example).

Another, more subtle, way of getting a bad pointer is by being sloppy with the scope of variables. This is quite common with main() but can happen anywhere. For example, you define a buffer or a structure as a local variable within main(), but then you access that buffer or structure from some other function (by passing a pointer to it). This is dangerous – you need to declare the buffer or structure as “static” if you want to do that. Otherwise the buffer or structure might be optimised away and not actually exist when that other function tries to access it. If that structure contained a pointer for example, that would become a bad pointer.

Imprecise Bus Fault

For the Cortex M3, an imprecise bus fault (as indicated by bit 10 in the CFSR register) means that a write to an invalid address was attempted. If you look at the program counter, the naughty naughty write is usually present in the 3 or so instructions leading up to the program counter address. Because of the Cortex M3 write buffer system, the program counter might have advanced slightly before the actual bus write took place, hence you need to look back slightly to find the erroneous write.

27 thoughts on “Cortex-M3 / M4 Hard Fault Handler

  1. Aurimas

    Thanks!

    I do not know almost nothing about assembler, so for me it took a day to make it work, but finally it works 🙂

    MDK-ARM STM32F107

    1. Comment out void HardFault_Handler(void)
    2. Add void hard_fault_handler_c (unsigned int * hardfault_args)
    3. In xxx.s file
    replace
    HardFault_Handler\
    PROC
    EXPORT HardFault_Handler [WEAK]
    B .
    ENDP
    with
    HardFault_Handler\
    PROC
    IMPORT hard_fault_handler_c
    TST LR,#4
    ITE EQ
    MRSEQ R0, MSP
    MRSNE R0, PSP
    B hard_fault_handler_c
    ENDP

    you are done!

  2. JAG

    I see Mike having same trouble as I have above :
    make it work on keil env.

    first time I do assembler with keil don’t don’t know if the best way :

    HardFault_Handler\
    PROC
    TST LR, #4
    ITE EQ
    MRSEQ R0, MSP
    MRSNE R0,PSP
    IMPORT hard_fault_handler[DATA]
    B hard_fault_handler
    ENDP

    rest remain the same.

    btw thanks Frank for the help 🙂

  3. frank Post author

    Thanks for the tip. Regarding the Cortex M7, I haven’t played with one yet so I don’t know. However I do have an nice ST M7 board sitting here so I hope to start working with it soon.

  4. frank Post author

    I don’t use Keil so I can’t directly help. However, have you asked Keil? Keil is owned by ARM (if I understand correctly), and it was ARM themselves (Joseph Yiu) who posted this information originally. So you’d figure they’d know how to do it!

  5. Mike

    Hello Frank!

    It’s an amazing work which helps me to be able to figure out the unpredictable reset/hard fault condition. Since I’ve verified your code under gcc compiler and I want to implement it onto keil MDK environment. After few days of assembly code struggling, still I can’t make it work ><.
    Do you have any experiment or hint? That would be helpful!!

    Thanks for your post again!

  6. Peter R.

    Hi Frank,

    you might want to add that it’s important to set the DISDEFWBUF bit to 1.
    This bit is part of the ACTLR (Auxiliary Control Register).
    this would assure to trigger the hardfault at the exact instruction which is responsible for it.

    otherwise, regarding write access to peripherals, this could be delayed due to the bus write buffer being active after reset.

    This technique is also described here:
    http://chmorgan.blogspot.de/2013/06/debugging-imprecise-bus-access-fault-on.html

    unfortunately this is no longer possible with cm7. any ideas on how to immedeatly trigger a fault there?

  7. Hank B

    Many thanks for publishing this article. I used the technique to track down a Hard Fault on an STM32F429I using an I-Jet debugger and the IAR workbench.

    I copied the assembler to the file that contains the interrupt vectors startup_stm32f429xx.s in place of the existing Hard Fault handler and only had to declare the hard_fault_handler_c symbol as EXTERN.

    I modified the C code to use our internal fatal error handler.

    I needed to disconnect the device from the I-Jet to provide the conditions that lead to the hard fault which I did. Upon reconnection, the debugger indicated the processor was stopped at the first line of the assembler code. This part I did not understand. However, the entire call stack including the code that led to the Hard Fault was displayed which made it pretty easy to identify the issue. This other part I also do not understand because previously entry to the Hard Fault handler left nothing in the call stack display.

    Regardless of the parts I do not understand, this did help me to resolve the problem.

    Thanks!

  8. Pepijn

    Hi Frank,

    Having some problems with one of my embedded software programs (STM32L1). I implemented the code above and getting results.
    I’m having some problems interperting the result value. The program counter is always a0 when the exception/fault occours.
    Can you point me in the direction what this means?

    [Hard fault handler – all numbers in hex]
    R0 = 20002820
    R1 = fffffff1
    R2 = 8000261
    R3 = 28013dd8
    R12 = 8000469
    LR [R14] = 20002820 subroutine call return address
    PC [R15] = a0 program counter
    PSR = 8007a11
    BFAR = e000ed38
    CFSR = 400
    HFSR = 40000000
    DFSR = 0
    AFSR = 0
    SCB_SHCSR = 0

    Regards,
    Pepijn

  9. Brad

    Frank,

    I wish I could go back and edit that last comment. I changed ‘.thumb’ to ‘.thumb_func’ and my issue was resolved. Thanks!

    Also, I was wondering – a little off topic – do you use the SDIO libraries provided by STM on your F2 projects?

  10. Brad

    Thanks as this allowed me to look in the correct direction – I am not very good with assembly coding. It is compiling and running the code, but in the disassembly, ‘tst lr, #4’ is translated as ‘svceq #04F01E’ so something is getting lost in translation.

  11. Frank

    Brad, it sounds like perhaps you’ve got the assembly code saved as C code, or something like that? Given that the TST instruction is the first line of assembler, if your toolchain is complaining about it, then it might not realise it’s supposed to be assembler.

  12. Kirill

    Thanks for the article. But i can`t assembler part get working till adding .thum_func above HardFault_Handler. Now works great!

  13. Hill

    Good article and comments.
    The HarfFaultException almost drived me crazy.
    Now I can see PC location that causes problem.

    Just adding on to Ankit’s comment.
    If you are using IAR Workbench. You need to replace __attribute__((naked)) with __task.
    __task void HardFaultException(void)

  14. vashero

    This works great, but is there a way to add the floating point registers to this? or is that done through the fpscr?__get_FPSCR();? I am using the STM32f4, any info will be helpful. Thanks.

  15. bow

    Thanks for sharing. I make a simple test by following this:
    char *pTest;
    *pTest = 0;// invoke Hard fault due to using uninitialized pointer

    I change the assembly code stated in your article, But I get wired LR value such as 0x6336 which can’t be find in map file to attach some corrected function. Out of my exception, The returned PC value is just point to the place where bug had happend. why?

  16. Ankit

    Just adding on to Ramon’s comment

    you also need to specify the naked attribute to avoid the compiler preamble polluting your stack before you read it into the array.

    the handler declaration should look like
    void HardFault_Handler(void) __attribute__((naked));

  17. Ivan

    This article saved my project from missing the release deadline and now we can get it under control eventually! Thank you.

  18. Ramon

    In IAR embedded workbench
    this will assist:

    void HardFault_Handler( void )
    {
    __ASM(“TST LR, #4”);
    __ASM(“ITE EQ”);
    __ASM(“MRSEQ R0, MSP”);
    __ASM(“MRSNE R0, PSP”);
    __ASM(“B hard_fault_handler_c”);
    }

    Thanks for the post

  19. Matt

    I am trying to implement this handler in order to track down a usage fault I am getting somewhere in my code. The problem I have is that the LR and the PC registers are both set to 0 once I enter the fault. Can any of you explain what I am doing wrong?

  20. z4gunn

    Thanks Frank for posting this helpful example from Joseph Yiu. This post prompted me to purchase the “Definitive Guide to the ARM Cortex M3”, which is a great reference book.

    For those of you using the IAR tool chain, the assembly stub must be specified in a modules and section to compile and link properly. Below is the code that I used to apply this example to the IAR tool chain:

    MODULE HARDFAULT_MOD

    SECTION HARDFAULT_SECT : CODE(2)

    THUMB
    PUBLIC HardFault_Handler
    EXTERN HardFault_Handler_C

    HardFault_Handler:
    TST LR, #4
    ITE EQ
    MRSEQ R0, MSP
    MRSNE R0, PSP
    B HardFault_Handler_C

    END

Leave a Reply

Your email address will not be published.