Bizarrer Firmware crash - ATmega4808. Wie Debuggen?

OP #8073759
Lesenswert?

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?

Angehängte Dateien:
OP #8073903
Lesenswert?

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.

#8073913
Lesenswert?

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.

#8074000
Lesenswert?

ich sehe in deinen Funktionen intensive Stacknutzung, das kann in die Hose gehen:

1
radio_send:
2

3
unsigned char tinytx_encrypt[datalen];
4
unsigned char tinytx_fec[datalen*2];
5

6
interleave_
7
unsigned char d[rows]; / worstcase rows = datalen *2

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/

: Bearbeitet durch User
OP #8074004
Lesenswert?

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.

Persönliche Seite #8074023
Lesenswert?

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.

#8074035
Lesenswert?

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.

#8074075
Lesenswert?

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)

#8074096
Lesenswert?

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.

Persönliche Seite #8074123
Lesenswert?

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.

Moderator Persönliche Seite #8074134
Lesenswert?

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.

Persönliche Seite #8074135
Lesenswert?

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_version

Andy 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.

OP #8074140
Lesenswert?

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.

Persönliche Seite #8074146
Lesenswert?

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.

Moderator Persönliche Seite #8074148
Lesenswert?

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.

#8074164
Lesenswert?

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.

#8074189
Lesenswert?

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.

Persönliche Seite #8074198
Lesenswert?

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?

#8074232
Lesenswert?

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?

Persönliche Seite #8074242
Lesenswert?

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).

#8074243
Lesenswert?

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.

OP #8074268
Lesenswert?

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?

Persönliche Seite #8074271
Lesenswert?

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.

: Bearbeitet durch User
Moderator Persönliche Seite #8074326
Lesenswert?

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.

OP #8074347
Lesenswert?

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

1
avr-objdump -S -m avr:103 firmware.elf > dateiname.asm
#8074371
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8074388
Lesenswert?

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
void init (int*);
2

3
int fun (int n, int i)
4
{
5
    int a[n];
6
    init (a);
7
    return a[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 ;-)

: Bearbeitet durch User
OP #8074408
Lesenswert?

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.

#8074421
Lesenswert?

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"?

OP #8074428
Lesenswert?

Der relevante Code Block vorher (Firmware läuft)

1
//FEC
2
    if(Cfg->FecEnable)
3
    {
4
        unsigned char tinytx_fec[datalen*2];   // the encoded message
5
        this->coder.encode_block((unsigned char*) rxpacket.payload, tinytx_fec, datalen); 
6
        interleave(tinytx_fec, sizeof (tinytx_fec));
7
        radio.send(tinytx_fec, sizeof (tinytx_fec));
8
    }
9
    // No FEC
10
    else
11
    {
12
        if (Cfg->InterleaverEnable)
13
        {
14
          interleave(rxpacket.payload, datalen);
15
        }
16
        radio.send(rxpacket.payload, datalen);
17
    }

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
        unsigned char tinytx_fec[datalen*2];   // the encoded message
5
        this->coder.encode_block((unsigned char*) rxpacket.payload, tinytx_fec, datalen); 
6
        interleave(tinytx_fec, sizeof (tinytx_fec));
7
        radio.send(tinytx_fec, sizeof (tinytx_fec));
8
    }
9
    // No FEC
10
    else
11
    {
12
        if(Cfg->FecEnable)
13
        //if (Cfg->InterleaverEnable)
14
        {
15
          interleave(rxpacket.payload, datalen);
16
        }
17
        radio.send(rxpacket.payload, datalen);
18
    }

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.

OP #8074443
Lesenswert?

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?

#8074455
Lesenswert?

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 ;)

(Firma: 1984now) #8074469
Lesenswert?

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.

Persönliche Seite #8074483
Lesenswert?

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?

#8074491
Lesenswert?

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.

Moderator Persönliche Seite #8074497
Lesenswert?

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. ;-)

: Bearbeitet durch Moderator
#8074545
Lesenswert?

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.

Persönliche Seite #8074552
Lesenswert?

Niklas G. schrieb:

Wo wird eigentlich radio.DATA initialisiert?

So jetzt hab ich's auch gefunden:

1
DATA = new byte[RF69_MAX_DATA_LEN];

https://github.com/nurazur/TiNo2/blob/4f83f5759ea78e05a03c327c58b7edc5268cbe01/libraries/TiNo2/src/RFM69.h#L119

Nächste Frage ist also: Wie ist operator new definiert? Im MegaCoreX framework:

1
void *operator new(size_t size)
2
{
3
  return malloc(size);
4
}
5

6
void *operator new[](size_t size)
7
{
8
  return malloc(size);
9
}

https://github.com/MCUdude/MegaCoreX/blob/ba59232f39249af7601e14ae1d1c7a92266cdf39/megaavr/cores/coreX-corefiles/new.cpp

Also gar keine Fehlerbehandlung...

Vielleicht mal versuchen die operatoren mit einer eigenen Variante zu überschreiben:

1
void *operator new(size_t size)
2
{
3
  void* res = malloc(size);
4
  if (!res)
5
    __asm__ volatile ("break");
6
  return res;
7
}
8

9
void *operator new[](size_t size)
10
{
11
  void* res = malloc(size);
12
  if (!res)
13
    __asm__ volatile ("break");
14
  return res;
15
}

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.

#8074668
Lesenswert?

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.

Persönliche Seite #8074700
Lesenswert?

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.

#8074716
Lesenswert?

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.

#8074719
Lesenswert?

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 im Beitrag #8074668:

Kommt auf die Perspektive an. Referenzen kann man ja als Compilermarkierungen betrachten

Persönliche Seite #8074737
Lesenswert?

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#?

#8074745
Lesenswert?

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

OP #8075006
Lesenswert?

Niklas G. schrieb:

Niklas G. schrieb:

Vielleicht mal versuchen die operatoren mit einer eigenen Variante zu überschreiben:

1
void *operator new(size_t size)
2
> {
3
>   void* res = malloc(size);
4
>   if (!res)
5
>     __asm__ volatile ("break");
6
>   return res;
7
> }
8
> 
9
> void *operator new[](size_t size)
10
> {
11
>   void* res = malloc(size);
12
>   if (!res)
13
>     __asm__ volatile ("break");
14
>   return res;
15
> }

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.

#8075040
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8075062
Lesenswert?

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 im Beitrag #8074135:

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 *operator new(size_t size)
2
> {
3
>   void* res = malloc(size);
4
>   if (!res)
5
>     __asm__ volatile ("break");
6
>   return res;
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_t sz, ret_addr_t ret);

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.

OP #8075183
Lesenswert?

Johann L. schrieb:

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

hab ich gemacht, und es bleibt dabei: die tools geben Version 7.3.0 aus. Gefällt dir die Version nicht?

1
C:\Users\xxx>avr-gcc --version
2
avr-gcc (GCC) 7.3.0
3
Copyright (C) 2017 Free Software Foundation, Inc.
4
This is free software; see the source for copying conditions.  There is NO
5
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

in einem vorherigen Post hatte ich einen Zahlendreher drin, sorry dafür.

OP #8075187
Lesenswert?

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.

OP #8075202
Lesenswert?

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.

OP #8075218
Lesenswert?

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.

OP #8075340
Lesenswert?

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.

Persönliche Seite #8075357
Lesenswert?

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 ;-)

Persönliche Seite #8075392
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8075400
Lesenswert?

Noch seltsamer wird's hier:

1
    3d8e:  3f c1         rjmp  .+638      ; 0x400e <__RODATA_PM_OFFSET__+0xe>

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
16
   pseudo-instruction __gcc_isr (Binutils v2.29, PR21683)
17

18
o  -mrodata-in-ram and -mflmap (GCC v14, PR112944), -mavrxmega2_flmap
19
   and -mavrxmega4_flmap (Binutils v2.42, PR31124). This does only
20
   make a difference for AVR64* and AVR128* devices, see the GCC v14
21
   Release Notes for details. 
22

23
Notice that the compiler behaves differently depending on the Binutils features it finds during configuration.

In dem Fall betroffen ist die Portierung von specs-atmega4808 auf GCC < v8.

#8075549
Lesenswert?

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.

#8075558
Lesenswert?

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:

1
unsigned char count = 0;
2
// ->
3
uint8_t count = 0;
4
// ----
5
count += 1;
6
// ->
7
count++;
8
// ----
9
bool fec_codec::decode_byte(unsigned char code, unsigned char &dest, char &num_errors)
10
{
11
    num_errors = 8;
12
    for (unsigned char i=0; i<16; i++) // go over all 16 codes, representing 0...9,a...f
13
    {
14
        unsigned char count = this->bitcount(code ^ this->hamming_codes[i]);
15
        if (count <= 1)
16
        {
17
            num_errors = count;
18
            dest = i;
19
            return true;
20
// 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
char errors;
25
uint8_t errors // 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 (byte i=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
return false; // 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.

#8075596
Lesenswert?

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:

1
RFM69(byte slaveSelectPin=RF69_SPI_CS, byte interruptPin=RF69_IRQ_PIN, bool isRFM69HW=false, byte interruptNum=RF69_IRQ_NUM) : GenericRadio(RF69_FSTEP)
2
{
3
  _slaveSelectPin = slaveSelectPin;
4
  _interruptPin = interruptPin;
5
  _interruptNum = interruptNum;
6
  _mode = RF69_MODE_STANDBY;
7
  _powerLevel = 31;
8
  _isRFM69HW = isRFM69HW;
9
  _PaBoost =false;
10
  fdev =0;
11
  //FSTEP = RF69_FSTEP;
12
  do_frequency_correction = false;
13
  DATA = new byte[RF69_MAX_DATA_LEN];
14
}

Das passiert aber so nicht im Assembler Code (beide Versionen), es sieht so aus als ob da die auf Github auskommentiert Version verwendet wird:

1
class RFM69 : public GenericRadio
2
{
3
  public:
4
    //static volatile byte DATA[RF69_MAX_DATA_LEN]; // recv/xmit buf, including hdr & crc bytes
5
    //static volatile byte DATALEN;
6
    static volatile uint8_t STATUSREG;
7
    //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).

: Bearbeitet durch User
OP #8075781
Lesenswert?

Nick schrieb:

Zum code, der bei mir einfach nur Stirnrunzeln erzeugt:

1
> unsigned char count = 0;
2
> // ->
3
> uint8_t count = 0;
4
> // ----
5
> count += 1;
6
> // ->
7
> count++;
8
> // ----
9

10
zugegeben, das ist nicht konsequent. 
11

12

13
> bool fec_codec::decode_byte(unsigned char code, unsigned char &dest, 
14
> char &num_errors)
15
> {
16
>     num_errors = 8;
17
>     for (unsigned char i=0; i<16; i++) // go over all 16 codes, 
18
> representing 0...9,a...f
19
>     {
20
>         unsigned char count = this->bitcount(code ^ 
21
> this->hamming_codes[i]);
22
>         if (count <= 1)
23
>         {
24
>             num_errors = count;
25
>             dest = i;
26
>             return true;
27
> // num_errors kann 1 sein, aber trotzdem wird true zurückgeliefert. Hää?
28
> // Ist 1 Fehler kein Fehler? Wenn ja, dann explizit als Kommentar 
29
> schreiben.
30
> 
31
richtig, 1 Fehler kann korrigiert werden und ist deshalb kein Fehler. Das hat der Hamming Code den ich verwende so an sich. Hier nehme ich einen 4/8 Hamming code, das heißt 4 Datenbits von 8 übertragenen Bits. Damit kann man 1 falsches Bit korrigieren, und 2 falsche Bits detektieren (aber nicht korrigieren).
32

33
> // ----
34
> char errors;
35

36
zugegeben, das ist blöd.
37

38
> uint8_t errors // kann nie negativ werden.
39
> // Besser uint_fast8, wenn man schon so auf Kante nähen will.
40
> 
41
> // ----
42
>     dest[2] = this->hamming_codes [value &0xf];
43
>     dest[1] = this->hamming_codes [(value>>4) &0xf];
44
>     dest[0] = this->hamming_codes [(value>>8) &0xf];
45
> 
46
> // Gibt es eine sinnvolle Erklärung den index von 2 ... 0 laufen zu 
47
> lassen?
48

49

50
nein. Der Code wurde aus Python übersetzt. In Python geht das mit einem Einzeiler. Wäre der Code anders herum besser?
51

52
> 
53
> // ----
54
>     for (byte i=0; i<num_bytes; i++)
55
>     {
56
>         if (this->decode_byte(encoded_msg[i], c))
57
>         {
58
>             decval <<= 4;
59
>             decval |= c;
60
>         }
61
>         else
62
>         {
63
>             success = false;
64
>         }
65
>     }
66
> 
67
> // Gibt es einen sinnvollen Grund weiter zu machen, auch wenn schon
68
> // festgestellt wurde, dass das daneben gegangen ist (success = false)
69
> // und decval nicht mehr weiter verändert wird?
70
> // ->
71
> return false; // statt das success sinnlos mitzuschleppen und überhaupt 
72
> zu verwenden.
73
>

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?

#8075938
Lesenswert?

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).

#8075986
Lesenswert?

Sehr wahrscheinlich habe ich jetzt die Ursache für das Problem, sieht wohl nach Compilerfehler aus.

Assembler Code im Original, hier passt es:

1
; Function:  bool myMAC::radio_receive(bool blocking)
2
;
3
; Source Code Zeile: rxpacket.FEI = radio.FEI;
4

5
41ea:   80 91 b2 28     lds r24, 0x28B2 ; 0x8028b2 <_ZN12GenericRadio3FEIE>
6
41ee:   90 91 b3 28     lds r25, 0x28B3 ; 0x8028b3 <_ZN12GenericRadio3FEIE+0x1>
7
41f2:   58 01           movw    r10, r16  ; this copy
8
41f4:   f5 e4           ldi r31, 0x45
9
41f6:   af 0e           add r10, r31
10
41f8:   b1 1c           adc r11, r1
11
41fa:   d5 01           movw    r26, r10
12
41fc:   8d 93           st  X+, r24
13
41fe:   9c 93           st  X, r25
14

15
; Source Code Zeile: rxpacket.TEMP = (int8_t)radio.readTemperature(0);
16

17
4200:   f6 01           movw    r30, r12  ; r12: this copy + 0x50, alles OK
18

19
4202:   80 81           ld  r24, Z   ; this
20
4204:   91 81           ldd r25, Z+1
21

22
4206:   dc 01           movw    r26, r24
23
4208:   ed 91           ld  r30, X+
24
420a:   fc 91           ld  r31, X
25
420c:   02 88           ldd r0, Z+18
26
420e:   f3 89           ldd r31, Z+19
27
4210:   e0 2d           mov r30, r0
28

29
4212:   60 e0           ldi r22, 0x00   ; Parameter 0
30
4214:   09 95           icall

Assembler Code nach Änderung, hier geht es schief:

1
; Function:  bool myMAC::radio_receive(bool blocking)
2
;
3

4
; Source Code Zeile: rxpacket.FEI= radio.FEI;
5

6
4156:   80 91 b2 28     lds r24, 0x28B2 ; 0x8028b2 <_ZN12GenericRadio3FEIE>
7
415a:   90 91 b3 28     lds r25, 0x28B3 ; 0x8028b3 <_ZN12GenericRadio3FEIE+0x1>
8
415e:   78 01           movw    r14, r16   ; this copy
9
4160:   f5 e4           ldi r31, 0x45
10
4162:   ef 0e           add r14, r31
11
4164:   f1 1c           adc r15, r1
12
4166:   d7 01           movw    r26, r14
13
4168:   8d 93           st  X+, r24
14
416a:   9c 93           st  X, r25
15

16
; Source Code Zeile: rxpacket.TEMP = (int8_t)radio.readTemperature(0);
17

18
; BUG: r31 wurde bei 0x4160 verwendet, welchen Wert soll r30 haben?
19

20
416c:   3f 96           adiw    r30, 0x0f
21

22
416e:   80 81           ld  r24, Z  ; this
23
4170:   91 81           ldd r25, Z+1
24

25
4172:   dc 01           movw    r26, r24
26
4174:   ed 91           ld  r30, X+
27
4176:   fc 91           ld  r31, X
28
4178:   02 88           ldd r0, Z+18
29
417a:   f3 89           ldd r31, Z+19
30
417c:   e0 2d           mov r30, r0
31

32
417e:   60 e0           ldi r22, 0x00   ; Parameter 0
33
4180:   09 95           icall
Persönliche Seite #8076033
Lesenswert?

https://gcc.gnu.org/PR101188

Behoben ab GCC 13.5+

Es gibt einen Backport auf GCC 8.5.1: https://github.com/sprintersb/avr-gcc-8?tab=readme-ov-file

Nen Build für Windows gibt's zum Beispiel hier: https://sourceforge.net/projects/winavr/files/avr-gcc/

Betroffen sind alle GCC Versionen bis vor GCC 5, unklar wie weit.

: Bearbeitet durch User
#8076158
Lesenswert?

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.

OP #8076685
Lesenswert?

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.

Persönliche Seite #8076697
Lesenswert?

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/

#8076752
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8076807
Lesenswert?

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.
OP #8076855
Lesenswert?

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':
2
(.init9+0x0): undefined reference to `main'
3
collect2.exe: error: ld returned 1 exit status
4
*** [.pio\build\ATmega4808_RX\firmware.elf] Error 1

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=]
2
  229 |    virtual void setSettings(

https://github.com/opencv/opencv/issues/27752

Frage: Welche Version von denen die funktionieren soll ich nun verwenden?

Persönliche Seite #8076898
Lesenswert?

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:

1
c:/users/myname/.platformio/packages/toolchain-atmelavr@8.5.1/bin/../lib/gcc/avr/8.5.1/../../../../avr/bin/ld.exe: 
2
> 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): 
3
> in function `__call_main':
4
> (.init9+0x0): undefined reference to `main'
5
> collect2.exe: error: ld returned 1 exit status
6
> *** [.pio\build\ATmega4808_RX\firmware.elf] Error 1

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.

1
.pio\libdeps\ATmega4808_RX\BME280\src\BME280.h:229:17: warning: 
2
> 'virtual void BME280::setSettings(const Settings&)' was hidden 
3
> [-Woverloaded-virtual=]
4
>   229 |    virtual void setSettings(

https://github.com/opencv/opencv/issues/27752

Frage: Welche Version von denen die funktionieren soll ich nun verwenden?

Mit der 8.5.1 bist du konservativ unterwegs und relativ nahe an der 7.3.0. Wobei die 8.5.1 nicht "offiziell" ist und z.B. mehr Devices unterstützt und Sachen wie signal(n), compact vector tables, effizientere Prologe in kleinen ISRs etc.

Die 15.1 hat mindestens ein Problem, das die 15.3 nicht mehr hat: https://gcc.gnu.org/PR122187

Ab GCC 10 gibt es 64-Bit double, was die AVR GCC 8 also noch nicht hat.

Neuere GCC werden vor allem geschätzt wegen neuerer C++ Versionen und besserer Diagnostics. Was die Features angeht, so werden diese i.d.R in den Release Notes angekündigt sofern erwähnenswert:

https://gcc.gnu.org/gcc-8/changes.html#avr https://gcc.gnu.org/gcc-9/changes.html#avr https://gcc.gnu.org/gcc-10/changes.html#avr https://gcc.gnu.org/gcc-11/changes.html#avr https://gcc.gnu.org/gcc-12/changes.html#avr https://gcc.gnu.org/gcc-13/changes.html#avr https://gcc.gnu.org/gcc-14/changes.html#avr https://gcc.gnu.org/gcc-15/changes.html#avr https://gcc.gnu.org/gcc-16/changes.html#avr https://gcc.gnu.org/gcc-17/changes.html#avr (future)

Für die 8.5.1 kommt dann noch deren README hinzu:

https://github.com/sprintersb/avr-gcc-8/blob/releases/gcc-8/README.md

Manchmal haben neuere GCC Versionen aber auch nervige Probleme wie falsche -Wstringop-overflow Warnungen https://gcc.gnu.org/PR88443 oder -Warray-bounds https://gcc.gnu.org/PR56456

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...

#8077029
Lesenswert?

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.

Persönliche Seite #8077518
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8077629
Lesenswert?

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...?

Persönliche Seite #8077703
Lesenswert?

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

: Bearbeitet durch User
#8077708
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8077714
Lesenswert?

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.

: Bearbeitet durch User
Persönliche Seite #8077786
Lesenswert?

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.

: Bearbeitet durch User
#8077788
Lesenswert?

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. ;)

Persönliche Seite #8077824
Lesenswert?

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!

OP #8077847
Lesenswert?

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.

Beitrag #8077865 wurde vom Autor gelöscht.
#8077894
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren