ARM Cortex-M HardFault



Wer kennt das nicht... Da läuft die Software seit Stunden oder gar Tagen/Wochen und dann steht das Ganze plötzlich im HardFault... :-(

Was kann da besser helfen, als ein kleiner Assembler Schnipsel im startup-code, der einem wenigstens die failed PC-Adresse und die Rückkehraddresse (zum Aufrufer) heraus holt.
Sollten die lokalen Variablen dort nicht durch die hier verwendeten Register überschrieben sein, kann man den Fehler somit schon recht gut mit einem angeschlossenen JTAG-Debugger identifizieren...


Cortex-M0Cortex-M3/4
HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] MOVS R5, #4 MOV R6, LR TST R5, R6 BEQ HardFault_M MRS R5, PSP B HardFault_reg HardFault_M MRS R5, MSP HardFault_reg LDR R6, [R5, #24] ; R6 is the failed PC-address LDR R7, [R5, #20] ; R7 is the failed LR-address B . ENDP

HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] TST LR, #4 ITE EQ MRSEQ R10, MSP MRSNE R10, PSP LDR R11, [R10, #24] ; R11 is the failed PC-address LDR R12, [R10, #20] ; R12 is the failed LR-address B . ENDP


Cortex-M23 (ARMv8M baseline)Cortex-M33 (ARMv8M mainline)
HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] MOVS R5, #0x40 MOV R6, LR TST R5, R6 BEQ HardFault_ns HardFault_s MOVS R5, #4 MOV R6, LR TST R5, R6 BEQ HardFault_s_M MRS R5, PSP B HardFault_reg HardFault_s_M MRS R5, MSP B HardFault_reg HardFault_ns MOVS R5, #2 MRS R5, CONTROL_NS TST R5, R6 BEQ HardFault_ns_M MRS R5, PSP_NS B HardFault_reg HardFault_ns_M MRS R5, MSP_NS HardFault_reg LDR R6, [R5, #24] ; R6 is the failed PC-address LDR R7, [R5, #20] ; R7 is the failed LR-address B . ENDP

HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] TST LR, #0x40 BEQ HardFault_ns HardFault_s TST LR, #0x04 ITE EQ MRSEQ R10, MSP MRSNE R10, PSP B HardFault_reg HardFault_ns MRS R10, CONTROL_NS TST R10, #2 ITE EQ MRSEQ R10, MSP_NS MRSNE R10, PSP_NS HardFault_reg LDR R11, [R10, #24] ; R11 is the failed PC-address LDR R12, [R10, #20] ; R12 is the failed LR-address B . ENDP




Wenn diese beiden Register nicht ausreichen um den Fehler zu finden oder eine detail-Implementierung zu Laufzeit benötigt wird, sollte ein umfassender Handler wie der Nachfolgende implementiert werden.

Cortex-M0Cortex-M3/4
HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] MOVS R0, #4 MOV R1, LR TST R0, R1 BEQ HardFault_M MRS R0, PSP B HardFault_reg HardFault_M MRS R0, MSP HardFault_reg B HardFault_HandlerC ENDP

HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] TST LR, #4 ITE EQ MRSEQ R0, MSP MRSNE R0, PSP B HardFault_HandlerC ENDP

void HardFault_HandlerC(unsigned int *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))) ; // --> print or handle the information-set __asm("BKPT #0\n") ; // Break into the debugger }



Für den Cortex-M23 und Cortex-M33 existiert eine Datei "HardFault.c" im CMSIS 5.0.0 die dieses übernimmt.




Ansonsten gibt es natürlich viele weitere Quellen im Internet, die sich mit diesem Thema beschäftigen....


Auf die gleiche Weise kann man die Quelle eines Interruptes, der auf dem DefaultHandler sitzt weil er eigentlich nicht verwendet wird, herausfinden.

Cortex-M
Default_Handler\ PROC EXPORT Default_Handler [WEAK] LDR R7, =0xE000ED04 ; ICSR LDR R7, [R7, #0] UXTB R7, R7 ; R7 is the number of the executing interrupt B . ENDP