/* 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
volatileuint32_tr0;
41
volatileuint32_tr1;
42
volatileuint32_tr2;
43
volatileuint32_tr3;
44
volatileuint32_tr12;
45
volatileuint32_tlr;/* Link register. */
46
volatileuint32_tpc;/* Program counter. */
47
volatileuint32_tpsr;/* 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
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
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.
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).
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.
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
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
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
#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.
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.
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.
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 ...
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.
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.
@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!
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).
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
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.
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.