Deutet folgende Befehlsfolge auf ein Problem im Bereich Stack Pointer und Interrupts beim AVR-GCC hin ? Vorweg eine Beschreibung ... Die Compilerausgabedatei xxx.lss enthält zusätzlich zum Projekt-C-Source den übersetzten Assemblercode. Es folgt ein kleiner Auszug daraus: a1c: 2d b7 in r18, 0x3d ; 61 a1e: 3e b7 in r19, 0x3e ; 62 a20: 24 5f subi r18, 0xF4 ; 244 a22: 3f 4f sbci r19, 0xFF ; 255 a24: 0f b6 in r0, 0x3f ; 63 a26: f8 94 cli a28: 3e bf out 0x3e, r19 ; 62 a2a: 0f be out 0x3f, r0 ; 63 a2c: 2d bf out 0x3d, r18 ; 61 Ich verstehe das so: a1c: // Stack Pointer (Low Byte) lesen und im r18 speichern !!! Hier darf ein Interruptaufruf dazwischen kommen !!! a1e: // Stack Pointer (HighByte) lesen und im r19 speichern a20: // vom gelesenen Stack Pointer in r18 ein LowByte subtrahieren a22: // vom gelesenen Stack Pointer in r19 ein HighByte subtrahieren a24: // Status bzw. Flagregister SREG lesen und in r0 speichern (retten) a26: // alle Interrupts sperren (SREG Bit 7) a28: // r19 ist das HighByte des veränderten Stack Pointers und wird hier in den Stack Pointer zurückgeschrieben. a2a: // r0 auf SREG zurückschreiben, um u.a. alten Interruptzustand wieder herzustellen. Der Interrupt kann zugelassen sein. !!! Hier darf aber niemals ein Interruptaufruf dazwischen kommen, weil der Stack Pointer auf eine ungültige Speicheradresse zeigt !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! a2c: // r18 ist das LowByte des veränderten Stack Pointers und wird hier eindeutig zu spät in den Stack Pointer zurückgeschrieben. Wie findet man solche Stellen ? Ganz einfach: 1. Datei xxx.lss mit Editor öffnen. 2. Mit der Suchfunktion (STRG F) nach dem Stichwort cli suchen und weitersuchen. Hinweis: Vergleichbare Befehlsfolgen kommen in der Compilerausgabedatei xxx.lss auch vor, wobei r18 und r19 durch andere Register ersetzt sind. Wenn ich das richtig erkannt habe, taucht die Befehlsfolge bei Berechnungen mit Fliesskomma-Variablen und beim <vfprintf> auf. Die Befehlsfolge von a24: bis a2c: ist typischerweise vorhanden. Die Befehlsfolge von a1c: bis a22: (Stack Pointer Manipulation) kann auch eine andere Berechnung als eine Subtraktion beinhalten. Folgendes Problem sehe ich da: Wenn zwischen Adresse a2a: und a2c: das laufende Programm durch einen Interruptaufruf unterbrochen wird, schreibt der AVR seine Rücksprungadresse auf den Stack und zwar dahin, wo der 16-bit-Stack Pointer hinzeigt. Da im genannten Beispiel der Stack Pointer undefiniert in den Speicher zeigt, - er kann wegen des Carry-Übertrags der Berechnung um fast eine Page (256Byte) daneben liegen - , werden also eventuell gültige Daten von der Rücksprungadresse überschrieben. Der AVR findet zwar nach Abarbeitung der Interruptroutine an die richtige Adresse a2c: zurück, aber die überschriebenen gütigen Daten sind im Speicher verändert und unbrauchbar, weil Variablen, andere Pointer, Rücksprungadressen von Call-Aufrufen (egal - was auch immer) sind verändert. Somit treten in anderen Programmteilen, die diese kaputten Daten benutzen, unvorhersehbare beliebig geartete Folgefehler auf! Wenn meine Theorie stimmt, ist der Absturz im wahrsten Sinne des Wortes vorprogrammiert. Ich weiss leider nicht, wie man dem Compiler das Problem abgewöhnen kann. Deshalb richtet sich dieser Hinweis an die Compiler-Experten und Autoren. Es sollte im Assemblercode besser folgende Reihenfolge eingehalten werden: 1. 16-bit-Stack Pointer komplett lesen 2. 16-bit-Stack Pointer komplett manipulieren, wobei das Ergebnis noch nicht in den Stack Pointer zurückgeschrieben wird. 3. SREG-Zustand lesen (retten) 4. Interrupt sperren 5. 16-bit-Stack Pointer komplett jetzt zurückschreiben 6. geretteten SREG-Zustand ins SREG zurückschreiben, d.h. erst jetzt erfolgt gegebenfalls eine Interruptfreigabe. Mit dieser neuen Reihenfolge ist sichergestellt, dass kein Interrupt mitten in das Setzen einer neuen Stack Pointer Adresse springen kann. Der Interrupt wäre zwar über einige Prozessortaktzyklen länger gesperrt, jedoch hat die Sicherheit der Stack Pointer Definition oberste Priorität. Querverweis: Im AVR-Datenblatt wird der Stackpointer grundsätzlich bei gesperrtem Interrupt gesetzt. ---------------------------------------------------------------------- Gedanklich hier noch vergleichbares anderes Beispiel, was aber kein Compilerfehler, sondern einen denkbaren Programmierfehler in der Anwendung aufzeigen soll, wenn ein Programmierer bei der Verwendung von Interrupts nicht aufpaßt. Ich möchte ergänzend klarstellen, dass meine Anwendung diesen Programmierfehler in C-Source nicht enthält. Wer will, kann das folgende Beispiel als ergänzende Sichtweise zum vorhandenen Thema im AVR-GCC-Tutorial sehen und verwenden. Hier nun das Beispiel1 (richtig programmiert): Wenn der Wert einer Mehrbyte-Variable, -Pointer, z.B. float,long oder int vom Hauptprogramm berechnet wird und vom Interruptprogramm gelesen wird, muss der Interrupt im Hautprogramm während der Variablenzuweisung gesperrt sein, damit der Interrupt die Zuweisung nicht unterbricht. Die Zuweisung einer Mehrbyte-Variable besteht ja aus mehreren Assemblerbefehlen, die einzeln Byte für Byte überschreiben. Das ist auf einem 8bit-µC völlig normal. Hier nun das Beispiel2 (falsch programmiert): Wenn der Interrupt die Zuweisung unterbrechen darf, wäre die Folge, dass ein Byte der Mehrbyte-Variable den alten Wert hat, aber das andere Byte bereits den neuen Wert hat. Numerisch ergibt das völlig einen unsinnigen Variablenwert. Wenn es z.B. den Exponenten einer float-Variable trifft, ist das mindestens eine numerische Katastrophe. Wenn damit das Interruptprogramm dann weiterechnet, passiert in der Anwendung ein unberechenbarer Blödsinn. Das gemeine ist, dass solche Laufzeitfehler selten auftreten, weil der Interrupt ja zeitlich die Befehlsfolge der Mehrbyte-Zuweisung exakt treffen muss. Aber man kann sich zu 100% darauf verlassen, dass der Interrupt irgendwann garantiert ins Schwarze trifft, weil Interrupts asynchron zur Softwarelaufzeit auftreten. Dieses theoretische Beispiel ist gedanklich problemlos auf den Stack Pointer übertragbar. ---------------------------------------------------------------------- Allgemeine Info zu meiner Anwendung: Auf die Stack Pointer Problematik wurde ich aufmerksam, weil gelegentlich - etwa alle 10..15min - ein numerischer Softwareabsturz bei meiner Anwendung auftaucht, den ich mir momentan nicht anders erklären kann. Als Interruptquelle verwende ich einen netzsynchronen externen 100Hz-Interrupt. Zum Softwaredebugging von Variablen verwende ich den printf des Compilers. Es ist zwar mein erstes Projekt auf dem ATMEL AVR. Jedoch habe ich seit 1979 auf den verschiedensten Prozessoren in Assembler und anderen Sprachen programmiert. ---------------------------------------------------------------------- Gruss Dietmar
Du brauchst dazu nicht erst umständliche Disassembler-Listings bemühen, der generierte Compilercode steht dir offen. Außerdem hättest du die Frage ziemlich kurz stellen können. (Sorry, ich habe das Lesen des länglichen Artikels abgebrochen, nachdem ich glaube erkannt zu haben, was dir nicht gefällt -- falls du danach noch mehr Fragen gestellt hast, musst du dich nochmal melden.) Nein, das ist kein Problem. Auch wenn es nirgends dokumentiert ist, hat Atmel dem Compilerschreiber mal irgendwann bestätigt, dass die Interruptfreigabe in der Tat und garantiert erst einen Befehl verspätet erfolgt, d.h. der Befehl direkt nach einer Zuweisung an das SREG, bei dem das I-Bit wieder gesetzt wird, wird selbst bei einem anhängigen Interrupt noch ausgeführt. Ich hoffe das beantwortet deine Frage (außer natürlich der nach deinen sporadischen Softwareabstürzen, aber die musst du wohl oder übel selbst finden ;-).
Hallo Dietmar, ich habe das gleiche Problem wie du, dass meine printfs immer wieder mal gestört werden, und auch manchmal der AVR eine reboot macht. Nun meine Frage: Hast du dein Problem gelöst? War ein SW-Bug von dir, oder hast du den Fehler immer noch? Ich bin auf das gleiche Problem gestossen, und suche noch nach der Lösung. Ein workaround war gestern Abend noch folgendes: Ich habe alle Variable als staisch deklariert, bzw. ausserhalb den C-Funktionen. Dann aht der Compiler keinen Stack manipulieren müssen. Bisher konnte ich keinen Reboot mehr provozieren. Nun bin ich mir nicht sicher, ob mein Programm ein problem hat, oder doch vielelicht manchmal nach dem cli und dem SPL-setzten sich doch nicht ein int dazwischen schummelt. Vermutlich muss ich da mal einen Fang-Code machen, um das genau zu untersuchen. Gruß Erwin
Hallo allerseits, Ich habe das lange Posting jetzt nicht ganz gelesen, jedoch kann ich bestätigen wie Erwin es sagt, dass der AVRGCC bei Verwendung von Strings im allgemeinen (egal ob nun bei printf oder was eigenem) manchmal "fehlerhaften" Code produziert der zum "restart" des AVRs führt. Wahrscheinlich ein Sprung zu einer falschen adresse o.ä. Also die Situation: Zunächst funktioniert das Programm wunderbar - kann durchaus ein größeres Programm sein was bei -os etwa 4kB belegt. Dann fügt man an einer - absolut unkritischen Stelle im Programm, z.B. in der Main Funktion - einen Aufruf wie writeUART_P("ASDF"); hinzu. Die Bedeutung dieser Funktion dürfte klar sein. Die Funktion selbst wird an vielen Stellen im Programm verwendet und funktioniert normalerweise problemlos. Nachdem man diesen einen zusätzlichen Aufruf hinzugefügt hat, funktioniert es aber auf einmal nicht mehr! Das programm stürzt dann schon kurz nach dem Start ab und der AVR springt zum Anfang des Programms zurück! Das Lustige daran ist: Fügt man z.B. direkt nach oder vor diesem neuen Aufruf nur ein einzelnes NOP hinzu - klappts oftmals wieder! Das geht aber nicht immer - machmal muss man mehrere Nops auch ganz wo anders hinzufügen... So und nun kommts: Schaltet man die Optimierung ab - also o0 - funktioniert es immer! Aber egal ob o1, o2, oder os - dann geht es ab und zu nicht! An meinem Code dürfte es also nicht liegen, denn der funktioniert in 99% der Fälle einwandfrei und bei abgeschalteter Optimierung sowieso. (Nur passt dann das Timing an einigen stellen nicht mehr so gut und der Code wird ziemlich aufgebläht) Habe leider gerade keinen Code an dem ich das demonstrieren kann (darf ich noch nicht veröffentlichen - kommt aber bald) Vllt. mach ich demnächst mal ein kleines Beispiel dazu fertig. Woran kann das liegen? Nervt nämlich extrem! Ich arbeite normalerweise mit PICs und dem C18 - da habe ich sowas seltsames noch nie gehabt... MfG, Dominik
"mach ich demnächst mal ein kleines Beispiel dazu fertig" Das wäre hilfreicher als allgemein gehaltene Anschuldigungen an die Adresse des Compilers. Die erweisen sich nämlich fast immer als Rohrkrepierer. Das von Joerg skizzierte CLI Verhalten ist ganz offiziell in der Instruction Set Reference dokumentiert. Und die ist deutlich älter als Dietmars Klageschrift. Also muss er sich hier mangelnde Recherche vorwerfen lassen, denn das ist ja wohl die erste Adresse in der man suchen sollte.
Hallo zusammen, ich ahbe mal eine etwas eigenwillige Untersuchung gemacht, um die Aussage, dass es sich nicht ume inen Compilerfehler handeln kann, zu unterstützen. Hab einfach den sprintf wieder reingebaut, geschaut, ob Falschausgaben kommen, und die kamen dann auch, zwar nicht reproduzierbar, aber sie kamen, und dann habe ich im List-File alle cli gesucht, die Befehlssequenzen im Hexfile umgepatched und noch einmal getestet. Diesesmal sollte sich ja an der Funktionalität überhaupt nichts ändern, denn selbst die Checksummen im Hexfile (ich hatte Glück, alle cli-Befehle waren in der Mitte der Hexzeilen, so musste ich die Checksummen nicht anpassen) waren ja gleich. Nur die Ausführung von SPL = R31 und Status zurückschreiben waren in der Reihenfolge vertauscht, also so, wie sie laut Lehrbuch auch sein sollten, zuerst beide Register SPH und SPL umsetzen und dann den Int wieder freigeben. Dann habe ich nochmals geschaut, und der Fehler war noch da! Es liegt also an etwas anderem! Die Befehlssequenz mit dem cli und dem Zurückschreiben ist es also definitiv nicht! So ein Bug wäre sicherlich schon früher aufgefallen! Jetzt muss ich eine Routine schreiben, die ein Stackmonitoring macht.
"Das von Joerg skizzierte CLI Verhalten ist ganz offiziell in der Instruction Set Reference dokumentiert." War mir doch gleich so, daß ich das irgendwo gelesen hab. Es gibt z.B. auch die Folge:
1 | cli(); |
2 | // ... Freigabe des Aufwachinterrupts
|
3 | sei(); |
4 | sleep(); |
Damit ist garantiert, daß kein Interrupt vor dem Sleep zuschlagen kann und man nie wieder aufwacht. Peter
@Dominik "An meinem Code dürfte es also nicht liegen, denn der funktioniert in 99% der Fälle einwandfrei und bei abgeschalteter Optimierung sowieso." In der Regel verbrennt man sich mit solchen Aussagen mächtig die Zunge. Solche Fehler sind typisch für ungültige Pointer (Schreiben über die Feldgrenze hinaus, schreiben als falscher Typ, fehlende Initialisierung). Damit werden Returnadressen zerstört und manchmal kann der Sprung trotzdem wieder im richtigen Kontext landen. Typische Fehlerkandidaten sind scanf, itoa, usw. Peter
Hallo, der Thread ist zwar schon älter, aber ich möchte trotzdem einen Hinweis loswerden. Das beschriebene Verhalten ist bei mir ebenfalls aufgetreten, und zwar im <__epilogue_restores__> von sprintf(), hier der Codeausschnitt mit Kommentaren: in r0, 0x3f ; SREG sichern cli ; alle Interrupts sperren out 0x3e, r29 ; SP_H setzen out 0x3f, r0 ; SREG restore (auch Interrupts) out 0x3d, r28 ; SP_L setzen Sofort nach dem SREG Restore springt der Programmzeiger nach 18: f5 c1 rjmp .+1002 ; <__vector_12> d.h. der anstehende Interrupt wird sofort ausgeführt, und nicht erst einen Befehl später nach seiner Freigabe. Aber: Diese Beobachtung habe ich im Simulator von VMLAB gemacht. Wenn die von Jörg und Peter zitierte Aussage von Atmel richtig ist, was wir wohl voraussetzen können, dann hat der VMLAB Simulator hier einen Bug. Möglicherweise auch andere Simulatoren ? Gruß, Peter H.
Ja, da hat offenbar VMLAB einen Bug. Wird aber leider nicht mehr gepflegt, da das Projekt für Enrique nicht so zum Fliegen gekommen ist, wie er sich das mal vorgestellt hatte.
>Aber: Diese Beobachtung habe ich im Simulator von VMLAB gemacht.
Richtig. VMLAB simuliert da falsch. Leider, dann das schränkt dessen
Nutzbarkeit doch ganz erheblich ein.
Andere Simulaturen sind andere Simulatoren - da besteht kein
Zusammenhang. AVRStudio z.B. macht es richtig.
Oliver
Es könnte natürlich mal jemand Enrique fragen, ob er nicht vielleicht wenigstens diesen Bug fixen möchte. Sonst ist der Simulator ja wirklich nicht schlecht, und das Freischalten der Interrupts um einen Befehl zu verzögern kann eigentlich kein großer Aufwand sein. Beim CLI musste er es ja ohnehin schon implementieren, denn dort ist es auch so dokumentiert.
Ich hatte den Fehler vor einiger Zeit mal im VMLAB-Forum angesprochen (als es das Forum noch gab), da gabs aber keine Reaktion. Noch schöner wäre natürlich, den source-code als OpenSource-Projekt zu veröffentlichen. Da gibt es nämlich schon noch ein paar Macken mehr, die man sich auch mal ansehen könnte. Oliver
Oliver wrote: > Noch schöner wäre natürlich, den source-code als OpenSource-Projekt zu > veröffentlichen. Da gibt es nämlich schon noch ein paar Macken mehr, die > man sich auch mal ansehen könnte. Wenn du dich mit Delphi rumschlagen willst... Kannst ja Enrique mal fragen. Vielleicht gibt er dir den Code ja auch zur Pflege weiter, ohne ihn gleich komplett opensource zu machen, wenn du dich im Gegenzug verpflichtest, Bugfix-Releases zu machen? Ich habe Enrique vor zwei Jahren treffen können, als ich dienstlich in Madrid unterwegs war. Ist durchaus ein netter Mensch.
>Wenn du dich mit Delphi rumschlagen willst...
Nöööö.....
Wobei, den Simulationsfehler ausbügeln würde wohl noch gehen, wenn ich
denn Delphi überhaupt hätte, aber Windows-Oberflächen mit Delphi, nein
danke. Da müssten dann schon "Profis" ran.
Oliver
Delphi gibt es hier als freie Version :) http://www.turboexplorer.com/downloads Damit hättest du es zumindest.
Hallo, das wäre schon eine tolle Sache, wenn sich jemand findet, der VMLAB weiterentwickelt und die Bugs beseitigt. Habe selber leider nicht ausreichend Zeit dafür. Die Simulationsumgebung von VMLAB ist super, insbesondere daß man eigene Komponenten hinzufügen kann. Ich habe meine Temperaturmodule und Schieberegister auf diese Weise an den ATmega rangehängt, und es funktioniert! Für das diskutierte Problem mit der Interruptfreigabe im VMLAB Simulator habe ich mir ein kleines Workaround gebastelt, und einen neuen Thread dafür aufgemacht, damit es nicht mehr als Compilerbug auftaucht: Beitrag "VMLAB Interrupt zu früh, Stackpointer falsch" Viele Grüße, Peter H.
ist es nicht die FAQ 9.9.28 aus dem avr-libc-user-manual, was die Ursprungsfrage beantwortet: 9.9.28 Why are interrupts re-enabled in the middle of writing the stack pointer? Es interressiert mich wirklich, wie Dominik und andere angebliche printf-Probleme gelöst haben. Mein sprintf_P() macht mich momentan verrückt: Beitrag "gcc-avr: sprintf_P kracht (mit 3 oder mehr Parameter)"
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.