Hallo Forum, ich sitze hier gerade an einem Cortex-M4, aber die Frage ist mehr oder weniger für alle Cortex-Ms gültig. Ich habe die Usage-, Bus- und MMU-Faults aktiviert (sonst würde der Prozessor bei einem solchen Fault im Hardfaulthandler landen), aber ich frage mich jz was man in den entsprechenden Handlern sinnvolles tun kann. Beim Hardfault ist es ja mehr oder weniger klar: ein schwerwiegender Fehler ist aufgetreten: eigentlich kann man nur while(1) {} machen und dann mit dem Debugger analysieren. Wenn man dann einfach mit dem Programm weitermachen würde, dann würde sonst was passieren. Bei der MMU-Exception ist es aber schon interessanter: was macht man da sinnvollerweise? Nur irgenwie mitloggen? (Wobei dieser Fall bei mir bisher noch keine Rolle spielt, weil ich die MMU noch nicht nutze) Ich habe auch die div0-Exception aktiviert, und die landet dann, neben Unaligned Access und falschem Coprocessor Access, im UsageFault-Handler. Was sollte man dort jetzt machen? - das Div0 würde ich einfach loggen und dann versuchen mit dem Programm weitermachen - Unalinged Access: eigentlich kann man doch fast nur seinen Compiler bemängeln oder schauen, was man für Gemeinheiten im Code macht - nicht vorhandenen Coprozessor benutzt: ? BusFault: ? ------- Unterm Strich sehen alle meine Exceptionhandler relativ gleich aus: nämlich nur eine Endlosschleife. Daher meine Frage: Was sollte man wann sinnvolles tun? Und nach welcher Exception kann man noch mit einem funktionierenden Programm rechnen? Mit freundlichen Grüßen, N.G. PS: zur Verfügung stehen unter anderem: - Logfile auf SD-Karte - UART - LEDs
N. G. schrieb: > Daher meine Frage: > Was sollte man wann sinnvolles tun? Du hast in deinen Betrachtungen zu allererst nur an den Debugger gedacht. Aber das ist nicht die Einsatz-Umgebung deines Controllers. Im realen Controller-Leben steht der arme Kerl nämlich allein und einsam auf weiter Flur da und bei irgend einer Störung, die da eventuell mal hereinrauscht und ihn aus der Bahn wirft, einfach nur per "B ." in sich zu gehen und reinweg garnichts mehr zu tun, alte ich für die dümmste aller Varianten. Ich halte viel mehr davon, in solchen Fällen einfach einen gezielten Restart zu veranlassen. Nee, nicht mit dem Holzhammer a la Watchdog, sondern mit einem Fehlercode durch den Kaltstart hindurch, so daß die eigentliche Firmware eine Chance hat, anhand dieses Fehlercodes den Grund ihres erneuten Aufstartens zu berücksichtigen (falls das aus Sicherheitsgründen nötig ist). W.S.
N. G. schrieb: > (Wobei dieser Fall bei mir > bisher noch keine Rolle spielt, weil ich die MMU noch nicht nutze) Welcher Cortex-M4 hat denn eine MMU o.O Die Fault-Handler deuten ja eigentlich immer auf fehlerhaften Code hin. Den kann dein Programm ja schlecht beheben. Das gilt auch für Division durch Null - einfach mit "irgendeinem" Ergebnis weiterrechnen kann ja eigentlich auch nur in einer Katastrophe enden. Einzig sinnvoll wäre es den Controller zu resetten oder anzuhalten, und ggf. den Fehler zu loggen oder in Endlosschleife per UART o.ä. rauszuschicken.
Hallo, W.S. schrieb: > N. G. schrieb: >> Daher meine Frage: >> Was sollte man wann sinnvolles tun? > > Du hast in deinen Betrachtungen zu allererst nur an den Debugger > gedacht. Da hast du durchaus recht. > Aber das ist nicht die Einsatz-Umgebung deines Controllers. Im > realen Controller-Leben steht der arme Kerl nämlich allein und einsam > auf weiter Flur da und bei irgend einer Störung, die da eventuell mal > hereinrauscht und ihn aus der Bahn wirft, einfach nur per "B ." in sich > zu gehen und reinweg garnichts mehr zu tun, alte ich für die dümmste > aller Varianten. Naja, vor dem while(1) gibts natürlich noch eine Error-Ausgabe mittels LED o.Ä. > Ich halte viel mehr davon, in solchen Fällen einfach einen gezielten > Restart zu veranlassen. dann läuft die Firmware ja irgendwann wieder in den Fault. Und am schlimmsten ist: ich bekomme es als Programmierer wenns ganz schlecht läuft nicht einmal mit! > Nee, nicht mit dem Holzhammer a la Watchdog, sondern mit einem > Fehlercode durch den Kaltstart hindurch, so daß die eigentliche Firmware > eine Chance hat, anhand dieses Fehlercodes den Grund ihres erneuten > Aufstartens zu berücksichtigen (falls das aus Sicherheitsgründen nötig > ist). Das ist mehr oder weniger Geschmackssache. Hallo, Dr. Sommer schrieb: > N. G. schrieb: >> (Wobei dieser Fall bei mir >> bisher noch keine Rolle spielt, weil ich die MMU noch nicht nutze) > > Welcher Cortex-M4 hat denn eine MMU o.O Uuuups, MPU war gemeint... Asche auf mein Haupt. > Die Fault-Handler deuten ja eigentlich immer auf fehlerhaften Code hin. > Den kann dein Programm ja schlecht beheben. Genau. Aber irgendwas muss man ja machen, damit der fehlerhafte Code behoben werden kann. > Das gilt auch für Division > durch Null - einfach mit "irgendeinem" Ergebnis weiterrechnen kann ja > eigentlich auch nur in einer Katastrophe enden. Naja, wenn man die Exception nicht aktiverit, rechnet der Core sowieso mit 0 als Quotient weiter. Wobei z.B. Unendlich mehr Sinn machen würde. Das wäre evtl. auch mein Vorgehen. > Einzig sinnvoll wäre es > den Controller zu resetten oder anzuhalten, und ggf. den Fehler zu > loggen oder in Endlosschleife per UART o.ä. rauszuschicken. Okay, so hab ich mir das fast gedacht. Dazu aber noch eine Frage: Kann man bei jeder Exception gefahrlos C-Code schreiben? Ich würde aus dem Bauch raus behaupten: Nein. Danke aber schonmal an euch
N. G. schrieb: > Dazu aber noch eine Frage: Kann man bei jeder Exception gefahrlos C-Code > schreiben? Ich würde aus dem Bauch raus behaupten: Nein. Naja, du solltest dich natürlich nicht auf den Zustand des Programms verlassen, also a priori annehmen dass in allen Variablen Unfug steht (deswegen wird auch das Loggen auf die SD-Karte schwierig - deren Ansteuerung inkl. Dateisystem ist ja sehr komplex, es ist nicht gerade wahrscheinlich dass genau das noch perfekt funktioniert). Das Hauptproblem ist dass der Stack übergelaufen/voll sein könnte, und daher lokale Variablen nicht funktionieren könnten. Daher wäre es vermutlich sinnvoll, den Stack bei Betreten des Handlers zu resetten. Danach solltest du aber normalen C-Code schreiben können, verlass dich aber besser nicht auf den Status des Programms. Am besten initialisierst du auch den UART neu, bevor du über den was rausschickst o.ä. N. G. schrieb: > Naja, wenn man die Exception nicht aktiverit, rechnet der Core sowieso > mit 0 als Quotient weiter Und das ist garantiert sowieso völlig falsch ;-) N. G. schrieb: > Wobei z.B. Unendlich mehr Sinn machen würde. > Das wäre evtl. auch mein Vorgehen. Geht ja auch nur bei float, und auch dann wird die unendlich vermutlich zu verkehrten/komischen Ergebnissen am Ende führen (NaN oder inf). Daher halte ich auch das für wenig sinnvoll und würde bei Division durch Null immer anhalten. Andersherum gefragt: Welcher Algorithmus funktioniert noch richtig, der eine Division durch Null durchführt und Unendlich als Ergebnis erhält? Ist ein Algorithmus, der durch Null teilt, nicht grundsätzlich fehlerhaft und in dem Fall nicht zu gebrauchen? Onehin sollte man (gerade auf schwer debuggbaren Embedded Systemen) bei Divisionen vorher immer auf 0 prüfen, genauso wie man bei Addition/Subtraktion vorher auf Überlauf prüfen sollte. Dann kann man direkt gescheit reagieren, anstelle im Error Handler ein Ergebnis zu "raten" und zu hoffen, dass alle betroffenen Algorithmen damit klar kommen.
N. G. schrieb: > dann läuft die Firmware ja irgendwann wieder in den Fault. Und am > schlimmsten ist: ich bekomme es als Programmierer wenns ganz schlecht > läuft nicht einmal mit! Nö. In den allermeisten Fällen passieren solche Abstürze nicht durch falsche Programmierung, sondern durch äußere Einflüsse - und sowas ist entweder nicht zyklisch oder es ist ohnehin nichts mehr zu machen. Im ersteren Fall rappelt sich der µC eben wieder hoch und tut seinen Dienst wieder. Im zweiten Fall geht ohnehin nichts mehr, z.B. ständiger Brownout oder so, dann ist an ein Weiterarbeiten ohnehin nicht mehr zudenken. Aber die ersteren Fälle sind damit erledigt, das System ist robuster als mit bloßem Stehenbleiben. Und als Programmierer kriegst du sowas allemal mit, wenn du das willst. W.S.
W.S. schrieb: > Nö. In den allermeisten Fällen passieren solche Abstürze nicht durch > falsche Programmierung, sondern durch äußere Einflüsse Woher nimmst du denn diese Statistik? Ich benutze z.B. STM32 in einem doch sehr störverseuchten Umfeld (ja, eigentlich nicht die optimale Wahl), und bisher war jeder Absturz ein Programmierfehler. Unter anderem verursacht durch Kollegen, die fröhlich durch eine Zahl teilen, die per Bus reinkam, ohne zu prüfen ob sie 0 sein könnte... Externe Einflüsse wirken sich doch wesentlich subtiler aus (leicht abweichende Zahlen usw.) als direkt freundlicherweise einen sauberen Absturz auszulösen.
Dr. Sommer schrieb: > Andersherum gefragt: Welcher Algorithmus funktioniert noch richtig, der > eine Division durch Null durchführt und Unendlich als Ergebnis erhält? > Ist ein Algorithmus, der durch Null teilt, nicht grundsätzlich > fehlerhaft und in dem Fall nicht zu gebrauchen? Nein. In den allermeisten Fällen kommen Divisionen durch 0 oder extrem kleine Werte aufgrund äußerer Umstände vor, als da wären menschliche Fehler bei Kalibrierungen, Überläufe oder Fehler bei Datenübertragungen, Störungen bei Sensoren usw. Natürlich kann man sowas alles abfangen (if(fabs(x)<1E-15)...), aber sowas ist ne Unannehmlichkeit, weswegen ich es durchaus vorziehen würde, wenn auch bei float die Rechenlogik sättigend funktionieren würde. Bei Filteranwendungen z.B. kann man sich nämlich aus Zeitgründen keine ausufernden Bereichsprüfungen von Argumenten erlauben. W.S.
W.S. schrieb: > Natürlich kann man sowas alles abfangen (if(fabs(x)<1E-15)...), aber > sowas ist ne Unannehmlichkeit, weswegen ich es durchaus vorziehen würde, > wenn auch bei float die Rechenlogik sättigend funktionieren würde. Du willst also Rechenergebnisse, die aus falschen Eingaben aufgrund von Bus-/Mess-/Kalibrier-Fehlern entstanden sind, trotzdem behalten? Na hoffentlich steuerst du keine gefährlichen Maschinen mit solchen falschen Daten! W.S. schrieb: > Bei > Filteranwendungen z.B. kann man sich nämlich aus Zeitgründen keine > ausufernden Bereichsprüfungen von Argumenten erlauben. Wenn du einen Filter hast, der durch eine Eingabe teilt, und bei dem du keinen einzelnen Takt für so etwas opfern kannst, und der völlig isoliert auf dem Controller läuft (damit die Exception nicht andere Algorithmen beeinflusst), und der bei falscher Eingabe plötzlich sehr viel Rechenzeit übrig hat (Exception-Entry & Exit ist nicht gerade gratis), dann kannst du das so machen, aber im Allgemeinen würde ich das doch mindestens als sehr unsauber oder geradezu gefährlich (s.o.) betrachten.
Man kann mit ein wenig Assembler den Exception-Stack untersuchen und da die Programmadresse rauspfriemeln, an der die Exception ausgelöst wurde. SP+24 ist das. Die Routine muß dazu natürlich in Assembler sein und kann auch gleich noch alle Interrupts abschalten. Sobald man Absturzadresse ermittelt hat, wirft man diese Daten in R0 und brancht dann zur C-Routine für die weitere Verarbeitung. Diese bekommt einen uint32_t als Parameter und sollte als "used" markiert werden, weil der Compiler deren Aufruf aus dem Inline-Assembler nicht unbedingt "sieht". Das Auslesen des fault registers kann diese Routine dann auch in C erledigen. Dann weiß man, warum die Software wo abgestürzt ist. Das Gemeine an der Stelle ist: man kann nur entweder auf stack overflow reagieren oder für die anderen exceptions die Adresse rauskriegen. Beides geht nicht. Denn wenn man wegen stack overflow im hard fault landen kann, muß man die Routine als naked definieren, weil das Pushen des exception stacks direkt WIEDER einen stack overflow auslösen würde. Aber stack overflow ist per Analyse ausschließbar. Hierzu muß man sich den call tree des Programmes analysieren. Die Daten kriegt man bei GCC mit der Compiler-Option -fstack-usage in .su-Files als text ausgegeben. Irgendwo habe ich dazu auch mal Auswertescripte gesehen. Keil wirft einem diese Daten gleich sinnvoll aufbereitet raus. Also worst case für den call tree der Applikation annehmen (bzw. aller Tasks zugleich) und dann davon ausgehen, daß sämtliche Interrupts genau in dem Moment AUCH noch auflaufen. Das ist dann der Stackbedarf, den man dimensionieren muß. Vielleicht noch ein kB draufschlagen, nur zur Sicherheit. Das sollte man sowieso machen, weil es leichter ist, Fehler auszuschließen, als sie zu debuggen. Bei allen anderen Exceptions weiß man dann, daß zumindest der Stack noch geht. Systemtimer geht nicht mehr, andere Interrupts auch nicht mehr. Aber Delay-Schleifen via DWT gehen noch, ebenso wie IO im Polling-Modus. Das reicht, um z.B. Sachen auf einem Display oder UART im Polling-Modus auszugeben. Was außerdem noch geht, ist der Zugriff aufs Backup-RAM und die RTC-Backup-Register. Hier kann man Systemzeit, Systemfehler (Inhalt des fault registers) und die ermittelte Absturzadresse speichern. Wenn man richtig gründlich ist, bildet man irgendeine Art von Checksumme über diese Fehlerdaten und speichert die auch noch mit ab. Das schließt aus, daß man beim Hochfahren Datenmüll irrtümlich als Fehler einliest. Dann Reset via Watchdog. Beim Hochfahren guckt man an genau diese Stellen im Backup-RAM bzw. den Backup-Registern, ob da was steht. Wenn ja, gibt man das aus, loggt es, was weiß ich. Das System funktioniert ja dank Reboot erstmal wieder, auch Dateisysteme sind damit nutzbar. Dann setzt man diese Speicherstellen wieder zurück.
N. G. schrieb: > Beim Hardfault ist es ja mehr oder weniger klar: ein schwerwiegender > Fehler ist aufgetreten: eigentlich kann man nur while(1) {} machen und > dann mit dem Debugger analysieren. Wenn man dann einfach mit dem > Programm weitermachen würde, dann würde sonst was passieren. Der Motor dreht gerade schnell, dann kommt ein kosmischer Strahl (ungültige Eingabe, Programmfehler) und haut ihm ein Bit weg, der Controller geht in eine Endlosschleife und blinkt eine LED. Bremspedal, Aufprallsensoren, Airbags, alles egal, ich sage ja, dass ich gerade kaputt bin. Wenn es so sehr knallt, dass du dein Programm nicht mehr sinnvoll fortsetzen kannst, dann musst du möglichst schnell einen sicheren Zustand herstellen. Wie der aussieht und wie schnell er da sein muss, hängt vom Anwendungsfall ab.
W.S. schrieb: > Nein. In den allermeisten Fällen kommen Divisionen durch 0 oder extrem > kleine Werte aufgrund äußerer Umstände vor Falsch. Das sind schlichtweg Softwarefehler. > als da wären menschliche Fehler bei Kalibrierungen Kalibrierdaten validiert man beim Hochfahren. Was passiert, wenn man das nicht tut, konnte man letztes Jahr in Spanien beobachten, als ein A400M abgestürzt ist, mit etlichen Toten. > Überläufe oder Fehler bei Datenübertragungen, Einkommende Daten validiert man ebenfalls. Datenübertragungen sichert man. > Störungen bei Sensoren usw. Auch diese muß man auf gültige Wertebereiche prüfen. Allein schon, weil Kabelbruch den Sensor out of range werfen kann. > Natürlich kann man sowas alles abfangen Das sollte man auch. > Bei > Filteranwendungen z.B. kann man sich nämlich aus Zeitgründen keine > ausufernden Bereichsprüfungen von Argumenten erlauben. Die prüft man vorher. Ggf. kann man seine Filter auch auf dem PC simulieren, um deren Stabilität sicherzustellen. Ganz besonders, wenn man Filter verwendet, die Divisionen vorsehen, typischerweise IIR-Filter. Die haben IMMER das Problem, daß sie instabil werden könnten, und da hat man zu prüfen, ob die Polstellen wirklich allesamt in der linken Hälfte der Z-Ebene liegen. Aber einfach nur nichts abprüfen und ggf. einen Absturz zu riskieren, ist einfach grober Pfusch in der Software. Der einzige "äußere Umstand", der dafür verantwortlich ist, ist der Programmierer.
S. R. schrieb: > Der Motor dreht gerade schnell, dann kommt ein kosmischer Strahl > (ungültige Eingabe, Programmfehler) und haut ihm ein Bit weg Bei wirklich kritischen Maschinen ist es an sich schon ein Designfehler im System, wenn nur ein Controller die Sache regelt.
Hallo Nop, vielen Dank für deine kompetente Erklärung, ich werde das so ähnlich umsetzen. Leider hat mein µC kein Backup-RAM o.Ä., aber es findet sich ja immer ein Weg ;) Hallo, S. R. schrieb: > N. G. schrieb: >> Beim Hardfault ist es ja mehr oder weniger klar: ein schwerwiegender >> Fehler ist aufgetreten: eigentlich kann man nur while(1) {} machen und >> dann mit dem Debugger analysieren. Wenn man dann einfach mit dem >> Programm weitermachen würde, dann würde sonst was passieren. > > Der Motor dreht gerade schnell, dann kommt ein kosmischer Strahl > (ungültige Eingabe, Programmfehler) und haut ihm ein Bit weg, der > Controller geht in eine Endlosschleife und blinkt eine LED. Bremspedal, > Aufprallsensoren, Airbags, alles egal, ich sage ja, dass ich gerade > kaputt bin. > > Wenn es so sehr knallt, dass du dein Programm nicht mehr sinnvoll > fortsetzen kannst, dann musst du möglichst schnell einen sicheren > Zustand herstellen. Wie der aussieht und wie schnell er da sein muss, > hängt vom Anwendungsfall ab. Du hast natürlich recht, allerdings spielt das in meiner Anwendung keine Rolle, da sie nichts Sicherheitskritisches enthält. Ganz nebenbei ist immer ein Mensch dabei, der im Fehlerfall reagieren kann. Alles in allem kann man wohl mal wieder sagen: "Es kommt darauf an". Je nach Anwendung muss man im Fehlerfall anders reagieren. Danke an alle. N.G.
N. G. schrieb: > vielen Dank für deine kompetente Erklärung, ich werde das so ähnlich > umsetzen. Leider hat mein µC kein Backup-RAM o.Ä., aber es findet sich > ja immer ein Weg ;) Klar :-) Du kannst auch einen Teil des RAM als Fehlerspeicher reservieren, einfach mit einem kleinen struct im BSS-Segment, also als globale Variable (mit volatile!). Dann aber wirklich mit Checksumme. Wenn Du jetzt beim Hochfahren den Assembler-Startup-Code etwas modifizierst (also wo das RAM genullt wird), kannste den Inhalt des structs in drei Register laden (Adresse, Fault, Checksumme), dann das RAM initialisieren und dann wieder ins struct zurückladen. Das struct kannste dann im C-Code auswerten. So geht der beschriebene Mechanismus auch ohne Backup-RAM. Übrigens, die Programmadresse kann man leicht mit dem Mapfile abgleichen und weiß, in welcher Funktion das passiert sein muß. Kann fies werden, wenn man dem Compiler das Inlinen von Funktionen erlaubt, weil man dann nicht mehr so einfach nachvollziehen kann, zu welcher Funktion die Adresse gerade gehört. Für Debugzwecke daher lieber mit -fno-inline arbeiten. Die "bequeme" Lösung ist natürlich, das Programm mit dem Debugger reinzuladen und vor dem Loslaufen an die Programmadresse zu scrollen, an der der Absturz war. Dann sieht man (sofern kein Inlining) sehr direkt, wo das im Quelltext war.
Nop schrieb: >> Der Motor dreht gerade schnell, dann kommt ein kosmischer Strahl >> (ungültige Eingabe, Programmfehler) und haut ihm ein Bit weg > > Bei wirklich kritischen Maschinen ist es an sich schon ein Designfehler > im System, wenn nur ein Controller die Sache regelt. Und kein wahrer Schotte würde das je so entwerfen, ne? :-) (Kontext: https://de.wikipedia.org/wiki/Kein_wahrer_Schotte) Nimm den gewöhnlichen billigchinesischen Solarcontroller: Leistungstransistor stirbt mit Kurzschluss, Controller bekommt unplausible Werte, wirft Exception und zeigt statt "Not-Aus" nur eine Fehler-LED. Nach ein paar Minuten brennt die Bude. Auch doof.
S. R. schrieb: > Und kein wahrer Schotte würde das je so entwerfen, ne? :-) Moment, daß es ein Designfehler ist, heißt ja nicht, daß er nicht begangen würde - sogar da, wo es dann Ärger gibt. Siehe den Skandal um den Toyota Camry, dessen Software ein einziger Haufen Dreck war, und wo deswegen auch Leute gestorben sind. Da das allerdings in den USA war und nicht in Europa, wurde das dementsprechend teuer für Toyota.
Du hast zwischen "kritischen Maschinen" und "wirklich kritischen Maschinen" gerade nach diesem Designkriterium unterschieden. Diese Unterscheidung ist Unsinn, wie du ja gerade selbst geschrieben hast. Nur darauf wollte ich dich hinweisen. Wir sind uns einig, dass die beste (sinnvollste) Lösung vom konkreten Anwendungsfall abhängt.
S. R. schrieb: > Du hast zwischen "kritischen Maschinen" und "wirklich kritischen > Maschinen" gerade nach diesem Designkriterium unterschieden. Ah jetzt ja, das war natürlich redundant, stimmt. :-)
Also nochmal ein großes Danke an alle, die sich an der Diskussion beteiligt haben! Ich weiß, dass diese Frage sehr allgemein Formuliert war, und es deswegen leicht sehr gegensätzliche Meinungen geben kann. Bei einem konkreten Problem melde ich mich wieder (oder der schönere Fall: ich melde mich nicht wieder, es gibt schließlich keine Probleme ;-) ) Mit freundlichen Grüßen, N.G.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.