Forum: Mikrocontroller und Digitale Elektronik STM32: HardFault-Error


von Jogi (Gast)


Lesenswert?

Hallo zusammen,

bei meinem STM32 wird regelmäßig ein "HardFault"-Error ausgelöst.
Habe im Internet dazu folgendes Beispiel gefunden:
1
 Debugging a ARM Cortex-M Hard Fault
2
The stack frame of the fault handler contains the state of the ARM Cortex-M registers at the time that the fault occurred. The code below shows how to read the register values from the stack into C variables. Once this is done, the values of the variables can be inspected in a debugger just as an other variable.
3
4
First, a very short assembly function is defined to determine which stack was being used when the fault occurred. Once this is done, the fault handler assembly code passes a pointer to the stack into a C function called prvGetRegistersFromStack().
5
6
The fault handler is shown below using GCC syntax. Note that the function is declared as being naked, so it does not contain any compiler generated code (for example, there is no function entry prologue code).
7
8
9
/* The prototype shows it is a naked function - in effect this is just an
10
assembly function. */
11
static void HardFault_Handler( void ) __attribute__( ( naked ) );
12
13
/* The fault handler implementation calls a function called
14
prvGetRegistersFromStack(). */
15
static void HardFault_Handler(void)
16
{
17
    __asm volatile
18
    (
19
        " tst lr, #4                                                \n"
20
        " ite eq                                                    \n"
21
        " mrseq r0, msp                                             \n"
22
        " mrsne r0, psp                                             \n"
23
        " ldr r1, [r0, #24]                                         \n"
24
        " ldr r2, handler2_address_const                            \n"
25
        " bx r2                                                     \n"
26
        " handler2_address_const: .word prvGetRegistersFromStack    \n"
27
    );
28
}
29
30
31
The implementation of prvGetRegistersFromStack() is shown below. prvGetRegistersFromStack() copies the register values from the stack into the C variables, then sits in a loop. The variables are named to indicate the register value that they hold. Other registers will not have changed since the fault occurred, and can be viewed directly in the debugger's CPU register window.
32
33
34
void prvGetRegistersFromStack( uint32_t *pulFaultStackAddress )
35
{
36
/* These are volatile to try and prevent the compiler/linker optimising them
37
away as the variables never actually get used.  If the debugger won't show the
38
values of the variables, make them global my moving their declaration outside
39
of this function. */
40
volatile uint32_t r0;
41
volatile uint32_t r1;
42
volatile uint32_t r2;
43
volatile uint32_t r3;
44
volatile uint32_t r12;
45
volatile uint32_t lr; /* Link register. */
46
volatile uint32_t pc; /* Program counter. */
47
volatile uint32_t psr;/* Program status register. */
48
49
    r0 = pulFaultStackAddress[ 0 ];
50
    r1 = pulFaultStackAddress[ 1 ];
51
    r2 = pulFaultStackAddress[ 2 ];
52
    r3 = pulFaultStackAddress[ 3 ];
53
54
    r12 = pulFaultStackAddress[ 4 ];
55
    lr = pulFaultStackAddress[ 5 ];
56
    pc = pulFaultStackAddress[ 6 ];
57
    psr = pulFaultStackAddress[ 7 ];
58
59
    /* When the following line is hit, the variables contain the register values. */
60
    for( ;; );
61
}
Quelle: 
http://www.freertos.org/Debugging-Hard-Faults-On-Cortex-M-Microcontrollers.html

Assembler ist jedoch nicht meine Welt und meine IAR Entwicklungsumgebung 
kommt mit dem Assembler-Code nicht klar.
U.a. kommt dieser Compiler-Fehler:
 Error[Og005]: Unknown symbol in inline assembly: ".word"

Was muss ich tun, damit ich den Code zum laufen bekomme?
Oder noch besser: Was für Möglichkeiten bestehen die Ursache zu finden?

Danke und Grüße
Jogi

von Jan B. (berge)


Lesenswert?

Hi,

ich habe solche Handler mal implementiert, so richtig viel bringen sie 
aber meiner Meinung nach nicht (außer man speichert in einem autonomen 
Gerät z.B. Dinge im Flash etc. und schickt dann nach einem Reboot 
Fehlercodes an einen Server).

Eins der Register enthält die Adresse, die vor dem Handler ausgeführt 
wurde. Das bringt dich dem Problem dann schon mal näher denke ich :)

LG Jan

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Ich habe mir spezielle Makros geschrieben:

Bei Aufruf einer Funktion wird ein Makro aufgerufen das die PC Adresse 
in ein Array mit 16 Elementen speichert und die Position im Array weiter 
setzt.

Nun im Hard-Fault Interrupt gebe ich dieses Array auf eine serielle 
Schnittstelle aus und kann so nachvollziehen welche Funktionen zu letzt 
aufgerufen werden.
Bei verschachtelten Funktionen muss man sich den Rücksprung auch merken.

Somit hat man ein Array mit den PC Adressen und kann anhand von der MAP 
Datei herausfinden welche Funktionen denn so alles aufgerufen wurden.

In das Array kann man natürlich auch noch andere Variablenwerte usw. mit 
speichern.
Das hat mit schon manchmal geholfen.

Nachteil: das muss für jede Funktion manuell implementiert werden. Wenn 
es jedoch einmal drin ist, dann ist es ein nettes Feature.

Der Aufruf muss ein Makro sein, damit man das mit einer Leerdefinition 
aus der fertigen Applikation wieder raus bekommt. Denn man möchte die 
CPU mit solchen Debug-Sachen nicht ausbremsen.

von Lothar (Gast)


Lesenswert?

In IAR (aber auch generell) ist es einfacher, den Assembler-Teil in ein 
separates Assembler-File zu tun, und dann C-Code als extern aufzurufen.

Hier mal als Beispiel SVC-Handler, hier wird die SVC-Nummer vom Stack 
geholt (HardFault geht aber genau so).

C-File:

void __kernel_delay_handler(unsigned long val)
{
  while (val!=0)
    val--;
}

Assembler-File:

        MODULE  ?handler

        PUBLIC  SVC_Handler

        #define __kernel_delay_code     0x33
        ...

        EXTERN  __kernel_delay_handler
        ...

        SECTION .text:CODE(2)
        CODE

SVC_Handler

        ; stack contains: r0, r1, r2, r3, r12, r14, previous PC, xPSR
        ; keep r0, r1, r2, r3 for kernel calls

        TST LR, #4                      ; kernel mode?
        ITE EQ
        MRSEQ R12, MSP                  ; kernel stack
        MRSNE R12, PSP                  ; user stack

        LDR R12, [R12, #6*4]            ; previous PC
        LDRB R12, [R12, #-2]            ; previous 16-bit instruction 
low-byte is svc number

        CMPS R12, #__kernel_delay_code
        BEQ __kernel_delay_handler
        ...

        BX LR                           ; PC=LR (return)

        END

Beim HardFault ist noch zu berücksichtigen, dass meistens der user stack 
overrun hatte (Hauptgrund für HardFault).

von faulty (Gast)


Lesenswert?

Meiner Erfahrung nach werden wirklich sehr oft falsche Zugriffe über 
Pointer durch diesen Handler abgefangen. Sieh mal nach deinen 
Variableninitialisierungen, ob du z.B. einen Pointer benutzt, ohne ihn 
vorher initialisiert zu haben oder auf "0" initialisiert hast usw. Ich 
durfte da in letzter Zeit recht viel Erfahrung sammeln und es lag 
wirklich immer an diesen Stellen.

von Jogi (Gast)


Lesenswert?

Hallo!

Danke für Eure Rückmeldung!
Also im Moment hänge ich noch mit dem Debugger dran.
Aber im Callstack sehe ich halt nicht, was die Ursache für den Interrupt 
ist ...

Wenn ihr eine Debug-Lösung zur Laufzeit mit dem Debugger habt, bin ich 
dafür auch dankbar!

Viele Grüße
Jogi

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Die Lösung: Manuell programmieren, wie oben gezeigt.

von Jogi (Gast)


Lesenswert?

Ja, nur ist das Beispiel ebenfalls viel Assembler mit einigen "...".
Und wie ich schon im Eingangsbeitrag geschrieben habe, verfüge ich über 
keine Assembler-Kenntnisse. Somit steht für mich da nur wirres Zeug.... 
:-O

von Lothar (Gast)


Lesenswert?

Jogi schrieb:
> viel Assembler mit einigen "..."

Die kannst Du einfach weglassen, das sollte nur anzeigen dass in einem 
SVC Handler mehrere Aufrufe drin sind, aber für HardFault braucht es ja 
nur den einen, in deinem Beispiel genannt:

void prvGetRegistersFromStack( uint32_t *pulFaultStackAddress )

und dementsprechend:

PUBLIC  HardFault_Handler

EXTERN  __prvGetRegistersFromStack

HardFault_Handler

tst lr, #4
ite eq
mrseq r0, msp
mrsne r0, psp
ldr r1, [r0, #24]
b __prvGetRegistersFromStack

end

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Bei mir sieht das eher so aus:
1
extern volatile unsigned long iPC[16];
2
extern volatile unsigned char iPCPos;
3
extern volatile unsigned char iPCEnable;
4
5
#define SAVE_PC() {if(iPCEnable){unsigned long t;iPCPos=(iPCPos+1)%(sizeof(iPC)/4);asm volatile("mov %0, r15" : "=r"(t));iPC[iPCPos]=t;}}

ich habe auch nicht wirklich Ahnung von Assembler, nur den einen Befehl 
habe ich mir aus der CMSIS abgeschaut.

Außerdem meinte ich MEIN oberes Posting. Das Makro SAVE_PC() ist bei 
jeder Funktion zu Anfang ein zu programmieren um einen Trace zu 
erhalten.

von trace (Gast)


Lesenswert?

Hmmm, mit dem Debugger? Auf dem Stack muss doch was zu finden sein. Beim 
ISR Aufruf werden vorher Register und Rücksprungadresse gesichert.

von Lothar (Gast)


Lesenswert?

trace schrieb:
> Beim ISR Aufruf werden vorher Register und Rücksprungadresse gesichert.

Wenn der Stack da noch nicht korrumpiert ist, ansonsten eben 
HardFault_Handler bzw. MemManage_Handler

Oder gleich das Hauptprogramm im UserMode und die ISRs im SupervisorMode 
laufen lassen, mit verschiedenen Stacks.

von trace (Gast)


Lesenswert?

Was ist denn der Hardfault? Das ist ein ganz normaler Iterrupt, nur mit 
einer hohen Priorität. Also Stack auswerten!!!

Der häufigste Fehler sind fehlende ISR bei freigegebenen Interrupts.

von Lothar (Gast)


Lesenswert?

trace schrieb:
> Der häufigste Fehler sind fehlende ISR bei freigegebenen Interrupts.

Falsch, wenn es eine ISR nicht gibt, gibt das keinen HardFault. Im IAR 
Startup-Code sind nämlich "Dummies" definiert:

Beispiel:

        PUBWEAK TMR0_IRQHandler
        SECTION .text:CODE:REORDER(1)
TMR0_IRQHandler
        B TMR0_IRQHandler

Das hierfür geeignete Tool ist der Watchdog.

trace schrieb:
> Was ist denn der Hardfault? Das ist ein ganz normaler Iterrupt, nur mit
> einer hohen Priorität. Also Stack auswerten!!!

Das mag schon sein, aber nochmals, wenn z.B. be re-entranter ISR der 
Stack überläuft, dann hat der HardFault-Handler keinen Stack zum 
auswerten ...

von Frank B. (f-baer)


Lesenswert?

faulty schrieb:
> Meiner Erfahrung nach werden wirklich sehr oft falsche Zugriffe über
> Pointer durch diesen Handler abgefangen. Sieh mal nach deinen
> Variableninitialisierungen, ob du z.B. einen Pointer benutzt, ohne ihn
> vorher initialisiert zu haben oder auf "0" initialisiert hast usw. Ich
> durfte da in letzter Zeit recht viel Erfahrung sammeln und es lag
> wirklich immer an diesen Stellen.

Das ist auch meine Erfahrung. Schreibzugriffe mittels Pointervariablen 
auf Adressen außerhalb von Registern, RAM und Flash lösen den Hardfault 
aus. Andere Gründe wären mir nicht bekannt.

von Lothar (Gast)


Lesenswert?

Frank Bär schrieb:
> Schreibzugriffe mittels Pointervariablen
> auf Adressen außerhalb von Registern, RAM und Flash lösen den Hardfault
> aus.

Das könnte man allerdings noch dadurch unterscheidbar machen, dass man 
mit der MPU eben die Bereiche wo physikalisch nichts ist, sperrt und 
einen Dummy-MemManage_Handler macht, um zu sehen, ob da reingesprungen 
wird.

von trace (Gast)


Lesenswert?

@Lothar
Falsch, das ist keine Definition. Hier wird nur die 
Interruptvektortabelle erzeugt. Code für eine ISR sehe ich in deinem 
Beispiel nicht.

Wenn der stack überläuft, überschreibt er etwas. Er ist aber noch 
vorhanden. Also gilt immer noch: Stack auswerten!

von John-eric K. (mockup)


Lesenswert?

Ich habe den hier integriert:
http://blog.frankvh.com/2011/12/07/cortex-m3-m4-hard-fault-handler/

Bei mir waren es bisher aber auch immer Pointer-Fehler

von Lothar (Gast)


Lesenswert?

trace schrieb:
> Falsch, das ist keine Definition. Hier wird nur die
> Interruptvektortabelle erzeugt. Code für eine ISR sehe ich in deinem
> Beispiel nicht.

Der STM32 ist ein Cortex-M, da besteht die Interruptvektortabelle aus 
Adressen, nicht aus Branches (wie bei Cortex-A):

__vector_table_0x1c
...
        DCD     TMR0_IRQHandler

Lothar schrieb:
> TMR0_IRQHandler
>         B TMR0_IRQHandler

Das ist tatsächlich die Default ISR für Timer 0, Label und 
Endlossschleife. Erst wenn woanders eine Prozedur mit gleichem Namen 
definiert wird, wird diese genommen (PUBWEAK->PUBLIC) z.B.

void TMR0_IRQHandler(void)
{
  FIO1PIN_bit.P1_25 ^= 1;       // toggle LED

  T0IR_bit.MR0INT = 1;          // timer 0 irq clear pending
  CLRPEND0 |= 1<<(TMR0_IRQ);    // timer 0 irq clear pending (NVIC)
}

trace schrieb:
> Wenn der stack überläuft, überschreibt er etwas. Er ist aber noch
> vorhanden. Also gilt immer noch: Stack auswerten!

Der HardFault kommt aber nicht beim "etwas" überschreiben sondern erst 
wenn der physikalische Speicher zu Ende ist (z.B. ein 16K RAM Block).

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

My 5 cents: AFAIR ist bei meinen ersten Versuchen mit trickreichen 
DMA-Zugriffen mein STM32F4 regelmäßig über ein Bus-Faul im Hard-Fault 
gelandet.

von Lothar (Gast)


Lesenswert?

Marcus H. schrieb:
> Bus-Fault im Hard-Fault

Es gibt aber auch eine BusFault-Exception, die landet nur dann im 
HardFault_Handler wenn man keinen BusFault_Handler gemacht hat:

        DCD     NMI_Handler                 ; NMI Handler
        DCD     HardFault_Handler           ; Hard Fault Handler
        DCD     MemManage_Handler           ; MPU Fault Handler
        DCD     BusFault_Handler            ; Bus Fault Handler
        DCD     UsageFault_Handler          ; Usage Fault Handler

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Hi Lothar, danke für die Rechtschreibkorrektur. :)
Marcus H. schrieb:
> über ein Bus-Fault im Hard-Fault
Genau darauf wollte ich raus, ein Hinweis zur Fehlersuche - man landet 
über Zwischenstationen im finalen Handler und fragt sich dann wie das 
passieren konnte. Also schaut man sich die Rücksprungadressen und die 
CPU/Peripherie-Statusregister an.

von Steffen R. (steffen_rose)


Lesenswert?

Auch wenn es nur wenige Unterschiede gibt, wäre die genaue Cortex-M 
Familie interessant.

Ich gehe auch mal davon aus, dass alle Fault-Handler zumindest als 
Endlosschleife implementiert wurden. Ansonsten wird es mühselig.

Wenn man sich mit dem FaultHandler beschäftigt, ist das Kapitel "Fault 
Handling" im jeweiligen Cortex-Handbuch anzuraten. Die sind auf der ST 
Seite mit verlinkt. Ansonsten stochert man nur im Trüben.

Hier findet man auch Infos, wie man herausbekommt, welche Ursache der 
Hard Fault genau hatte. (z.B. Lese/Schreibzugriff oder Sprung ins 
Nirwana). Dann weiß man schonmal weiter, wonach man sucht.

Wie auch schon geschrieben, findet man die PC Adresse, die den Fehler 
ausgelöst hat. Wenn man sich den Assemblerbefehl anschaut, kann man auch 
herausbekommen, welches Register noch relevant ist.

Die vorgenannten Dinge, wie Stacküberlauf und wenigstens Defaulthandler 
sollte man sicherstellen. Ansonsten hat man keinen Spaß beim Suchen. 
Ohne diese Ursachen kann man wenisgtens herausbekommen, welcher Code den 
Fehler verursacht hat und vorher Debugmöglichkeiten einbauen (Breakpoint 
oder Ausgaben).

Weiß man, was der Auslöser war, kann man häufig per Watchpoint auch den 
Verursacher fangen.

Grundkenntnisse Assemblercode wären gut, um zumindest den Code lesen zu 
können. Ohne Assembler in diesen Tiefen vorzudringen ist schierig.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.