Updated: Developing a Generic Hard Fault handler for ARM Cortex-M3/Cortex-M4 using GCC

The original article was first posted back in 2013. Since posting I have been contacted many times regarding the article. One re-occuring question has been “How do I do this using GCC?”. So I thought it was about time I updated the article using GCC.

GNU Tools for ARM Embedded Processors

The original article used the Keil toolchain, here I am using arm-none-eabi-gcc. One of the major benefits of CMSIS is that almost all the code from the original posting will compile unchanged as CMSIS uses conditionals to replace instructions where necessary.

However, note that some of the file names have changed since that original article, e.g.

#include "ARMCM3.h" 

as a file no longer exists. Its contents have been split across a number of headers in the latest CMSIS. In addition, typically for a build, you will be building against a specific platform. In my case I’m targetting an STM32F4xx core.

In my project “ARMCM3.h” has been replaced with “cmsis_device.h” which maps on the the STM32F411.

From Keil to GCC

The code changes only occur when we use assembler to help dump the processor registers as part of the Hard Fault handling. As expected, inline assembler is specific to a toolchain.

The original Keil code was:

void Hard_Fault_Handler(uint32_t stack[]);

__asm void HardFault_Handler(void) 
{
  MRS r0, MSP
  B __cpp(Hard_Fault_Handler) 
}

The same code for GCC is:

void Hard_Fault_Handler(uint32_t stack[]);

void HardFault_Handler (void)
{
  asm volatile(
      " mrs r0,msp    \n"
      " b Hard_Fault_Handler \n"
  );
}

Register Dump Analysis

Given the code from the original posting:

enum { r0, r1, r2, r3, r12, lr, pc, psr};

void Hard_Fault_Handler(uint32_t stack[])
{
   static char msg[80];
   printErrorMsg("In Hard Fault Handler\n");
   sprintf(msg, "SCB->HFSR = 0x%08lx\n", SCB->HFSR);
   printErrorMsg(msg);
   if ((SCB->HFSR & (1 << 30)) != 0) {
       printErrorMsg("Forced Hard Fault\n");
       sprintf(msg, "SCB->CFSR = 0x%08lx\n", SCB->CFSR );
       printErrorMsg(msg);
       if((SCB->CFSR & 0xFFFF0000) != 0) {
         printUsageErrorMsg(SCB->CFSR);
      }
   }

   sprintf(msg, "r0  = 0x%08lx\n", stack[r0]);  printErrorMsg(msg);
   sprintf(msg, "r1  = 0x%08lx\n", stack[r1]);  printErrorMsg(msg);
   sprintf(msg, "r2  = 0x%08lx\n", stack[r2]);  printErrorMsg(msg);
   sprintf(msg, "r3  = 0x%08lx\n", stack[r3]);  printErrorMsg(msg);
   sprintf(msg, "r12 = 0x%08lx\n", stack[r12]); printErrorMsg(msg);
   sprintf(msg, "lr  = 0x%08lx\n", stack[lr]);  printErrorMsg(msg);
   sprintf(msg, "pc  = 0x%08lx\n", stack[pc]);  printErrorMsg(msg);
   sprintf(msg, "psr = 0x%08lx\n", stack[psr]); printErrorMsg(msg);
   __ASM volatile("BKPT #01");
   while(1);
}

When we hit a hard fault we see a dump of the current registers, e.g.

SCB->HFSR = 0x40000000
Forced Hard Fault
SCB->CFSR = 0x02000000
Usage fault: Divide by zero
r0  = 0x0000000a
r1  = 0x00000000
r2  = 0xe000ed00
r3  = 0x00000210
r12 = 0x00000000
lr  = 0x08000415
pc  = 0x080003f8
psr = 0x21000000

The two main registers of interest are the Program Counter (pc) and the Link register (lr).

Using Keil we could disassemble the executable (.axf) using the utility fromelf. However this is proprietary to the Keil toolchain.

Using GCC we have a couple of other useful utilities; to disassemble an (.elf) executable there is a utility called objdump supplied as part of the arm-none-eabi- toolchain.

objdump will disassemble the whole program, but if we pipe the output to grep we can try and filter out the line that caused the hard fault, e.g.:

$ arm-none-eabi-objdump -S build/debug/minimal-gcc.elf | grep 80003f8:
 80003f8:   fb90 f0f1   sdiv    r0, r0, r1

As expected, the signed divide instruction (sdiv) is the root cause of the hard fault as r0 = 10 and r1 = 0.

Note the added : to the grep’ed address, this helps filter only instruction addresses.

Another useful utility is addr2line. Using this will tell us the source file and line number of the supplied address, e.g.

$ arm-none-eabi-addr2line -a 80003f8 -e build/debug/minimal-gcc.elf 
0x080003f8
<path>/src/div.c:6

Now we can see that the offending instruction can be found at (or around) line 6 of the file div.c.

Finally, the Link register contains the address of the calling function before the hard fault. For example, passing the lr value to addr2line:

$ arm-none-eabi-addr2line -a 8000415 -e build/debug/minimal-gcc.elf 
0x08000415
<path>/src/main.c:32

we can see, in main.c at line 32, the call to the function that caused the code to fail, e.g.

 c = div(a, b);

Using Seggar’s Ozone

For most of my target work, where possible I use Segger’s Ozone tool for download and debug. Segger offer the capability to replace certain built-in in-circuit debugger and programmer with their own J-Link compatible software.

For example to use an ST Discovery board with Ozone, see J-Link

When debugging with Ozone, to activate SWO you need to enable it through Tools->Trace Settings…->Trace Source: SWO and leave Clocks on Auto.

 

Niall Cooling

Director at Feabhas Limited
Co-Founder and Director of Feabhas since 1995.
Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking.
His current interest lie in IoT Security and Agile for Embedded Systems.
Dislike (0)

About Niall Cooling

Co-Founder and Director of Feabhas since 1995. Niall has been designing and programming embedded systems for over 30 years. He has worked in different sectors, including aerospace, telecomms, government and banking. His current interest lie in IoT Security and Agile for Embedded Systems.
This entry was posted in ARM, C/C++ Programming, CMSIS, Cortex and tagged , , . Bookmark the permalink.

Leave a Reply