Es geht um einen Funkempfänger. Die empfangenen Binärdaten werden durch
einen Decoder geschickt (Deinterleaver, Hamming-Decoder,
Entschlüsselung) und dann über den seriellen Port ausgegeben. Mein Code
funktionierte problemlos über Jahre.
Ein kleine Änderung an ganz anderer Stelle führt aber jetzt zum totalen
Absturz, d.h. die Firmware startet neu. Der Absturz erfolgt wenn ein
neues Paket empfangen wurde.
Die Änderung ist simpel:
von
1
if(Cfg->InterleaverEnable){...}
auf
1
if(Cfg->FecEnable){...}
Die Stelle im Code wo die Änderung gemacht wurde ist im Sendeteil und
wird beim Empfangen eines Pakets auch überhaupt nicht durchlaufen. Das
ist bizarr.
Mein Setup ist Platformio mit MegacoreX im Arduino Framework.
Debuggen, 1. Versuch:
ATMEL-ICE, avr-gdb und VSCode. Die Tatsache, dass der Code im Debug
Modus kompiliert wird, kaschiert den Absturz, alles läuft problemlos.
Debuggen, 2. Versuch. Im release Modus kompilieren und mit assert()
herausfinden, wo genau der Absturz passiert: Der Decoder wird problemlos
durchlaufen, aber der Rücksprung in die Hauptschleife führt zum Absturz.
Irgendetwas im Decoder scheint den Programmzeiger zu überschreiben. Das
ist bizarr. Ich vermute ein Problem im Decoder, finde das Leck aber
nicht. Hier wäre ich für Hilfe und/oder Tipps dankbar. Ich hänge den
Decoder hier an, das gesamte Projekt ist hier:
https://github.com/nurazur/TiNo2
Frage: Wie kann ich das Debuggen? Wie kann eine so kleine Änderung an
komplett anderer Stelle einer Firmware so etwas bewirken? Kann es sein
dass der Compiler (Version gnu++17) - aus welchem Grund auch immer -
schuld ist?
Andy P. schrieb:> Frage: Wie kann ich das Debuggen?
Alle Warnings einschalten und die Warnings durchgehen und möglichst alle
beseitigen.
Parameter "-fsanitize=address" des compilers nutzen
Tools wie cppcheck.
Udo S. schrieb:> Alle Warnings einschalten und die Warnings durchgehen und möglichst alle> beseitigen.> Parameter "-fsanitize=address" des compilers nutzen> Tools wie cppcheck.
Danke für die Tips.
1
build_flags = -Wall
ist in platform.ini immer gesetzt.
Alle Warnings sind beseitigt, das mache ich für Releases sowieso.
Die beiden anderen Tips werde ich probieren und hier berichten.
Andy P. schrieb:> Frage: Wie kann ich das Debuggen? Wie kann eine so kleine Änderung an> komplett anderer Stelle einer Firmware so etwas bewirken?
Üblicherweise ein "dangling pointer" oder "out of bounds"-Zugriff.
Indiz für "out of bounds" ist es, wenn der Fehler nicht mehr auftritt,
nachdem man Variablen umsortiert oder filler dazwischen einfügt. Das ist
aber schon eine Verzweiflungstat.
Andy P. schrieb:> Kann es sein> dass der Compiler (Version gnu++17) - aus welchem Grund auch immer -> schuld ist?
Die Wahrscheinlichkeit ist 0.0000000000000001%
Dein code ist etwas seltsam geschrieben, ich bekomm da oft Stirnrunzeln.
Wenn ich richtig sehe ist datalen max. 12, d.h. alleine hier werden
schon 60 Byte Stack verbraten. Dann die tiefe Verschachtelung... Es kann
gut sein, das der gcc beim vorherigen code erkannt hat, das der Pfad nie
aktiv war und deswegen z.B. tinytx_fec immer weg optimiert hat.
Schau Dir mal das gcc flag "-fstack-usage" an. Der GCC erstellt dann
eine Datei mit Informationen zum Stackbedarf der einzelnen Funktionen.
Musst du dann entsprechend der Aufrufschachtelung zusammenrechnen, gibt
auch fertige Tools für: https://github.com/HBehrens/puncover/
Nick schrieb:> Üblicherweise ein "dangling pointer" oder "out of bounds"-Zugriff.> Indiz für "out of bounds" ist es, wenn der Fehler nicht mehr auftritt,> nachdem man Variablen umsortiert oder filler dazwischen einfügt. Das ist> aber schon eine Verzweiflungstat.
tatsächlich hilft der Austausch der beiden If statements in meinem
Eingangs Post. Filler helfen leider nicht. Also haben wir es
wahrscheinlich mit 'out of bounds' zu tun.
> Dein code ist etwas seltsam geschrieben, ich bekomm da oft Stirnrunzeln.
Ich bin immer offen für Verbesserungsvorschläge und/oder konstruktiver
Kritik. Du darfst also gerne konkreter werden. Der Code ist über 13
Jahre von einem kleinen ATtiny84 Projekt über den ATmega328P, den
ATmega4808, und zuletzt bis zum AVR64DD32 gewachsen und angepasst
worden, wurde 100+ mal gepatcht und sieht garantiert nicht mehr wie vom
Reißbrett entwickelt aus. Ursprünglich wollte ich vom ATmega328P
ausgehend das Projekt auf ATMEL Studio übertragen, aber der Import hat
überhaupt nicht funktioniert. Was bleibt wäre das Ganze von Grund auf
neu zu konzipieren, gleich mit MPLAB anzufangen und konsequent in C zu
programmieren.
Thorsten S. schrieb:> klingt sehr nach Puffer-Überlauf. Also ein Schreiben in ein Array über> die Länge hinweg.
Ok, ich habe die Größe aller infrage kommenden Arrays verdoppelt. Keine
Veränderung, leider.
Andy P. schrieb:> Debuggen, 2. Versuch. Im release Modus kompilieren und mit assert()> herausfinden, wo genau der Absturz passiert:
Warum nicht einfach den Release Code debuggen?
Dee Fehler klingt stark nach Buffer Overflow, also Rücksprungadresse auf
dem Stack überschrieben. Lokale Arrays mit variabler Länge (!!) sind
tückisch, ganz besonders auf dem AVR.
Vergleiche doch mal das Disassembly der beiden Versionen.Prpfe ob der
Empfangsteil wirklich unverändert übersetzt wird.
Andy P. schrieb:> Was bleibt wäre das Ganze von Grund auf neu zu konzipieren, gleich mit> MPLAB anzufangen und konsequent in C zu programmieren.
C++ ist hier sicherlich nicht schuld... Also wenn schon Neuanfang, dann
auf ARM Cortex -M, da hast du viel mehr Leistung und kannst komfortabel
debuggen.
Ist aber wahrscheinlich nicht nötig. Debugge den Release Code (mit -g3
kompilieren), das sollte helfen.
Andy P. schrieb:> tatsächlich hilft der Austausch der beiden If statements in meinem> Eingangs Post.
Naja, da wird anderer code aufgerufen. Also liegt das Problem in dem neu
aufgerufenen code.
Andy P. schrieb:> Du darfst also gerne konkreter werden.
Danke. :-)
Ich geh morgen drauf ein. Dazu wird es mir heute sonst zu spät.
Andy P. schrieb:> Was bleibt wäre das Ganze von Grund auf> neu zu konzipieren, gleich mit MPLAB anzufangen und konsequent in C zu> programmieren.
Hmm ... wenns im Prinzip läuft, warum ändern? Ausser du willst das
übungshalber machen. Und wenns in C++ ist, dann bleib halt dabei.
Ich hätte es in MPLAB-X aufgesetzt, aber das ist für dein Problem
absolut nicht relevant.
NB:
Andere haben es auch schon angemerkt. Setz mal den Stack hoch.
Vorsichtshalber nah ans Maximum bei dem es noch compiliert werden kann.
Andy P. schrieb:> Wie kann ich das Debuggen? Wie kann eine so kleine Änderung an> komplett anderer Stelle einer Firmware so etwas bewirken?
Arbeite mal mit einem Hexeditor und verändere da mal was. Wenn die
Längen nicht passen, kann so einiges durcheinanderkommen -> böser
Absturz bzw. -> kaputter Code.
Andy P. schrieb:> und konsequent in C zu> programmieren.
Warum nicht gleich in Asm?
(Vorteil: normalerweise gut überschaubar und üblicherweise auch sehr
einfach zu debuggen)
Nick schrieb:> Setz mal den Stack hoch
Hat das irgendeine Auswirkung auf dem AVR?! Das Stacklimit wird von der
Hardware doch nicht überwacht?
Rbx schrieb:> Warum nicht gleich in Asm?> (Vorteil: normalerweise gut überschaubar und üblicherweise auch sehr> einfach zu debuggen)
Man kann ja immer auch den vom Compiler generierten Assemblercode
debuggen.
Niklas G. schrieb:> Nick schrieb:>> Setz mal den Stack hoch>> Hat das irgendeine Auswirkung auf dem AVR?!
Eigentümliche Frage. Dadurch wird dann ein 57 Bit-Prozessor daraus? Oder
wie willst du das interpretiert haben?
Ersatzweise kann er natürlich den Stack auf 100 Bytes setzen, macht ja
nix am Controller.
Aber am Programm!
> Das Stacklimit wird von der Hardware doch nicht überwacht?
Und weils nicht überwacht wird (ohne den spezifischen µC zu kennen) kann
das gerne mal zu unangenehmen Effekten führen.
Nick schrieb:> Eigentümliche Frage. Dadurch wird dann ein 57 Bit-Prozessor daraus? Oder> wie willst du das interpretiert haben?
Was verstehst du überhaupt genau unter "Setz mal den Stack hoch" ? Ich
schätze mal, im Linkerscript das Symbol für den minimalen Wert des
Stackpointers kleiner machen? Dieses Symbol wird aber nur dafür
verwendet, dass der Linker prüfen kann, ob dieser Stack zusammen mit den
restlichen Daten in den RAM passt. Das Symbol hat aber keinen Effekt auf
das final erzeugte Image und ändert nichts am Laufzeitverhalten; der
Stack kann durchaus größer werden, was ein Problem sein kann aber nicht
muss. Das Symbol kleiner zu machen bewirkt also höchstens dass der
Linker jetzt einen Fehler wirft, was aber keinerlei Aussage darüber
trifft, ob dort wirklich ein Problem vorliegt.
Beim ARM Cortex-M kann man die MPU dazu nutzen, ein kleines Stück
"Niemandsland" unter dem Stack anzulegen, sodass Zugriffe darauf sofort
zum Absturz führen. Die Adresse dieses Bereichs würde man aus der
Stack-Definition aus dem Linkerscript ableiten. Diese Überwachung ist
aber auch nicht "scharf", wenn eine Funktion mit großem Stackframe
diesen Bereich einfach überspringt, springt die Überwachung nicht an.
Beim AVR gibt es aber kein Äquivalent dazu, oder?!
Nick schrieb:> Aber am Programm!
Nö, was denn?
@ TO: Wenn du mal das gesamte Programm sowie die Disassembly Listings
beider Versionen zeigst, könnte man da einen Blick drauf werfen.
Insbesondere der Aufruf von crypter_pad ist interessant, was da in der
Funktion passiert sieht "gefährlich" aus.
Rbx schrieb:> Viel Spaß mit der Stackframe-Navigation. ;)
Der GCC ist da ganz harmlos, der allokiert die Stackframes nach Schema
F.
Nick schrieb:> Setz mal den Stack hoch.
Was genau meinst du denn damit?
GCC auf AVR arbeitet by default so, dass der Stack ab dem oberen Ende
des RAMs beginnt, damit hat er den maximalen Platz, den er bekommen
kann, bevor er mit den statisch allozierten Variablen (oder dem Heap, so
es einen gibt) kollidiert. Was willst du hier "hoch setzen"?
Niklas G. schrieb:> Warum nicht einfach den Release Code debuggen?>> Dee Fehler klingt stark nach Buffer Overflow, also Rücksprungadresse auf> dem Stack überschrieben. Lokale Arrays mit variabler Länge (!!) sind> tückisch, ganz besonders auf dem AVR.
Vielen Dank für den Tip mit dem Debuggen des Release Codes, da bin ich
grade dran.
Niklas G. schrieb:> Lokale Arrays mit variabler Länge (!!) sind tückisch, ganz besonders auf> dem AVR.
Naja, wenn man ein Problem mit Daten hat, die dynamisch entstehen, dann
muss man sich so oder so mit dynamischer Allozierung herumschlagen – ob
nun via malloc() oder Stack ist fast egal. Einzig, malloc()
implementiert noch einen (eher primitiven) boundary check gegen den
Stack, während der Stack natürlich „einfach so“ wächst.
Man muss sich auf jeden Fall Gedanken machen, wie viele Daten in einem
realen Szenario maximal wo alloziert werden müssen.
Andy P. schrieb:> Kann es sein dass der Compiler (Version gnu++17) ... schuld ist?
Welche Compilerversion ist das? Welche LibC? Binutils?
https://avrdudes.github.io/avr-libc/avr-libc-user-manual/FAQ.html#faq_toolchain_versionAndy P. schrieb:> ich habe die Größe aller infrage kommenden Arrays verdoppelt.
Ist natürlich nur sinnvoll, wenn sicher ist, dass das Zeugs immer noch
in den Speicher passt.
Wird Heap (malloc, calloc, new, ...) verwendet?
Falls ja: Was passiert wenn eine dieser Routinen null zurückliefert.
Falls nein: AVR-LibC bietet eine Funktion, mit welcher die Speichergröße
abgeschätzt werden kann:
https://avrdudes.github.io/avr-libc/avr-libc-user-manual/group__util__ram__usage.html
Da muss also genügend Luft nach oben sein.
Zwar glaub ich nicht dass dies das Problem ist, aber Nachprüfen ist ja
kein großer Aufwand.
Das obige -fsanitize=... funktioniert übrigens deshalb nicht, weil die
entsprechende Target-Lib für AVR nicht unterstützt wird. Bislang war es
offenbar niemandem wichtig genug, den Support beizutragen.
Vielen Dank an die Community für die rege Diskussion meines Problems,
die vielen Vorschläge und Tips. Ich werde daran arbeiten. besonders der
Vorschlag den Release Code zu debuggen gefällt mir, ich kämpfe
allerdings im Moment noch mit dem Debugger und VSCode. Wenn ich den
Absturz im Debugger nachvollziehen könnte wäre ich schon mal einen
Riesenschritt weiter. Ich melde mich sobald ich neue Erkenntnisse habe.
Auch das Assembler Listing der beiden Versionen zu Vergleichen werde ich
umsetzen.
Wie man beim AVR den "Stack hochsetzen" kann weiß ich nicht. Ich glaube
auch nicht dass es einen Überlauf des RAMs in irgendeiner Form gibt.
Denn der ATmega4808 hat 6kB RAM, der AVR64DD32 hat 8kB. Auf beiden
Prozessoren ist der Absturz identisch, d.h. der Fehler muss imho in
meinem Code liegen, nicht an der Hardware. Nach dem Kompilieren sind
gerade einmal 863 Bytes bzw 14% des Rams belegt, und ich kann mir beim
besten Willen nicht vorstellen was passieren muss um das RAM mit Daten
und/oder Heap voll zu müllen. Beim AVR64DD32 war ich gerade überrascht:
RAM: 32.7% (used 2675 bytes from 8192 bytes). Trotzdem, wie schon
gesagt, das Absturzverhalten ist auf beiden Prozessoren identisch.
Jörg W. schrieb:> Naja, wenn man ein Problem mit Daten hat, die dynamisch entstehen, dann> muss man sich so oder so mit dynamischer Allozierung herumschlagen – ob> nun via malloc() oder Stack ist fast egal.
Naja meistens gibt es da eine bessere Lösung für, im einfachsten Fall
halt statisch allokiert. Dann kann der Linker direkt sehen ob Platz
dafür ist. In C++ gibt's aus gutem Grund keine lokalen variablen Arrays
(VLA), der GCC implementiert sie als Extension, aber den Heap braucht
man trotzdem nicht unbedingt. Funktionen mit VLAs haben Stackframes
variabler Größe, wodurch die Offsets der lokalen Variablen zum SP
variieren, wodurch man mehr komplexere Adressberechnungen hat - langsam
gerade bei 8bittern wie AVR.
Jörg W. schrieb:> Man muss sich auf jeden Fall Gedanken machen, wie viele Daten in einem> realen Szenario maximal wo alloziert werden müssen.
Das sowieso, bei Embedded-Systemen (ohne OS) macht es oft keinen Sinn
weniger als das Maximum zu allokieren, weil man mit dem Speicher sowieso
nichts anderes macht.
Andy P. schrieb:> Auf beiden> Prozessoren ist der Absturz identisch
Guter Punkt - also vermutlich eher ein überlauf eines lokalen Arrays
und dann Überschreiben der Rücksprungadresse.
Niklas G. schrieb:> Naja meistens gibt es da eine bessere Lösung für, im einfachsten Fall> halt statisch allokiert.
Ich kenne Andys Szenario zu wenig, aber es sind durchaus Fälle
vorstellbar, in denen ein komplettes statisches Allozieren einen
größeren Controller bräuchte als eine sinnvolle dynamische Allokation
wahrscheinlich braucht.
In beiden Fällen müsste man aber so oder so Vorkehrungen haben, was
passieren soll, wenn der gewünschte Speicher nicht verfügbar ist (weil
mehr Daten reinkommen als vorgesehen waren [statische Allokation] bzw.
untergebracht werden können [malloc() liefert NULL]). Ob das nun ein
Verwerfen der zu vielen Daten ist, irgendeine Fehlermeldung oder gar ein
Reboot, das hängt dann von der Anwendung ab.
Niklas G. schrieb:> Guter Punkt - also vermutlich eher ein überlauf eines lokalen Arrays> und dann Überschreiben der Rücksprungadresse.
Oder ein uninitialisierter Pointer auf dessen Adresse schreibend
zugegriffen wird.
Andy P. schrieb:> Wie man beim AVR den "Stack hochsetzen"
Kann man nicht. Ist auch Unsinn. Wie Jörg schon schrieb, beginnt beim
AVR der Stackpointer normalerweise an der höchsten RAM Addresse und
bewegt sich dann je nach Stackfülle immer weiter in den unteren RAM
Bereich.
Der untere RAM Bereich wird zunächst mit allen statischen Variablen
gefüllt. Darauf folgt dann der Heap.
Da können zwei Probleme auftreten:
* Der Heap wird zu groß (also zuviel dynamischer Speicher angefordert)
so das der aktuelle Stack inhalt überschrieben wird
* Der Stack wird zu groß, das er in den Heap oder sogar die statischen
Variablen reinreicht. Dann überschreiben Zugriffe auf den Stack den Heap
oder die statischen Variablen oder umgekehrt.
Beides ekelig zu debuggen, der AVR hat leider keine MPU oder sowas.
Jörg W. schrieb:> GCC auf AVR arbeitet by default so, dass der Stack ab dem oberen Ende> des RAMs beginnt, damit hat er den maximalen Platz, den er bekommen> kann, bevor er mit den statisch allozierten Variablen (oder dem Heap, so> es einen gibt) kollidiert. Was willst du hier "hoch setzen"?
Schon mal in eine Memory Map geschaut?
Also, wenn ich (wie auch immer) festelle (feststellen lasse), dass ich 4
k Stack brauch, dann geb ich das an.
Der Linker weiß, dass 3 k statisch zugewiesenes RAM benötigt wird. Dann
bleiben (bei 8 k RAM) 1 k für den heap. Das weiß dann auch der Linker.
Ein malloc(2000) wird also scheitern und das auch freundlich
zurückmelden. Falls man sich z.B. dazu herablässt assert zu verwenden.
Wenn das genau bei dem µC (wobei mir der zunächst komplett wurscht ist)
völlig anders ist, dann bitte ich höflichst den aus meiner Erfahrung
gewonnenen Hinweis zu entschuldigen.
Rbx schrieb:> Ja, eigentlich kann man das Programm ganz gut nach Asm übertragen (viele> Bools, viele Ifs, viele Datenworte).
Das passiert beim compilieren automatisch.
Rbx schrieb:> (oder eben damit nochmal genauer auseinandersetzten:
Da der Fehler wahrscheinlich nicht in der Allokation selbst liegt,
sondern an einem Buffer-Overflow, ist das vermutlich nicht relevant.
Nick schrieb:> Also, wenn ich (wie auch immer) festelle (feststellen lasse), dass ich 4> k Stack brauch, dann geb ich das an.
Der ganze Knackpunkt ist, dass man das nicht so einfach feststellen
kann.
Nick schrieb:> Der Linker weiß, dass 3 k statisch zugewiesenes RAM benötigt wird. Dann> bleiben (bei 8 k RAM) 1 k für den heap. Das weiß dann auch der Linker.> Ein malloc(2000) wird also scheitern und das auch freundlich> zurückmelden.
Das hilft nur bei einem Heap Overflow. Aber das Programm nutzt den Heap
anscheinend überhaupt nicht, und das Symptom - Absturz bei
Funktionsrücksprung - ist nicht sehr typisch für einen Heap Overflow der
dann ignoriert wurde. Den Stack zu "erhöhen" bringt bei einem Heap
Overflow auch nichts, höchstens dass er früher auftritt.
Nick schrieb:> Wenn das genau bei dem µC (wobei mir der zunächst komplett wurscht ist)> völlig anders ist
Das ist bei allen Plattformen gleich - bei welcher Plattform hat ein
"Erhöhen des Stacks" eine Auswirkung auf den Heap (und den Rückgabewert
von malloc () ), und bei welcher Plattform hat das irgendeine Auswirkung
auf ein Programm das den Heap überhaupt nicht nutzt?
Niklas G. schrieb:> Nick schrieb:>> Also, wenn ich (wie auch immer) festelle (feststellen lasse), dass ich 4>> k Stack brauch, dann geb ich das an.>> Der ganze Knackpunkt ist, dass man das nicht so einfach feststellen> kann.
Dafür gibt es tools. zum Teil auch der Compiler oder externe tools. Und
wenn man die nicht hat, kann man es einfach mal probieren. Wenns was
hilft, weiß man wie es weiter geht.
Man kann auch in die Memory Map schauen und eine guard-variable
dazubauen die zuoberst liegt. Immer wieder mal kontrollieren, dann sieht
man, ob der Stack da reingeschrieben hat.
Niklas G. schrieb:> Das ist bei allen Plattformen gleich
Nein, wohl nicht, denn:
Jörg W. schrieb:> Sorry, Nick, ich glaube, du hast noch nie einen AVR programmiert.
Muss also was ganz spezielles am AVR sein. Ich kann mich dumpf erinnern,
dass in der Steinzeit der µC der Stack per Hardware festgelegt war und
irgendwie nur 8 Rücksprünge aufnehmen konnte. Sonst nix. Keine lokalen
Variablen, keine Übergabe von Werten. Wenn dieser AVR so eine Krücke
ist, dann ... selber schuld.
Niklas G. schrieb:> bei welcher Plattform hat ein> "Erhöhen des Stacks" eine Auswirkung auf den Heap (und den Rückgabewert> von malloc () ), und bei welcher Plattform hat das irgendeine Auswirkung> auf ein Programm das den Heap überhaupt nicht nutzt?
Wenn heap, stack und statische Allokation im gleichen RAM sind, dann ist
das so eine Plattform.
Nochmal:
Selbst wenn man den heap nicht nützt, läuft der Stack -möglicherweise-
in das statisch allokierte RAM.
Wie man das ausschließen/nachweisen kann: Siehe oben.
Wenn man so völlig im Nebel stochert und nicht weiß was der Grund ist,
kann man auch Ursachen ausschließen und so den Suchbereich reduzieren.
Muss man nicht. Jeder hat im Laufe der Zeit so seine eigenen Methoden
entwickelt. K.A. was der TO für einen Programmer hat und was der
leisten kann. Breakpoints? Backtrace? Stackview? Single step? Oder
nichts?
Oder gar einen Simulator in der IDE?
Nick schrieb:> Wie man das ausschließen/nachweisen kann: Siehe oben.
Mach doch mal bitte vor, wie du den Linkerscript dafür aufbauen willst.
(Davon abgesehen, AVR-GCC-Nutzer müssen sich normalerweise nicht mit
selbst gefummelten Linkerscripten herumschlagen.)
Nick schrieb:> Dafür gibt es tools.
Ja, aber wie gesagt, nicht ganz einfach. Ganz besonders wenn VLAs im
Spiel sind! Da ist es im Allgemeinen überhaupt nicht berechenbar. Genau
wie bei Rekursion.
Nick schrieb:> Man kann auch in die Memory Map schauen und eine guard-variable> dazubauen die zuoberst liegt.
Ja, das ist die simple Alternative zur MPU.
Nick schrieb:> Muss also was ganz spezielles am AVR sein.
Ist es nicht. Ist bei x86, ARM, ... auch ganz genau so. Nur das diese
Plattformen eine MPU/MMU haben mit der man mithilfe des Guard-Bereichs
in vielen (nicht allen) Fällen den Stack-Overflow finden kann, d.h.
das Erhöhen des Stacks würde das Auslösen dieses Mechanismus verhindern
- aber dieser Mechanismus existiert auf dem AVR nicht und hat daher
hier auch nicht ausgelöst. Bei einem simplen OS oder Bare Metal für x86
oder ARM, wo der Guard-Bereich nicht explizit konfiguriert wurde, ist
das Verhalten identisch zum AVR.
Nenn doch mal eine konkrete Plattform, bei der ein "Hochsetzen des
Stacks" einen Stack-Overflow verhindert, auch wenn kein Guard-Bereich
per MPU/MMU definiert wurde (z.B. durch ein OS).
Nick schrieb:> Wenn dieser AVR so eine Krücke> ist, dann ... selber schuld.
Nö, der hat einen "normalen" Software-Stack.
Nick schrieb:> Selbst wenn man den heap nicht nützt, läuft der Stack -möglicherweise-> in das statisch allokierte RAM.
Richtig. Und was bewirkt dann das Ändern der Stackgröße im Linkerscript?
Nichts, da die ja keine Auswirkung auf die Laufzeit hat.
Nick schrieb:> Wenn man so völlig im Nebel stochert und nicht weiß was der Grund ist,> kann man auch Ursachen ausschließen und so den Suchbereich reduzieren.
Haben wir doch. Ein Stack-Überlauf ist unwahrscheinlich, ein
Heap-Überlauf anscheinend unmöglich (damit ist dein Hinweis auf malloc()
sinnlos).
Andy P. schrieb:> Nach dem Kompilieren sind> gerade einmal 863 Bytes bzw 14% des Rams belegt, und ich kann mir beim> besten Willen nicht vorstellen was passieren muss um das RAM mit Daten> und/oder Heap voll zu müllen
Ein 1 Byte großes Speicherleck in einer Schleife, und schon läuft der
größte Speicher voll.
Das muss keine echte C-Schleife (for, while etc.) sein, das kann auch
eine Ereignisschleife sein.
Rbx schrieb:> Na dann könnte TO auch den Asm-Code posten.
Hab ich doch sogar schon vorgeschlagen:
Niklas G. schrieb:> @ TO: Wenn du mal das gesamte Programm sowie die Disassembly Listings> beider Versionen zeigst
Niklas G. schrieb:> Rbx schrieb:>> Na dann könnte TO auch den Asm-Code posten.>> Hab ich doch sogar schon vorgeschlagen:>> Niklas G. schrieb:>> @ TO: Wenn du mal das gesamte Programm sowie die Disassembly Listings>> beider Versionen zeigst
Würde (Werde) ichn gerne machen, im Build sehe ich allerdings nur .o
Dateien und keinen Assembler. Den erzeugt man glaub ich mit einer
Compiler Option, richtig?
-S statt -c
-fsave-temps behält sämtliche Zwischendateien, statt sie zu löschen
Schwieriger wird es allerdings, wenn du auch -flto benutzt; dann wird
nach dem Linken der Compiler nochmal angeworfen, und das Resultat hat
mit dem ursprünglichen Code nicht mehr viel zu tun. Dann geht nur noch
Disassemblieren.
Andy P. schrieb:> Den erzeugt man glaub ich mit einer Compiler Option, richtig?
Ich erzeuge das lieber mit "objdump -d ..." nach dem Linken, dann ist es
ein direktes Abbild des finalen Ergebnis.
Wenn man sich das Listing vom Compiler geben lässt, sind da eventuelle
Änderungen vom Linker nicht berücksichtigt (LTO und Wegeoptimierung
durch --gc-sections), und die Adressen sind auch nur Dummies.
Jörg W. schrieb:> Rolf schrieb:>> Ein 1 Byte großes Speicherleck in einer Schleife>> Wie erzeugst du selbiges, wenn kein malloc() benutzt wird?
Zu "Speicherleck ohne malloc()" sagt die KI deines Vertrauens so dies
und das. ;-)
Rolf schrieb:> Jörg W. schrieb:>> Rolf schrieb:>>> Ein 1 Byte großes Speicherleck in einer Schleife>>>> Wie erzeugst du selbiges, wenn kein malloc() benutzt wird?>> Zu "Speicherleck ohne malloc()" sagt die KI deines Vertrauens so dies> und das. ;-)
Du hast eine Behauptung aufgestellt, wenn du jetzt die Beweislast auf
andere (oder die Halluzination einer KI) abwälzt, machst du dich nicht
gerade glaubwürdig.
anbei zwei Assembler Dateien. Die eine mit dem originalen Code, die
andere mit der Minimaländerung, welche zum Absturz führt. Ich bin total
geplättet wie so eine wirklich kleine Änderung zu derart vielen
Unterschieden im Assembler Code führt. Damit ich nichts falsch mache
habe ich es nochmals de-assembliert mit dem selben Ergebnis.
Erzeugt hatte die Dateien mit
radio_receive() und radio_send() in datalinklayer.cpp unterscheiden sich
in den beiden Versionen deutlich, u.a. weil interleave() in codec.cpp
nicht mehr da ist weil es nicht mehr aufgerufen wird.
Zeig doch mal den kompletten Source Code der beiden Versionen mit der
Änderung und nicht nur eine Zeile davon, die sich noch nicht mal genau
zuordnen läßt.
Andy P. schrieb:> Johann L. schrieb:>> Welche Compilerversion ist das? Welche LibC? Binutils?> toolchain-atmelavr @ 1.70300.191015 (7.3.0)
Ok, also sind AVR-LibC und Binutils "irgendwas unbekanntes"...
Bei alten Binutils stellte der Linker (bzw. Linker Script) noch nicht
einmal Überlauf von Static Storage fest.
Niklas G. schrieb:> Funktionen mit VLAs haben Stackframes variabler Größe, wodurch> die Offsets der lokalen Variablen zum SP variieren, wodurch man> mehr komplexere Adressberechnungen hat - langsam gerade bei> 8bittern wie AVR.
Ja, einen irrsinnigen Overhead von ca. 6 Cycles um einen Array-Zugriff
vorzubereiten. Die absolute Hölle!
Jetzt mal ernst. Aus diesem Grund (Overhead) VLAs zu verwerfen ist
Käse.
Beispiel:
1
voidinit(int*);
2
3
intfun(intn,inti)
4
{
5
inta[n];
6
init(a);
7
returna[i];
8
}
Vorbereitug des Zugriffs:
1
movw r30,r14 ; 95 [c=4 l=1] *movhi/0
2
lsl r30 ; 79 [c=8 l=2] *ashlhi3_const/0
3
rol r31
4
add r30,r16 ; 50 [c=8 l=2] *addhi3/0
5
adc r31,r17
6
; Zugriff qua Z
Falls i an der Stelle nicht in einen Register lebt so kommt noch die
Zeit hinzu um i aus dem Frame zu lesen. Kann mit malloc aber ebenso der
Fall sein.
Zudem verwendet der Autor malloc, float und printf. Hat also Resourcen
im Überfluss ;-)
Johann L. schrieb:> Ok, also sind AVR-LibC und Binutils "irgendwas unbekanntes"...
nein AVR-LibC ist mir natürlich ein Begriff, aber in der von mir
benutzten Toolchain stehen alle Utilities im /bin Verzeichnis und
stellen sich mir alle mit Version 7.0.3 vor. Die LibC steht im
Verzeichnis /avr. Das für den ATmega4808 benötigte zusätzliche Framework
ist der MegacoreX und heisst bei PlatformIO
framework-arduino-megaavr-megacorex. Für die Arduino IDE könnte man das
ebenfalls installieren.
Andreas M. schrieb:> Ich habe mindestens ein "New" im Quelltext gefunden :-)
Es gibt mehrere malloc / new Aufrufe (asm statisch) für folgende
Blockgrößen in Bytes:
41 (5×), 14, 2, 67, 22, 4*num_actions.
Jörg W. schrieb:> Du hast eine Behauptung aufgestellt, wenn du jetzt die Beweislast auf> andere (oder die Halluzination einer KI) abwälzt, machst du dich nicht> gerade glaubwürdig.
Na, na, immer ruhig Blut.
Zunächst mal hatte ich lediglich auf die Bemerkung des OP geantwortet,
wonach er sich einen Speicherüberlauf nicht vorstellen könne, WEIL sein
Problem auch bei einem sehr großen Speicher auftritt.
malloc() hatte ich dabei gar nicht erwähnt, also nichts Einschlägiges
behauptet.
Es warst du, der - wenn auch nur indirekt in Form einer rhetorischen
Frage - ohne Beweise behauptet hat, man könne kein Speicherleck haben,
wenn man malloc() nicht verwende.
Ich habe dann scherzhaft gesagt, wie du im Nullkommanichts (KI-Prompts
akzeptiert ja inzwischen IMHO jeder Browser) auf einschlägige
Gesichtspunkte kommen kannst. DAFÜR - also zum Bekommen von Denkansätzen
- sind ChatGPT und Konsorten nämlich durchaus brauchbar.
Tatsächlich sagt die KI zum Beispiel, dass malloc() ja auch in
irgendwelchen fremden Libs versteckt sein kann, wovon man als
Programmierer gar nichts weiß bzw. wissen kann.
Sind das Halluzinationen oder hat die KI da recht? Und wo habe ich was
"auf andere abgewälzt"?
Rein logisch macht aber ohne FEC ein Interleaver keinen Sinn, also habe
ich folgende Änderung vorgenommen, zunächst nur zum Testen. Dabei stürzt
die Firmware beim Rücksprung von radio_receive() zum Hauptprogramm ab,
obwohl das Code-Snippet in radio_send() steht und mit der Ausführung von
radio_receive() nichts zu tun hat.
1
//FEC
2
if(Cfg->FecEnable)
3
{
4
unsignedchartinytx_fec[datalen*2];// the encoded message
ich gehe mal davon aus dass der Compiler merkt dass if(Cfg->FecEnable)
im else Block immer false ist und schmeisst den if Block weg.
Ich finde es wirklich bewundernswert wie du so schnell den ASM Code
analysieren kannst, weil für mich ist das unlesbar. Obwohl ich selbst
mal vor 30 Jahren einen 8051 in Assembler programmiert habe. Lange her.
Wenn ich mein Listing von damals heute anschau, verstehe ich Bahnhof.
Andy P. schrieb:> if(Cfg->FecEnable)
Das kommt im Code auf GitHub nicht vor. Es gibt nur eine Stelle
> if(Cfg.FecEnable)
Also irgendwas passt hier nicht zusammen!
Niklas G. schrieb:> Andy P. schrieb:>> if(Cfg->FecEnable)>> Das kommt im Code auf GitHub nicht vor. Es gibt nur eine Stelle>>> if(Cfg.FecEnable)>> Also irgendwas passt hier nicht zusammen!
stimmt! Ich hatte testweise probiert ob die Übergabe von Cfg auf die MAC
KLasse per Referenz bzw. per Zeiger einen Unterschied macht. Machts aber
nicht. Ich werde also zurück zur Referenz gehen. Sorry dafür, ich
arbeite im Moment mit mehreren Versionen von Try-and-error Tests. Bisher
keinerlei neue Erkenntnisse. Ob ich arrays statisch mache, grösser mache
oder was auch immer, die Firmware stürzt ab. Ergibt sich im Assembler
ein anderer Code abhängig davon ob ich eine Referenz oder einen Zeiger
verwende?
Andy P. schrieb:> Ergibt sich im Assembler> ein anderer Code abhängig davon ob ich eine Referenz oder einen Zeiger> verwende?
Ja, ich glaube eine Referenz kann man z.B. nicht im Stackframe hin- und
herschieben. Übrigens bin ich in C++-Feinheiten nicht so fit - da kann
man ja vom 100sten zum 1000en kommen. "using std" sieht man eigentlich
auch selten, und fragt sich warum eigentlich. Oder was an D eigentlich
so toll ist (wenn es kaum jemand nutzt) - aber lassen wir das ;)
Andy P. schrieb:> Sorry dafür, ich> arbeite im Moment mit mehreren Versionen von Try-and-error Tests. Bisher> keinerlei neue Erkenntnisse. Ob ich arrays statisch mache, grösser mache> oder was auch immer, die Firmware stürzt ab. Ergibt sich im Assembler> ein anderer Code abhängig davon ob ich eine Referenz oder einen Zeiger> verwende?
Sollte nicht. Falls doch, hast du einen Fehler im Code.
Was ich mich frage: Warum debuggst du nicht einfach den Assemblercode?
Es sollte doch kein Problem sein, beim Eintritt in die Funktion zu
prüfen, was da auf dem Stack abgelegt wird (insbesondere natürlich die
Rücksprungadresse), dann bis zur der letzten Instruktion vor dem ret zu
gelangen und zu schauen, was dann da als Rücksprungadresse auf dem Stack
liegt.
Hat sich was geändert? Falls ja, einfach dasselbe noch mal von vorn und
halbwegs sinnvoll durch die Funktion steppen und immer mal wieder
kontrollieren, ob die Rücksprungadresse noch passt. So sollte sich doch
relativ schnell die Ursache einkreisen lassen.
Rolf schrieb:> dass malloc() ja auch in irgendwelchen fremden Libs versteckt> sein kann, wovon man als Programmierer gar nichts weiß bzw. wissen kann.
Hat ja jeder, der nicht ins Map-File schauen will, sein Recht auf
Unwissenheit.
Andy P. schrieb:> ich gehe mal davon aus dass der Compiler merkt dass if(Cfg->FecEnable)> im else Block immer false ist und schmeisst den if Block weg.
So ist es. Folgerung:
- In radio_send() wird interleave() überhaupt nicht mehr aufgerufen
- Dadurch gibt es nur noch genau 1 Stelle, an der interleave()
aufgerufen wird, nämlich in radio_receive()
- Der Linker inlined dann diesen einen interleave() Aufruf in
radio_receive()
- Dadurch ist der Assemblercode von radio_receive() in der modifizierten
Version auch deutlich größer
Und genau dadurch ergibt sich der kuriose Effekt, dass die Änderung der
Senderoutine die Empfangsroutine beeinflusst. Weil hier der Einfluss des
Linkers (LTO) relevant wurde, ist es auch gut dass du per "objdump"
disassembliert hast statt dich alleine auf der Compiler-Eigene Listing
zu verlassen.
Durch das inlinen versucht der Compiler (wird vom Linker mit LTO nochmal
aufgerufen), irgendwas an interleave() zu optimieren, quasi es nahtlos
in radio_receive() zu integrieren. Wahrscheinlich gibt es irgendwo einen
Fehler der dadurch dann richtig "scharf geschaltet" wird. Ich glaub
interleave() an sich ist korrekt aber ganz sicher bin ich nicht...
Wo wird eigentlich radio.DATA initialisiert?
Rbx schrieb:> Andy P. schrieb:>> Ergibt sich im Assembler>> ein anderer Code abhängig davon ob ich eine Referenz oder einen Zeiger>> verwende?>> Ja, ich glaube
Auf Assemblerebene sind Referenz und Zeiger das gleiche.
Oliver
Ich habe noch mal genauer in den Assembler Code geschaut: Der Compiler
optimiert nach der Änderung die verbleibenden zwei Aufrufe von
interleave() in datalinklayer.cpp (einmal in radio_receive() und einmal
in radio_send()) und baut beim ersten mal nur den "reverse" Teil von
interleave() und beim zweiten mal nur den "forward" Teil "inline" direkt
in die entsprechende Funktion ein.
Ob diese intensive Optimierung eventuell für das Problem verantwortlich
ist weiß ich nicht, eventuell mal versuchen was bei etwas weniger
Optimierung passiert.
Rolf schrieb:> Tatsächlich sagt die KI zum Beispiel, dass malloc() ja auch in> irgendwelchen fremden Libs versteckt sein kann, wovon man als> Programmierer gar nichts weiß bzw. wissen kann.
Dann isses aber doch wieder malloc. Ich wollte ja wissen, wodurch es
ohne malloc noch zu einem Speicherleck kommen soll.
Gut, inzwischen wissen wir natürlich, dass sich malloc-Aufrufe auch
anderweitig im Code befinden.
Allerdings spricht gegen die These des Speicherlecks, dass der Code auf
viel kleineren Devices zuvor ohne ein solches gelaufen ist.
Andy P. schrieb:> Sorry dafür, ich arbeite im Moment mit mehreren Versionen von> Try-and-error Tests.
Dann schieb das doch in einen eigenen git-Branch, dann können andere es
auch nachvollziehen. Hat dann den Vorteil, dass alle über den gleichen
Code reden. ;-)
Wenn noch ein oder zwei Pins für eine "Debugausgabe" vorhanden sind, und
ein Logikanalyzer, könnte man sich "von hinten" dem Problem nähern. An
strategisch günstigen Punkten der SW rausgeben "bin hier", und dann
Rausmessen was die letzten Aktionen waren. Dann das Suchfenster enger
machen, also im spannenden Bereich weitere Debugausgaben dazubauen.
Wiederholen, bis ein überschaubarer Bereich eingegrenzt ist. Auch
Stackinhalt rausschreiben über zwei Pins an der interessanten Stelle
haben wir schon praktiziert.
Wenn also irgendeines der "new" fehlschlägt sollte der Debugger anhalten
(ohne Debugger dran dann Einfrieren), das wäre eine einfache Möglichkeit
den Fehler einzugrenzen.
Oliver S. schrieb:> Auf Assemblerebene sind Referenz und Zeiger das gleiche.
Kommt auf die Perspektive an. Referenzen kann man ja als
Compilermarkierungen betrachten, die bei 8-Bit MCs eventuell einen Hauch
zuviel Ressourcen verbrauchen (und sich schlecht debuggen lassen..).
In Assembler hast du das Problem nicht - und darüberhinaus einen viel
besseren Überblick über Zusammenhänge, und soviel Stackaufrufe wie bei
C++ braucht man bei ASM auch nicht. Eventuell wäre die Übersetzung nach
C ein einfacherer Schritt, der Erfolg verspricht.
Rbx schrieb:> Referenzen kann man ja als> Compilermarkierungen betrachten, die bei 8-Bit MCs eventuell einen Hauch> zuviel Ressourcen verbrauchen
Die können sogar weniger Ressourcen brauchen als Pointer, denn weil
Referenzen nie null sein können, spart man sich die Null-Pointer-Checks.
Rbx schrieb:> und soviel Stackaufrufe wie bei> C++ braucht man bei ASM auch nicht.
Ein gutes Assembler-Programm braucht genau so viel Stack wie ein gutes
C++ -Programm, die Daten müssen ja irgendwo hin.
Udo S. schrieb:> ??? Referenzen und Zeiger sind in Assembler indirekte Adressierung.
So ein Quark. Referenzen gibt es in Assembler gar nicht. Referenzen
dienen zur Übersetzungsoptimierung bei C++. Und genau dieser Hintergrund
beschert uns hier vermutlich das Debugging-Problem.
Oliver S. schrieb:> Ganz sicher nicht.
Ganz sicher doch.
Niklas G. schrieb:> Die können sogar weniger Ressourcen brauchen als Pointer, denn weil> Referenzen nie null sein können, spart man sich die Null-Pointer-Checks.
Bei einem 8-Bit MC? Zusätzlich Register zur Kontrolle und massive
Ram-Belegung deswegen stören natürlich auch gar nicht.
Ganz abgesehen davon wäre das ja dann doch ein Argument der
Unterscheidung.
Rbx schrieb:> Udo S. schrieb:>> ??? Referenzen und Zeiger sind in Assembler indirekte Adressierung.> So ein Quark. Referenzen gibt es in Assembler gar nicht. Referenzen> dienen zur Übersetzungsoptimierung bei C++. Und genau dieser Hintergrund> beschert uns hier vermutlich das Debugging-Problem.
Sorry du hast meine nicht ganz eindeutige Formulierung anscheinend nicht
verstanden.
Referenzen oder Pointer in C sind in Assembler nichts anderes als
indirekte Adressierung.
Der Quark war eher:
Rbx schrieb:> Kommt auf die Perspektive an. Referenzen kann man ja als> Compilermarkierungen betrachten
Rbx schrieb:> Zusätzlich Register zur Kontrolle und massive> Ram-Belegung deswegen stören natürlich auch gar nicht.
Hä? Was sind deiner Meinung nach C++ Referenzen ?!
Der generierte Assembler-Code ist für diese beiden Funktionen absolut
identisch, einmal mit Referenz, einmal mit Pointer:
https://godbolt.org/z/1114vT39f
Denkst du vielleicht C++ Referenzen funktionieren so wie die Referenzen
von Java oder C#?
Rbx schrieb:> Oliver S. schrieb:>> Ganz sicher nicht.>> Ganz sicher doch.
Was wieder zeigt, dass du von C++ einfach gar keine Ahnung hast. Man
kann ein C++ Programm nicht einfach nach C übersetzen, es sind schlicht
zwei völlig verschiedene und unabhängige Sprachen.
Man könnte das Programm in C neu schreiben, was aber hier völlig am
Thema vorbei ginge.
Oliver
Jörg W. schrieb:>> Zu Zeiten von CFront vor 30 Jahren ging das noch. :-)
Damit war schon etwas früher Schluss als die Exceptions kamen:
https://en.wikipedia.org/wiki/Cfront
Apples MPW (Macintosh Programmer's Workshop) hat den CFront lange
verwendet.
>> Wenn also irgendeines der "new" fehlschlägt sollte der Debugger anhalten> (ohne Debugger dran dann Einfrieren), das wäre eine einfache Möglichkeit> den Fehler einzugrenzen.
sehr gute Idee! Allerdings glaube ich inzwischen nicht mehr so recht
dass mein Problem mit Allokation von Daten bzw. Klassen zu tun hat. Ich
glaube eher dass das während der Programmausführung passiert, und zwar
genau dann, und nur dann, wenn ein Datenpaket empfangen wurde. Diesen
Teil muss ich unter die Lupe nehmen.
Leider habe ich diese und nächste Woche nicht mehr so viel Zeit mich um
das Problem intensiv zu kümmern. Ab August dann wieder Vollzeit.
Andy P. schrieb:> Ich> glaube eher dass das während der Programmausführung passiert, und zwar> genau dann, und nur dann, wenn ein Datenpaket empfangen wurde.
Ja, aber wenn radio.DATA ins Nirvana zeigt kann natürlich alles mögliche
passieren.
Empfängst Du die Daten von einem unverändertem Sender (also mit dem
Original-Code erzeugt) oder sind es Daten die durch die Änderung in
radio_send() erzeugt wurden?
Zur RAM Nutzung: Die Aufrufe von malloc() in den beiden Assembler
Listings sind sehr überschaubar, das sind ein paar mit konstanter Größe
und einer mit einem dynamischen Wert ("new action[num_actions]" mit
"num_actions" aus dem EEPROM).
Das sind die mit konstanter Größe aus
"disassebled_minimal_change_crash.asm"
1
322c: 89 e2 ldi r24, 0x29 ; 41
2
322e: 90 e0 ldi r25, 0x00 ; 0
3
3230: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
4
5
32d0: 89 e2 ldi r24, 0x29 ; 41
6
32d2: 90 e0 ldi r25, 0x00 ; 0
7
32d4: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
8
9
334c: 89 e2 ldi r24, 0x29 ; 41
10
334e: 90 e0 ldi r25, 0x00 ; 0
11
3350: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
12
13
335a: 89 e2 ldi r24, 0x29 ; 41
14
335c: 90 e0 ldi r25, 0x00 ; 0
15
335e: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
16
17
33d6: 89 e2 ldi r24, 0x29 ; 41
18
33d8: 90 e0 ldi r25, 0x00 ; 0
19
33da: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
20
21
3560: 8e e0 ldi r24, 0x0E ; 14
22
3562: 90 e0 ldi r25, 0x00 ; 0
23
3564: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
24
25
6604: 82 e0 ldi r24, 0x02 ; 2
26
6606: 90 e0 ldi r25, 0x00 ; 0
27
6608: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
28
29
6660: 83 e4 ldi r24, 0x43 ; 67
30
6662: 90 e0 ldi r25, 0x00 ; 0
31
6664: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
32
33
6866: 86 e1 ldi r24, 0x16 ; 22
34
6868: 90 e0 ldi r25, 0x00 ; 0
35
686a: 0e 94 61 45 call 0x8ac2 ; 0x8ac2 <malloc>
Dadurch dass interleave() durch die Änderung "inline" wurde ist die
Benutzung des Stack etwas kleiner. radio_receive() belegt zwar drei
Bytes mehr für lokale Variablen aber dafür fällt der Aufruf von
interleave() weg, ebenso die 18 "push rXX" wenn interleave() eine
Funktion ist. Als dynamischer Anteil auf dem Stack gibt es in beiden
Fällen "unsigned char d[rows]" von interleave() wobei "rows" durch
"radio.DATALEN" festgelegt wird. Wenn ich das richtig sehe sollte
"radio.DATALEN" nicht größer als "RF69_MAX_DATA_LEN" werden.
Nachtrag, noch etwas zur Aussage im ersten Post des TO: "Die Stelle im
Code wo die Änderung gemacht wurde ist im Sendeteil und wird beim
Empfangen eines Pakets auch überhaupt nicht durchlaufen."
radio_receive() sendet unter Umständen ein "ACK" und damit wird der
Sendeteil auch beim Empfang aufgerufen.
Andy P. schrieb:> AVR-LibC ist mir natürlich ein Begriff, aber in der von mir> benutzten Toolchain stehen alle Utilities im /bin Verzeichnis> und stellen sich mir alle mit Version 7.0.3 vor.
Wie die Versionen zu erhalten sind habe ich oben verlinkt:
Johann L. schrieb:> Welche Compilerversion ist das? Welche LibC? Binutils?> https://avrdudes.github.io/avr-libc/avr-libc-user-manual/FAQ.html#faq_toolchain_version
Zu new, malloc, free, delete: Wird irgendwas davon in ISRs verwendet?
Falls ja so ist das ein Bug weil die AVR-LibC Implementierungen nicht
reentrant sind.
Niklas G. schrieb:> Vielleicht mal versuchen die operatoren mit einer eigenen Variante zu> überschreiben:
1
>void*operatornew(size_tsize)
2
>{
3
>void*res=malloc(size);
4
>if(!res)
5
>__asm__volatile("break");
6
>returnres;
7
>}
Ich frag mich ja gerade ob eine Wrapper-Lib sinnvoll wäre.
Funktionieren würde das so:
Die Lib stellt Wrapper für die Heap-Funktionen. Um diese zu verwenden,
müsste der Anwender:
1) Linken mit -L<path> -lxxx
2) Linken mit -Wl,--wrap,malloc -Wl,--wrap,_Znwj ...
3a) Eine Funktion xxx_putc (char) implementieren, die ein Zeichen
ausgibt (optional).
3b) Eine Funktion xxx_bad (int) implementieren, die ein Problem
vermeldet (optional).
Probleme wären zum Beispiel: malloc returnt NULL, Aufruf von realloc (0,
0), Heap Unterlauf, Heap Überlauf, etc.
Damit könnten einige wenige Probleme identifiziert werden. Die Ausgaben
von 3a) können in eine Datei geschrieben und post morten analysiert
werden. Damit können weitere Probleme detektiert werden, zum Beispiel
wenn ein Block freigegeben wird der nie allokiert wurde. Zudem kann die
Position in der Quelle angezeigt werden, die den kritischen Aufruf
tätigte.
Das ist zwar Größenordnungen weniger als -fsanitize kann, aber
vielleicht besser als garnix?
Codeverbrauch wär bei maximal ca 1k wenn alle Funktionen gewrapped
werden, ansonsten entsprechend weniger. Zusätzlich benötigt RAM (Stack)
bei ca. max. 30 Bytes bei komplizierten Wraps wie realloc.
Funktioniert so:
https://sourceware.org/binutils/docs-2.46/ld.html#index-_002d_002dwrap_003dsymbol
Der Wrapper für new wäre zum Beispiel:
1
.macro .return_address reg
2
REGNO ..regno, \reg
3
#ifdef __AVR_3_BYTE_PC__
4
pop ..regno-0
5
pop ..regno-1
6
pop ..regno-2
7
push ..regno-2
8
push ..regno-1
9
push ..regno-0
10
#else
11
pop ..regno+1
12
pop ..regno+0
13
push ..regno+0
14
push ..regno+1
15
#endif
16
.endm
17
18
ENTRY __wrap__Znwj
19
.weak __wrap__Znwj
20
.return_address r22
21
XJMP __lha_new
22
ENDFUNC
__lha_new ist eine normale C Funktion, so wie __lha_malloc auch.
1
void*__lha_new(size_tsz,ret_addr_tret);
Ein Wrapper für new wird gebraucht um die Calladresse in der Applikation
zu bekommen, denn die Calladresse von __lha_malloc zeigt ja nur auf
__lha_new, ist also nicht sonderlich hilfreich.
Die Funktionen sind qua xxx_putc gesprächig, ein malloc(2) sagt etwa:
1
m@0001d2:02:0262_B=0260#02_T=04
Caller ist Byteadresse 0x1d2, Argument ist 0x2, return ist 0x262. Der
allokierte Block startet bei 0x260 und 0x2 Bytes sind nutzbar. Der
insgesamt allokierte Heap beläuft sich danach auf 0x4 Bytes
(0x260..0x263).
Das in einem post mortem Analyser zu verfolgen ist dann ziemlich easy.
Dieter S. schrieb:> Nachtrag, noch etwas zur Aussage im ersten Post des TO: "Die Stelle im> Code wo die Änderung gemacht wurde ist im Sendeteil und wird beim> Empfangen eines Pakets auch überhaupt nicht durchlaufen.">> radio_receive() sendet unter Umständen ein "ACK" und damit wird der> Sendeteil auch beim Empfang aufgerufen.
Das ist richtig, allerdings erfolgt der Absturz in jedem Fall, auch wenn
kein Acknoledgement vom Sender angefordert wurde. Dieser Teil des Codes
wird also beim Absturz nicht durchlaufen, kann also nicht die URSACHE
sein. Ich teste hier mit Sendern, die kein ACK anfordern. Einer, den ich
auf Knopfdruck senden lassen kann fordert einen ACK an, und der Absturz
erfolgt immer, nämlich dann wenn irgendein Paket empfangen wurde,
unabhängig vom ACKREQUEST Bit.
Dieter S. schrieb:> Empfängst Du die Daten von einem unverändertem Sender (also mit dem> Original-Code erzeugt) oder sind es Daten die durch die Änderung in> radio_send() erzeugt wurden?
Ich verstehe die Frage nicht. Die Sender senden ein bestimmtes Protokoll
und sind externe, körperlich vom Empfänger unabhängige Einheiten. Der
Empfänger empfängt ein Paket, und versucht anhand des ihm programmierten
Protokolls das Paket zu entschlüsseln. Es ist dabei unerheblich mit
welchem Code der Sender geflasht wurde, da es hier nur um das Protokoll
geht. Einige Sender basieren noch auf einer Minimalfirmware mit
ATtiny84, funktionieren aber noch, da das HF-Protokoll rückwärts
kompatibel ist.
Johann L. schrieb:> Zu new, malloc, free, delete: Wird irgendwas davon in ISRs verwendet?
Nein. Beim Empfang eines Pakets wird ein interrupt handler aufgerufen,
der etwas komplexer ist, da müsste ich etwas tiefer in die Analyse
gehen. Aber in den ISR's selbst gibts kein new, calloc malloc etc.
Der Empfang eines Pakets läuft folgendermaßen:
Der Empfangschip horcht in den Äther, und wenn er etwas entdeckt was
sich wie ein Radiosignal anhört das bekannt vorkommt, synchronisiert er
sich mit dem Signal, und wenn es vernünftig klingt detektiert er es,
lädt die Daten in seinen Puffer und löst einen Interrupt aus. Der
Interrupt Handler kopiert die Daten dann in radio.Data und setzt ein
Flag dass Daten empfangen wurden, die -wahrscheinlich- einem gültigen
Paket entsprechen.
Alle anderen ISR's sind sehr simpel gehalten und aktualisieren eine
globale Variable oder so.
Andy P. schrieb:>> Ich verstehe die Frage nicht.
Du hast eine Änderung in radio_send() gemacht. Verwendest Du Code mit
dieser Änderung um die Pakete zu versenden oder ist es jedes mal der
selbe Code ohne die Modifikation?
Dieter S. schrieb:> Du hast eine Änderung in radio_send() gemacht. Verwendest Du Code mit> dieser Änderung um die Pakete zu versenden oder ist es jedes mal der> selbe Code ohne die Modifikation?
Es ist normalerweise der selbe Code ohne Modifikation, wobei der Bereich
wo ich die kritische Änderung gemacht habe ja nie durchlaufen wird
(logischer Denkfehler beim Erstellen der Firmware). Natürlich habe ich
den Code MIT der Änderung zum Senden getestet, und der sendet ohne
Probleme.
Der Denkfehler beim Schreiben des Programms bestand darin, dass ein
Interleaver ohne FEC sinnfrei ist. Deshalb wollte ich nichts anderes als
die beiden Zeilen entfernen, was dann zu dem beschriebenen Absturz
führt. Im radio.receive() habe ich diese Logik ebenfalls entfernt, aber
ohne jegliche Probleme. Je mehr ich drüber nachdenke, desto bizarrer
wird es.
Bin gerade dabei den Code etwas aufzuräumen, dann poste ich die
Unterschiede vorher/nachher.
Andy P. schrieb:> Bin gerade dabei den Code etwas aufzuräumen
Würde ich nicht machen, das verschleiert solche Fehler gerne mal -
plötzlich funktioniert es, keiner weiß warum, und irgendwann kommt der
Fehler zurück...
Bei solchen schwierigen Problemen versuche ich immer den exakten
Firmwarestand genau so zu sichern, teilweise sogar neu-kompilieren
vermeiden, weil die Reproduzierbarkeit der große Knackpunkt sein kann.
In einer Kopie des Codes kann man dann versuchen einen Minimal-Testcase
zu bauen, also alles unnötige entfernen, idealerweise auch die
Abhängigkeit vom Sender (Empfangsroutine mit Dummy-Daten füttern?),
irgendwann fängt es dann an zu funktionieren - dann kann man den
kritischen Schritt eingrenzen.
Aber: In deinem Fall ist das Problem ja reproduzierbar und du weißt
offenbar genau bei welchem Funktions-Rücksprung es passiert (so ein
Luxus). Ich würde den Debugger anwerfen, einen Breakpoint an genau die
Stelle setzen, und dann Step-By-Step weiterlaufen lassen bis es schief
geht. Ggf. assembly-level Stepping, also jede Assembler-Instruktion
einzeln. Wenn tatsächlich die Rücksprung-Anweisung ("ret") an die
falsche Adresse springt, lass dir das Stackframe ausgeben und schau
womit die Rücksprung-Adresse überschrieben wurde. Vielleicht kommen dir
die Daten bekannt vor und du weißt welches Array übergelaufen ist. Wenn
nicht: Mach einen Breakpoint an den Anfang der Funktion, wenn dort
angehalten setze einen Data-Watchpoint auf genau die Stelle im Stack wo
die Rücksprung-Adresse steht, und lass laufen. Dann solltest du sofort
die Stelle finden, wo der Stack fälschlich überschrieben wurde. Das
ganze natürlich im Release-Modus.
Mit etwas Glück hast du den Fehler so in ein paar Minuten gefunden; Code
aufräumen kann man danach immer noch machen. Ich versuche solche Fehler
immer sofort zu finden; spätere Änderungen könnten den Fehler
verschleiern bis er irgendwann zurück kommt, aber dann vielleicht nicht
mehr so gut reproduzierbar. Mein Motto: Alle reproduzierbaren Fehler
sind findbar ;-)
Das lst File ist auch seltsam:
Ausführbarer Code reicht bis 8d3d:
1
00008d3c <__stop_program>:
2
8d3c: ff cf rjmp .-2 ; 0x8d3c <__stop_program>
.data LMA startet bei 0x9444:
1
000002ea <__do_copy_data>:
2
...
3
2f0: e4 e4 ldi r30, 0x44 ; 68
4
2f2: f4 e9 ldi r31, 0x94 ; 148
5
2f4: 02 c0 rjmp .+4
6
2f6: 05 90 lpm r0, Z+
7
2f8: 0d 92 st X+, r0
8
...
Aber was steht im Flash an 8d3e..9443? Das Map-File sollte Klarheit
schaffen.
Bei Emulation avrxmega3 würde da .rodata liegen, aber diese Emulation
wird erst ab GCC v8 / Binutils v2.29 unterstützt. Mit älteren Tools
muss Emulation avrxmega2 verwendet werden, welche .rodata ins RAM
lokatiert.
Mit GCC 7.3 würde .data LMA also direkt auf den ausführbaren Code
folgen, tut's aber nicht...
Welche Emulation wird überhaupt verwendet? Sieht man mit
1
$ avr-gcc -mmcu=atmega4808 -print-multi-directory
Falls ein Device-Pack verwendet wird, müssen die entsprechenden Optionen
dem obigen Kommando zugefügt werden!
Da Kommandozeilenkommandos auf Windows offenbar extrem schwierig
auszuführen sind: Die Emulation sieht man auch im specs-atmega4808.
Es gibt also ein Symbol __RODATA_PM_OFFSET__mit dem Wert 0x4000, was
aber nur für Emulation avrxmega3 definiert wird, welche mit GCC 7.3
NICHT unterstützt wird!
Kann es sein, dass ein Specs File verwendet wird, dass nicht zu den Tool
Versionen passt?
Zwar erlauben Specs Files das Hinzufügen von Device-Support ohne die
Tools selbst anfassen zu müssen, Specs Files sind aber NICHT 100%
unabhängig von den Versionen der Tools!
Siehe zum Beispiel https://gcc.gnu.org/wiki/avr-gcc#AVR_GCC_v5_and_newer
Supporting "unsupported" Devices → AVR GCC v5 and newer
1
Spec files allow to add support for new devices without the
2
need to change the binaries of the compiler, the assembler or the
3
linker. Spec files may depend on the versions of GCC and Binutils,
4
and using an incompatible spec file may lead to errors or wrong or
5
sub-optimal code. For example, this is the case when newer tool
6
versions support more or different options, but a spec file doesn't
7
reflect that.
8
9
As the tools evolve, new features and command-line options are being
10
added. When porting a device-specs file across one of the following
11
features and versions, extra care must be taken:
12
13
o -mmcu=avrxmega3 (GCC v8, PR81072), -mavrxmega3 (Binutils v2.29, PR21472)
14
15
o -mgas-isr-prologues (GCC v8, PR81268), -mgcc-isr and
Jörg W. schrieb:> Nick schrieb:>> Wie man das ausschließen/nachweisen kann: Siehe oben.>> Mach doch mal bitte vor, wie du den Linkerscript dafür aufbauen willst.
Dafür brauchts keinen Linkerscript. Es gibt verscheidene Methoden
festzustellen, dass der Stack in .text oder .data (nicht) reinläuft.
Google hilft, ich mach mir nicht die Arbeit euch das zu erklären.
Nochmal zu Stack des ATmega4808:
Er hat keinen Hardware stack. Schon mal gut.
Weil ich MPLAB-X sowieso installiert hab, hab ich mir angesehen, ob man
die Stackgröße festlegen kann. Geht nicht mit dem XC8. Warum ich nur den
XC8 auswählen kann und nicht den avr-gcc ist mir rätselhaft aber auch
egal. Mit dem avg-gcc scheint es zu gehen. Ob das dann auch konsequent
umgesetzt wurde weiß ich nicht. Aber weder werde ich da Zeit reinstecken
das zu belegen, noch interessiert es mich.
Aus der Sicht von C:
Sobald man malloc, free und realloc verwendet, muss man die Stackgröße
festlegen können. Denn es gibt drei Abchnitte im RAM: statisch
allokierte Daten (Variablen), heap und stack. Der heap darf nicht in den
stack reinlaufen, also muss der heap begrenzt werden können und somit
die heap-Größe fetgelegt werden. Und das geht nur, wenn die Stackgröße
bekannt ist. Wenn die Stackgröße nicht bekannt ist, kann man auch nicht
mehr sinnvoll dynamisch allokieren. Kann man hinfummeln, wird ein
Gebastle auf das man sich bewusst einlassen will. Ich nicht.
Sobald man C++ verwendet, riskiert man, dass irgendwo in der stl ein
malloc steht. Kann man machen, muss man nicht.
Aus meiner Sicht ist das eine ziemliche Krücke. Das Kernproblem ist der
Compiler. Passende Linkerscripts kann MPLAB-X zuverläsig generieren.
Wenn man sich auf den AVR einlässt, lässt man sich auch auf die
Stack/Heap-Problematik ein. Und muss halt dann auch Sorge tragen, dass
das nicht schief geht. Ohne es probiert zu haben, es gibt für den
ATmega4808 einen "Stack advisor".
Für das doppelte Geld gibts im 48-Pin-Gehäuse (als das andere Ende des
Spektrums) PIC32 auf denen man auch sinnvoll C++ einsetzen kann.
Einen Fehler als bizarr zu bezeichnen, ist an sich schon bizarr. Erst
recht, wenn man nicht weiß, wo genau der Fehler ist und wie er ensteht.
Als bizarr würde ich durchgehen lassen, wenn im constructor absichtlich
eine exception geworfen wird und der compiler das nicht richtig
handhabt. Hatte ich mal, das zu finden und den Fehler im compiler
nachzuweisen war schwierig.
Wenn man schon von Anfang an den Compiler verdächtigt, es das
tatsächlich bizarr und zeigt mir, dass der TO wenig Erfahrung hat. Darf
er haben, wir alle haben mal angefangen. "Ich find den Fehler nicht,
bitte helft mir" wäre eine passende Überschrift gewesen.
Das nur zur Einstimmung.
Zum code, der bei mir einfach nur Stirnrunzeln erzeugt:
// num_errors kann 1 sein, aber trotzdem wird true zurückgeliefert. Hää?
21
// Ist 1 Fehler kein Fehler? Wenn ja, dann explizit als Kommentar schreiben.
22
23
// ----
24
charerrors;
25
uint8_terrors// kann nie negativ werden.
26
// Besser uint_fast8, wenn man schon so auf Kante nähen will.
27
28
// ----
29
dest[2]=this->hamming_codes[value&0xf];
30
dest[1]=this->hamming_codes[(value>>4)&0xf];
31
dest[0]=this->hamming_codes[(value>>8)&0xf];
32
33
// Gibt es eine sinnvolle Erklärung den index von 2 ... 0 laufen zu lassen?
34
35
// ----
36
for(bytei=0;i<num_bytes;i++)
37
{
38
if(this->decode_byte(encoded_msg[i],c))
39
{
40
decval<<=4;
41
decval|=c;
42
}
43
else
44
{
45
success=false;
46
}
47
}
48
49
// Gibt es einen sinnvollen Grund weiter zu machen, auch wenn schon
50
// festgestellt wurde, dass das daneben gegangen ist (success = false)
51
// und decval nicht mehr weiter verändert wird?
52
// ->
53
returnfalse;// statt das success sinnlos mitzuschleppen und überhaupt zu verwenden.
Ich weise nochmal auf den Debugger hin, abhängig vom verwendeten
Programmer und der IDE. Dass der TO den verwendet hat und die exakte
Stelle wo es kracht gefunden hat, konnte ich bis jetzt nicht lesen. Ist
mir aber auch zunehmend egal.
Das solls gewesen sein. MMn sollte der TO zuerst mal an seiner
Einstellung zum Programmieren arbeiten, damit würde er deutlich weiter
kommen.
Nick schrieb:> Mit dem avg-gcc scheint es zu gehen.
Kannst du mal das selbe Programm mit unterschiedlichen Stack-Größen
kompilieren und zeigen, was das für Auswirkungen auf die erzeugte .bin
Datei hat?
Nach ein paar intensiveren Blicken auf den Assembler Code von hier
Beitrag "Re: Bizarrer Firmware crash - ATmega4808. Wie Debuggen?"
bin ich mir ziemlich sicher dass der Source Code zu diesem Assembler
Code einige wichtige Unterschiede zu dem Source Code auf Github hat, auf
den der TO verweist.
Ein Beispiel, Speicherbelegung im Constructor per "new" für "DATA" vom
Github Source Code:
//int RSSI; //most accurate RSSI during reception (closest to the reception)
8
//static volatile int16_t FEI;
9
static volatile byte _mode; //should be protected?
10
static volatile byte TEMPREG;
11
//int fdev;
12
//long vcc_dac;
13
14
...
Wer es selber nachsehen möchte: "_GLOBAL__I_65535_0_TiNo2V71.cpp.o.6447"
in beiden Varianten des Assembler Code, dort dann in etwa so:
1
; Z auf RAM Adresse 0x2B00 (Variable "radio")
2
3
ldi r30, 0x00
4
ldi r31, 0x2B
5
6
; radio.DATA auf RAM Adresse 0x2B17
7
8
ldi r24, 0x17
9
ldi r25, 0x2B
10
std Z+12, r24
11
std Z+13, r25
Solange der TO es also nicht schafft den kompletten Source Code für das
zu liefern was das Problem verursacht bzw. funktioniert braucht man wohl
kaum über mögliche Ursachen des Problems diskutieren.
Nachtrag: Was der FEC Codec macht ist für das Problem irrelevant da der
Code dafür gar nicht im Assembler Code vorhanden ist (FEC ist nicht
konfiguriert).
Rbx schrieb:> In Assembler hast du das Problem nicht -
Dafür 1000 andere.
> und darüberhinaus einen viel> besseren Überblick über Zusammenhänge,
Genau, weil Spaghetticode aus Mnemonics ja bekanntlich sooo
übersichtlich ist...
>// Gibt es einen sinnvollen Grund weiter zu machen, auch wenn schon
65
>// festgestellt wurde, dass das daneben gegangen ist (success = false)
66
>// und decval nicht mehr weiter verändert wird?
67
>// ->
68
>returnfalse;// statt das success sinnlos mitzuschleppen und überhaupt
69
>zuverwenden.
70
>
Ja, es gibt einen sinnvollen Grund. Es handelt sich hier um einen String
von Rohdaten, die pro Byte 4 gültige Datenbits auswerfen. Ich möchte
hier nicht die in dem String codierten Daten komplett verwerfen, nur
weil ein einzelnes Nibble falsch detektiert wurde. Allerdings, und da
möchte ich dir für den Hinweis danken, sollte dann auch eine
Fehlermeldung dergestalt ausgegeben werden, aus der ersichtlich ist an
welcher Stelle sich ein falsches Nibble befindet.
>> Ich weise nochmal auf den Debugger hin, abhängig vom verwendeten> Programmer und der IDE. Dass der TO den verwendet hat und die exakte> Stelle wo es kracht gefunden hat, konnte ich bis jetzt nicht lesen. Ist> mir aber auch zunehmend egal.
Der TO hat die exakte Stelle gefunden wo es kracht, allerdings nicht mit
dem Debugger, sondern über assert(). Der Debugger hat im ersten Anlauf
versagt, weil der Code im DEBUG Modus kompiliert wurde und dies den
Fehler komplett kaschiert hat. Erst der Hinweis dass man auch im RELEASE
Mode einen Debugger verwenden kann, hat weiter geholfen. Allerdings
zickt mein Setup in diesem Modus, und ich muss mir sehr viel mehr Zeit
nehmen das hinzubekommen, welche ich erst wieder im August haben werde.
> Das solls gewesen sein. MMn sollte der TO zuerst mal an seiner> Einstellung zum Programmieren arbeiten, damit würde er deutlich weiter> kommen.
Der TO hat Programme von professionellen Programmierern gesehen die
wesentlich chaotischer dahergekommen sind, absolut unkommentiert waren
und manches waren unlesbare Spaghetti. Der TO hat es erlebt dass ein
Praktikant einen Code abgeliefert hat, den die Profis dann unverändert
übernommen haben, und darauf ihren eigenen Stiefel aufgesetzt haben,
weil einfach keine Zeit war, den Code aufzuräumen, weil er einfach gut
funktioniert hat.
Ich bin motiviert, ehrgeizig, aufgeschlossen, wissbegierig, aber mit
Sicherheit kein professioneller Programmierer. Mit welcher "Einstellung
zum Programmieren" komme ich deutlich weiter? Kannst du das bitte etwas
präzisieren?
Andy P. schrieb:> Allerdings> zickt mein Setup in diesem Modus
Definiere "zickt"? Es ist normal dass beim Debuggen im Release-Modus die
ausgeführte Zeile wild herum springt, weil der Compiler die einzelnen
Anweisungen umsortiert. Es funktioniert aber trotzdem.
Da der TO es ja leider nicht schafft den tatsächlichen Source Code zu
liefern bleibt bisher nur der Blick in den Assembler Code.
Ein Problem was es dort vermutlich gibt: Sowohl in radio_receive() als
auch in radio_send() wird Xxtea.crypter() ohne den Constructor Xxtea()
aufgerufen. Entweder fehlt der (im Github Source Code gibt es ihn) oder
der Compiler hat ihn weggelassen. Das Problem dabei ist dass die Adresse
des Datenbuffer durch den Constructor festgelegt wird. Es gibt zwar
einen einmaligen Aufruf des Constructors bei der Initialisierung, bei
dem auch der Datenbuffer gesetzt wird. Aber es gibt auch noch andere
Stellen im Code die Xxtea mit dem Constructor verwenden und dabei den
gespeicherten Datenbuffer verändern.
Die Verschlüsselung ist sehr wahrscheinlich per Cfg.EncryptionEnable
eingeschalten und wird daher wohl auch verwendet.
Und noch mal zu dem FEC Codec, da muss ich mich korrigieren: es gibt den
FEC Codec im Assembler Code, der Compiler hat ihn "inline" erzeugt daher
sieht man ihn nicht sofort. Der FEC Codec sollte passen, zumindest wenn
man den Assembler Code dazu im Simulator laufen lässt sieht es so aus
als ob er so arbeitet wie er soll (das trifft auch auf den "inline"
erzeugen Code für interleave() zu).
Veit D. schrieb:> Damit macht ihr mich ja arbeitslos.
Wieso das denn?
> Ist der 8.5.1er deine Hand optimierte Toolchain?
Nicht optimiert. Aber mit einigen ausgewählten, rückportierten PRs à la
README.
Johann L. schrieb:> Veit D. schrieb:>> Damit macht ihr mich ja arbeitslos.>> Wieso das denn?
Das ist nicht wörtlich gemeint. Ich stelle Toolchains im Arduino Forum
mittels 3. netten User bereit. Könnte ich mir demzufolge schenken. ;-)
Müssen wir jetzt nicht weiter auseinander nehmen. Ich baue weiterhin
eigene Toolchains, keine Sorge. :-)
>> Ist der 8.5.1er deine Hand optimierte Toolchain?>> Nicht optimiert. Aber mit einigen ausgewählten, rückportierten PRs à la> README.
Aha.
Johann L. schrieb:> https://gcc.gnu.org/PR101188> Nen Build für Windows gibt's zum Beispiel hier:> https://sourceforge.net/projects/winavr/files/avr-gcc/
Krass! Also ich muss gestehen, ich bin total geplättet. Ich habe die
Toolchain von obigem Link in Platformio installiert, und, was soll ich
sagen, der Totalabsturz ist beseitigt, der Empfänger scheint zu
funktionieren (Test läuft)!
Ein Problem ist wohl, dass Platformio die Toolchains praktisch nie
aktualisiert, und die Version 7.3.0 die von allen Tools ausgegeben wird
ist uralt (von 2017).
Ich bekomme zwar eine Menge Warnungen, aber viele davon kommen mir
bekannt vor. Die muss ich jetzt Stück für Stück abarbeiten.
VIELEN DANK AN ALLE!!!
Habe eine Menge dazugelernt.
Andy P. schrieb:> Johann L. schrieb:>> https://gcc.gnu.org/PR101188>>> Nen Build für Windows gibt's zum Beispiel hier:>> https://sourceforge.net/projects/winavr/files/avr-gcc/>> Krass! Also ich muss gestehen, ich bin total geplättet. Ich habe die> Toolchain von obigem Link in Platformio installiert, und, was soll ich> sagen, der Totalabsturz ist beseitigt, der Empfänger scheint zu> funktionieren (Test läuft)!
Noch ne Anmerkung dazu: Der ATmega4808 wird direkt von den Tools
unterstützt, es brauch also kein extra Device-Pack für den ATmega4808.
Gleiches gilt für andere Versionen wie v15.3.
Falls ein Device ohne extra Device-Pack unterstützt wird, dann würde ich
von solchen extra Packs abraten.
Zu avr-gcc-8.5.1_2025-07-14_mingw32.tar.xz ist anzumerken, dass es keine
definierte Version der AVR-LibC enthält, sondern den Stand der
2025-07-14 aktuell war. Neueste Release ist die v2.3.2, also wäre es
gut zu checken ob der 8.5.1 irgendwas (für dich) wichtiges abgeht :-/
v2.3 Branch: https://github.com/avrdudes/avr-libc/commits/avr-libc-2_3/
Jörg W. schrieb:> Krasse Analyse, Dieter! Dafür dürfte das Attribut "bizarr" mehr als> gerechtfertigt sein.
Dem kann ich noch nur anschließen. Daumen hoch. Wirklich erstaunlich wie
und dass du (Dieter) das gefunden hast.
Zu der Analyse des Assembler Codes: nach ein paar Tests mit einem
Simulator war klar dass die "inline" erzeugte interleave() Funktion (das
war ja die größte Veränderung am Assembler Code nach der Modifikation)
und auch der in beiden Fällen "inline" erzeugte FEC Codec funktioniert.
Man kann das zwar auch ohne Simulator im Assembler Code verifizieren
aber zusammen mit einem Simulator geht es einfacher.
Damit musste vermutlich das "Umfeld" in radio_receive() für das Problem
verantwortlich sein, Probleme mit der Speicherbelegung (sowohl Heap als
auch Stack) waren sehr wahrscheinlich nicht das Problem, siehe hier:
Beitrag "Re: Bizarrer Firmware crash - ATmega4808. Wie Debuggen?"
Wenn man an diesem Punkt angekommen ist fällt der Fehler dann auch
relativ schnell auf, auch hier hilft ein Simulator.
Den Fehler hätte man wohl auch im Debugger gefunden, aber nur dann wenn
man sich dabei auch den Assembler Code ansieht, es ist ja ein
Compilerfehler der falschen Assembler Code erzeugt.
Johann L. schrieb:> Andy P. schrieb:>> Johann L. schrieb:>>> https://gcc.gnu.org/PR101188>>>>> Nen Build für Windows gibt's zum Beispiel hier:>>> https://sourceforge.net/projects/winavr/files/avr-gcc/>>>> Krass! Also ich muss gestehen, ich bin total geplättet. Ich habe die>> Toolchain von obigem Link in Platformio installiert, und, was soll ich>> sagen, der Totalabsturz ist beseitigt, der Empfänger scheint zu>> funktionieren (Test läuft)!>> [...] Zu avr-gcc-8.5.1_2025-07-14_mingw32.tar.xz ist anzumerken, dass> es keine definierte Version der AVR-LibC enthält, sondern den Stand der> 2025-07-14 aktuell war. Neueste Release ist die v2.3.2, also wäre es> gut zu checken ob der 8.5.1 irgendwas (für dich) wichtiges abgeht :-/>> v2.3 Branch: https://github.com/avrdudes/avr-libc/commits/avr-libc-2_3/
Hab mal eine aktuellere Version generiert:
avr-gcc-8.5.1_2026-07-22_mingw32.tar.xz
1
$ cat ABOUT.txt
2
=== GCC ===
3
git: https://github.com/sprintersb/avr-gcc-8.git
4
branch/tag: releases/gcc-8
5
hash: 9dfb324740db6c52d2d495ee2aabf47f957899e5 (2026-07-20) README.md: PR101188 is fixed in v13.5.
6
7
=== Binutils ===
8
git: git://sourceware.org/git/binutils-gdb.git
9
branch/tag: binutils-2_46_1
10
hash: 5e56594815854de5eca35c7c04b11705d0f19c02 (2026-06-08) This is the 2.46.1 release
11
12
=== AVR-LibC ===
13
git: https://github.com/avrdudes/avr-libc.git
14
branch/tag: avr-libc-2_3_2-release
15
hash: 490384175ced9b387a1c76d86f6ee7f0cff70109 (2026-06-05) configure.ac: Set version for v2.3.2 release.
Johann L. schrieb:> Zu avr-gcc-8.5.1_2025-07-14_mingw32.tar.xz ist anzumerken, dass es keine> definierte Version der AVR-LibC enthält, sondern den Stand der> 2025-07-14 aktuell war. Neueste Release ist die v2.3.2, also wäre es> gut zu checken ob der 8.5.1 irgendwas (für dich) wichtiges abgeht :-/
ja, irgendwas fehlt dem Linker, kann es gerade nicht lokalisieren:
1
c:/users/myname/.platformio/packages/toolchain-atmelavr@8.5.1/bin/../lib/gcc/avr/8.5.1/../../../../avr/bin/ld.exe: c:/users/myname/.platformio/packages/toolchain-atmelavr@8.5.1/bin/../lib/gcc/avr/8.5.1/../../../../avr/lib/avrxmega3\libatmega4808.a(call_main.o): in function `__call_main':
wenn ich 8.5.1 von sourceforge herunterlade, schaltet der Browser
automatisch auf die Liste der letzten Releases um, ganz oben 15.3.0.
Ich habe beide installiert, 15.3.0 funktioniert. Die offizielle
Toolchain von Microchip
https://www.microchip.com/en-us/tools-resources/develop/microchip-studio/gcc-compilers
ist Version 15.1.0, welche ich ebenfalls installiert habe, und die
funktioniert ebenfalls. Versionen 15.x.x erzeugen jede Menge Warnungen,
die wohl auf einen anderen Bug zurückzuführen sind, z.B.
1
.pio\libdeps\ATmega4808_RX\BME280\src\BME280.h:229:17: warning: 'virtual void BME280::setSettings(const Settings&)' was hidden [-Woverloaded-virtual=]
Andy P. schrieb:> ja, irgendwas fehlt dem Linker, kann es gerade nicht lokalisieren:> (.init9+0x0): undefined reference to `main'
Offensichtlich fehlt ihm ein main() …
Andy P. schrieb:> Johann L. schrieb:>> Zu avr-gcc-8.5.1_2025-07-14_mingw32.tar.xz ist anzumerken, dass es keine>> definierte Version der AVR-LibC enthält, sondern den Stand der>> 2025-07-14 aktuell war. Neueste Release ist die v2.3.2, also wäre es>> gut zu checken ob der 8.5.1 irgendwas (für dich) wichtiges abgeht :-/>> ja, irgendwas fehlt dem Linker, kann es gerade nicht lokalisieren:>
Sieht aus wie https://github.com/avrdudes/avr-libc/issues/1060 was in
AVR-Libc 2.3.1 behoben wurde. Trat auf, wenn main in einem Archiv
definiert wird wie es z.B. Arduino macht.
Mit der avr-gcc-8.5.1_2026-07-22_mingw32.tar.xz sollte das nicht mehr
auftreten.
> wenn ich 8.5.1 von sourceforge herunterlade, schaltet der Browser> automatisch auf die Liste der letzten Releases um, ganz oben 15.3.0.> Ich habe beide installiert, 15.3.0 funktioniert. Die offizielle> Toolchain von Microchip> https://www.microchip.com/en-us/tools-resources/develop/microchip-studio/gcc-compilers> ist Version 15.1.0, welche ich ebenfalls installiert habe, und die> funktioniert ebenfalls. Versionen 15.x.x erzeugen jede Menge Warnungen,> die wohl auf einen anderen Bug zurückzuführen sind, z.B.>
Vielen Dank für die Zusammenfassung. Sehr interessant, ich hatte mir
noch nie um Toolchains Gedanken gemacht, nur genutzt.
Johann L. schrieb:> Mit der avr-gcc-8.5.1_2026-07-22_mingw32.tar.xz sollte das nicht mehr> auftreten.
Diese Version scheint ja von heute zu sein. Wo kann ich sie
herunterladen?
Johann L. schrieb:> Wo sich nix geändert hat ist der Stand der libstdc++ Unterstützung, die> hat's mit Arduiono oder PIO. Bislang war es wohl noch niemandem wichtig> genug, da was zum GCC beizutragen...
Hallo,
ich weiß nicht ob es "nur" unglücklich formuliert ist. Für 8Bit AVR-gcc
gibt es im gcc keine libstdc++ Unterstützung. Was hat Arduino oder
PlatformIO (PIO) damit zu tun? Es wäre eine Aufgabe vom µC Hersteller
sich da einzubringen. Es gibt jedoch eine Möglichkeit sie sich
"einzubauen". Die bislang einzig funktionierende und das unabhängig
einer IDE.
https://github.com/modm-io/avr-libstdcpp
Man muss es nur überlegt verwenden.
Veit D. schrieb:> Die bislang einzig funktionierende und das unabhängig> einer IDE.> https://github.com/modm-io/avr-libstdcpp> Man muss es nur überlegt verwenden.
Ist aber halt nicht vollständig. Ob eine vollständige C++ lib mit
exceptions und allem anderen auf einem AVR sinnvoll ist, ist natürlich
diskutabel.
Oliver
Veit D. schrieb:> Johann L. schrieb:>>> Wo sich nix geändert hat ist der Stand der libstdc++ Unterstützung, die>> hat's mit Arduiono oder PIO. Bislang war es wohl noch niemandem wichtig>> genug, da was zum GCC beizutragen...>> ich weiß nicht ob es "nur" unglücklich formuliert ist. Für 8Bit AVR-gcc> gibt es im gcc keine libstdc++ Unterstützung.
sag ich doch.
> Was hat Arduino oder PlatformIO (PIO) damit zu tun?
Ich dächt Arduino hätte zumindest minimale STL Unterstützung. Danke für
die Richtigstellung, dass dem nicht so ist.
> Es wäre eine Aufgabe vom µC Hersteller sich da einzubringen.
Ist nett wenn er es macht, aber MC hat da wohl keinerlei Ambitionen.
Embecosm hatte zu Atmel Zeiten mal nen Versuch gestartet aber recht bald
wieder aufgegeben.
> Es gibt jedoch eine Möglichkeit sie sich "einzubauen".> Die bislang einzig funktionierende und das unabhängig> einer IDE. https://github.com/modm-io/avr-libstdcpp
Das ist lediglich ein Anhängsel von modm, siehe zum Beispiel
https://github.com/modm-io/avr-libstdcpp/issues/39#issuecomment-2869012946>> So if I understand correctly the avr-libstdcpp project is not>> for stand-alone usage with avr-g++ but rather an appendage of modm?> Yes correct.Oliver S. schrieb:> Ob eine vollständige C++ lib mit exceptions und allem anderen> auf einem AVR sinnvoll ist, ist natürlich diskutabel.
Die eigentliche Frage ist, welche Rolle Exceptions in der libstdc++ bzw.
der libsuppc++ spielen. Womöglichwirk kein Maintainer die libstdc++ für
AVR aktivieren, ohne dass das Backend Exceptions unterstützt.
Dann ist die Frage, ob -f[no-]exceptions ohne Rückwirkung aufs ABI
möglich ist. Falls nicht würde sich die Anzahl der Multilibs von
derzeit 57 auf über 100 verdoppeln.
Und sich aus der libstdc++ leichte Rosinen wie <array> oder
<type_traits> rauszupicken geht auch nicht.
Dann gibt's noch https://github.com/modm-io/avr-libstdcpp/issues/23
aber da tut sich auch nix. Und es würde AVR als freestanding behandeln,
was weit wehiger ist als AVR GCC + AVR-LibC wirklich können.
Johann L. schrieb:> Die eigentliche Frage ist, welche Rolle Exceptions in der libstdc++ bzw.> der libsuppc++ spielen.
Exceptions funktionieren nichtmal mit dem arm-none-eabi-gcc für Cortex-M
vernünftig, weil da eine gigantische Support-Library rein gezogen wird.
Vermutlich könnte man da was effizientes für implementieren, aber es
stellt sich die Frage nach dem Nutzen:
Exceptions sind ja als einheitlicher Mechanismus zur Weitergabe von
unerwarteten(!) Fehlern gedacht, damit jede Ebene einer
Software-Architektur den Fehler entsprechend sauber abhandeln kann ohne
dass das Programm irgendwie undefiniert abstürzt o.ä. Für normale
Hosted-Umgebungen funktioniert das wunderbar und effizient, natürlich
insbesondere in Kombination mit RAII. Im Firmware-Bereich gibt es solche
Fehler aber einfach kaum, und wenn doch, kann man sie meistens sowieso
nicht sinnvoll abhandeln, sondern höchstens "sauber crashen" (sicheren
Zustand betreten, Software-Reset o.ä. ).
Die Art von Fehler, mit der man in Firmware zu tun hat, sind eher
erwartete Fehler, deren Behandlung aber expliziter Teil der
Funktionalität und des Programmablaufs ist, z.B. Motor zu heiß,
Fehlerbehandlung ist "Motor abschalten". Die Software soll dann aber
normal weiterlaufen und ggf. den Vorgang neu starten können (z.B. wenn
Motor abgekühlt). Gerade weil Firmware oft asynchron ist oder einfach
nur eine endlose main()-Loop mit "kombinatorischer Logik", bringen
Exceptions hier praktisch nichts.
Vorstellbar wäre es vielleicht für Prototyping/Debugging, damit
LowLevel-Treiber Fehler durchreichen können, aber da würde vielleicht
ein gutes Logging-Framework reichen, oder ein globaler Event-Handler mit
Breakpoint.
Vielleicht in Kombination mit Coroutinen...?
Niklas G. schrieb:> Johann L. schrieb:>> Die eigentliche Frage ist, welche Rolle Exceptions in der libstdc++ bzw.>> der libsuppc++ spielen.>> Exceptions funktionieren nichtmal mit dem arm-none-eabi-gcc für Cortex-M> vernünftig, weil da eine gigantische Support-Library rein gezogen wird.
Ich geh mal davon aus dass das ein Minimal-Support ist, der erforderlich
für GCCs libstdc++ ist. Eine Möglichkeit für Unwind-Info ist DWARF,
d.h. es landet DWARF Debuginfo auf dem Target und muss zur Laufzeit
ausgewertet werden. Das ist natürlich extrem teuer.
Weil avr-g++ Stand dato keine Named Address Spaces unterstützt, würde
der ganze DWARF Kladderadatsch im RAM landen. Nicht besser sähe es aus
wenn die Unwind-Info auf dem Stack liegt — in welchem Format auch immer.
Und jede Funktion zwischen try und catch brauch ein Exception Handler
Landing Pad um Objekte zu Dekonstruieren. Das gilt auch für Funktionen,
die selbst nix mit Exceptions machen.
> Vermutlich könnte man da was effizientes für implementieren,
War wohl noch niemandem wichtig genug.
> Vorstellbar wäre es vielleicht für Prototyping/Debugging, damit> LowLevel-Treiber Fehler durchreichen können, aber da würde vielleicht> ein gutes Logging-Framework reichen, oder ein globaler Event-Handler> mit Breakpoint.
Man kann Fehler ja direkt ausgeben: UART, Pin wacheln, LED blinken,
Beeper -- --- .-. ... . -. .-.. .- ... ... . -.
> Vielleicht in Kombination mit Coroutinen...?
Jau, wenn schon Bloat dann aber richtig!!!
Coroutinen sind stackless → malloc
Johann L. schrieb:> Man kann Fehler ja direkt ausgeben: UART, Pin wacheln, LED blinken,> Beeper -- --- .-. ... . -. .-.. .- ... ... .
Sehe ich eigentlich auch so. Auf einem AVR ein exception Handling. Kann
man machen ja....
Es gibt ja auch noch Debugger.
Auf einem ARM Cortex oder so gibt es dafür die Hard Fault Exception. Die
ist für sowas schon sehr hilfreich so dass auch hier exception Handling
via Cpp übrig ist meiner bescheidenen Meinung nach.
Gut, alles fängt das auch nicht ab.
Und überall dieses try/Error Zeugs im Code finde ich persönlich
unübersichtlich.
Johann L. schrieb:> Eine Möglichkeit für Unwind-Info ist DWARF,> d.h. es landet DWARF Debuginfo auf dem Target und muss zur Laufzeit> ausgewertet werden. Das ist natürlich extrem teuer.
Bei meinen Versuchen waren die DWARF Daten nichtmal da, es wäre also nur
um das Unwinding gegangen, und das war schon fast unmöglich.
Johann L. schrieb:> Und jede Funktion zwischen try und catch brauch ein Exception Handler> Landing Pad um Objekte zu Dekonstruieren. Das gilt auch für Funktionen,> die selbst nix mit Exceptions machen.
Ja, ich glaub diese Personality Funktionen sind verbrauchsmäßig nicht
unbedingt schlimmer als äquivalente Funktionen mit Auswertung von
Rückgabewerten usw.; aber die Runtime-Library die diese aufruft ist
fett.
Johann L. schrieb:> Man kann Fehler ja direkt ausgeben: UART, Pin wacheln, LED blinken,> Beeper -- --- .-. ... . -. .-.. .- ... ... . -.
Die Frage ist weniger nach dem Ausgeben, sondern dem Zusammensammeln der
Informationen; wenn man viele Ebenen an Libraries kombiniert hat, muss
man bei komplett unerwarteten Fehlern erstmal rausfinden, wo es geknallt
hat.
Johann L. schrieb:> Jau, wenn schon Bloat dann aber richtig!!!>> Coroutinen sind stackless → malloc
Eigentlich war die Idee von Coroutinen ja, den Bloat von vollwertigen
Threads loszuwerden. Klar sind die existierenden
Coroutine-Implementationen nicht Embedded-optimiert, auch hier wäre es
IMO sehr interessant darüber nachzudenken Coroutinen (ggf. in
abgespeckter Form) z.B. auf den Cortex-M zu bringen. Ich glaub das
könnte ziemlich nützlich sein - wenn man sich anschaut, wie einfach und
elegant man in Kotlin Coroutines asynchrone Abläufe modellieren kann,
will man nie wieder Zustandsdiagramme sehen...
900ss schrieb:> Auf einem ARM Cortex oder so gibt es dafür die Hard Fault Exception.
Diese Hardware-Exceptions haben nichts mit den Software-Exceptions zu
tun, das ist nur eine blöde Namensüberschneidung. Die Hardware-Exception
könnte höchstens als Auslöser für das Unwinding von
Software-Exceptions auftreten, wobei die typischen Hardfault-Probleme
dann aber gar nicht sinnvoll durch Software bzw. Software-Exceptions
behandelbar sind außer "sauber abwürgen". Aber eine ganze Menge von
Fehlern führt natürlich gar nicht erst zum Hardfault.
900ss schrieb:> Und überall dieses try/Error Zeugs im Code finde ich persönlich> unübersichtlich.
Wenn der ganze Code damit voll ist, hat man Exceptions falsch
verstanden. Ein gutes Programm hat nur ganz wenige try-catch-Blöcke,
dafür aber Destruktoren für Ressourcen-Freigabe, die man aber sowieso
braucht(!). Dadurch haben die meisten Funktionen überhaupt keine
Fehlerbehandlung und sind wunderbar kurz, kürzer als bei der Nutzung
klassischer Rückgabe-Fehlercodes.
Rbx schrieb:> Johann L. schrieb:>> Jau, wenn schon Bloat dann aber richtig!!!>> Ich musste so lachen. Ich hatte mich gleich mal umgesehen, fand u.a. das> hier:> https://www.e-lab.de/AVRco/index_en.html> https://github.com/ETLCPP/etl - kenne das jetzt aber nur oberflächlich,> und habe keine Erfahrung damit gemacht.
Also google und dann was rausgehauen ohne auch nur ansatzweise to
eruieren ob das auf AVR läuft...
Unter der Haube wird z.B. __sync verwendet, funktioniert also schon mal
nicht mit AVR GCC.
Niklas G. schrieb:> Was hat das mit Coroutinen zu tun?
Frag dich das doch selber. Außerdem könntest du ja auch deine Coroutinen
selber frisieren, bzw. für 8-Bit-MCs optimieren. (naja, wenn die im
Multitasking gut sind) C++ ist da auch schon im Vergleich ein großer
LKW-Zug auf der Autobahn, der für viele Staus sorgt. Naja, und wenn du
Lust hast, kannst du dir ja auch einen GC hinprogrammieren - den braucht
aber auch nicht jeder. Und: wieso gibt es bei C keine Exceptions?
Aber: wenn du willst, könntest du schon einen Artikel über C++
Exceptions und Coroutinen für kleine MCs schreiben, wie man die richtig
einbaut, so dass die wie ein Fruchtgummi im Hals gut runterflutschen. ;)
Rbx schrieb:> Außerdem könntest du ja auch deine Coroutinen selber frisieren, bzw.> für 8-Bit-MCs optimieren. (naja, wenn die im Multitasking gut sind)
Ok, du hast also keinen blassen Schimmer was C++ Coroutinen überhaupt
sind.
Rbx schrieb:> Johann L. schrieb:>> Rbx schrieb:>>> Außerdem könntest du ja auch deine Coroutinen selber frisieren, bzw.>>> für 8-Bit-MCs optimieren. (naja, wenn die im Multitasking gut sind)>> Ok, du hast also keinen blassen Schimmer was C++ Coroutinen überhaupt>> sind.>> Du wohl scheinbar auch nicht.
C++ Coroutinen realisieren kein Multitasking, sondern operieren auf der
Ebene des Aufrufers. Es sind einfach Funktionen, die ihren Status nach
dem Zurückkehren ("yield") merken und zu einem späteren Zeitpukt
fortgesetzt werden konnen.
Aber ich lerne gerne dazu. Also los!
Hallo,
irgendwie habe ich das Gefühl, der Thread driftet in eine komplett
andere Richtung ab als der Titel "Bizarrer Firmware crash - ATmega4808.
Wie Debuggen?" impliziert.
Mein Problem wurde Dank der ausgezeichneten Expertise einiger Mitglieder
hier im Forum perfekt gelöst. Vielen Dank nochmals dafür.
Mein Problem ist NICHT "libstdc++ Unterstützung" oder "C++ Coroutinen",
das ist für mein Projekt völlig irrelevant.
Ich weiss nicht wie ihr das in diesem Forum handhabt - dies ist mein
erster Post hier - aber ich würde vorschlagen diese Diskussion, so
interessant ich sie auch finde, in einem extra Thread fortzusetzen. Nur
so aus Respekt denen gegenüber, die eventuell ein ähnliches Problem wie
ich hatten, bzw. denen die nach "libstdc++ Unterstützung" oder "C++
Coroutinen" bei AVR's suchen.
Andy P. schrieb:> aber ich würde vorschlagen diese Diskussion, so interessant ich sie auch> finde, in einem extra Thread fortzusetzen
Ja, das wäre der vernünftige Weg dafür.
Hallo,
das Thema vom TO ist doch gelöst. Also ist es doch üblich den Thread
freizugeben? Ansonsten bitte ich Jörg oder einen anderen Moderator alle
nicht zum Thema hier passenden Beiträge auszulagern. Weil das Thema
Toolchain und libstdcpp sind äußerst interessant und sollten nicht so
abgewürgt werden.
Danke.