Forum: Compiler & IDEs Bizarrer Firmware crash - ATmega4808. Wie Debuggen?


von Andy P. (andy_p241)


Angehängte Dateien:

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?
von Thorsten S. (thosch)


Lesenswert?

klingt sehr nach Puffer-Überlauf. Also ein Schreiben in ein Array über 
die Länge hinweg.
von Andras H. (andras_h)


Lesenswert?

Stack overflow?

Nmi?
von Udo S. (urschmitt)


Lesenswert?

Andy P. schrieb:
> Frage: Wie kann ich das Debuggen?

Alle Warnings einschalten und die Warnings durchgehen und möglichst alle 
beseitigen.
Parameter "-fsanitize=address" des compilers nutzen
Tools wie cppcheck.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Andy P. schrieb:
> release Modus kompilieren und mit assert()

Meistens sind im Release Modus die Assertions deaktiviert (NDEBUG 
Makro).
von Andy P. (andy_p241)


Lesenswert?

Niklas G. schrieb:
> Meistens sind im Release Modus die Assertions deaktiviert (NDEBUG
> Makro).
Danke, Assertions sind aktiviert, funktionieren.
von Andy P. (andy_p241)


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.
von Nick (b620ys)


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.
von Andreas M. (amesser)


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
von Andy P. (andy_p241)


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.
von Andy P. (andy_p241)


Lesenswert?

Thorsten S. schrieb:
> klingt sehr nach Puffer-Überlauf. Also ein Schreiben in ein Array über
> die Länge hinweg.
Ok, ich habe die Größe aller infrage kommenden Arrays verdoppelt. Keine 
Veränderung, leider.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Udo S. schrieb:
> Parameter "-fsanitize=address" des compilers nutzen

Nicht wirklich: ATmega4808
von Niklas G. (erlkoenig) Benutzerseite


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.
von Nick (b620ys)


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.
von Rbx (rcx)


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)
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Nick schrieb:
> Setz mal den Stack hoch

Hat das irgendeine Auswirkung auf dem AVR?! Das Stacklimit wird von der 
Hardware doch nicht überwacht?

Rbx schrieb:
> Warum nicht gleich in Asm?
> (Vorteil: normalerweise gut überschaubar und üblicherweise auch sehr
> einfach zu debuggen)

Man kann ja immer auch den vom Compiler generierten Assemblercode 
debuggen.
von Nick (b620ys)


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.
von Rbx (rcx)


Lesenswert?

Niklas G. schrieb:
> Man kann ja immer auch den vom Compiler generierten Assemblercode
> debuggen.

Viel Spaß mit der Stackframe-Navigation. ;)
von Niklas G. (erlkoenig) Benutzerseite


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Nick schrieb:
> Setz mal den Stack hoch.

Was genau meinst du denn damit?

GCC auf AVR arbeitet by default so, dass der Stack ab dem oberen Ende 
des RAMs beginnt, damit hat er den maximalen Platz, den er bekommen 
kann, bevor er mit den statisch allozierten Variablen (oder dem Heap, so 
es einen gibt) kollidiert.  Was willst du hier "hoch setzen"?
von Dieter S. (ds1)


Lesenswert?

Eventuell schauen was auf dem Stack passiert ist vermutlich bei dem Code 
angebracht. Da wird einiges so wie z.B. hier gemacht:

1
void interleave (unsigned char* s, int rows, bool forward)
2
{
3
    int i;
4
    unsigned char d[rows], bit;
5
6
...
7
}

Darafu wurde ja hier auch schon hingewiesen:

Beitrag "Re: Bizarrer Firmware crash - ATmega4808. Wie Debuggen?"
von Andy P. (andy_p241)


Lesenswert?

Niklas G. schrieb:
> Warum nicht einfach den Release Code debuggen?
>
> Dee Fehler klingt stark nach Buffer Overflow, also Rücksprungadresse auf
> dem Stack überschrieben. Lokale Arrays mit variabler Länge (!!) sind
> tückisch, ganz besonders auf dem AVR.

Vielen Dank für den Tip mit dem Debuggen des Release Codes, da bin ich 
grade dran.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.
von Johann L. (gjlayde) Benutzerseite


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.
von Andy P. (andy_p241)


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.
von Niklas G. (erlkoenig) Benutzerseite


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.
von Udo S. (urschmitt)


Lesenswert?

Niklas G. schrieb:
> Guter Punkt - also vermutlich eher ein überlauf eines lokalen Arrays
> und dann Überschreiben der Rücksprungadresse.

Oder ein uninitialisierter Pointer auf dessen Adresse schreibend 
zugegriffen wird.
von Andreas M. (amesser)


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.
von Rbx (rcx)


Lesenswert?

Andreas M. schrieb:
> Beides ekelig zu debuggen

Ja, eigentlich kann man das Programm ganz gut nach Asm übertragen (viele 
Bools, viele Ifs, viele Datenworte). Und sei es nur zu Lernzwecken.
(oder eben damit nochmal genauer auseinandersetzten: 
https://www.geeksforgeeks.org/c/dynamic-memory-allocation-in-c-using-malloc-calloc-free-and-realloc/)
von Nick (b620ys)


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sorry, Nick, ich glaube, du hast noch nie einen AVR programmiert.
von Niklas G. (erlkoenig) Benutzerseite


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?
von Nick (b620ys)


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?
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Nick schrieb:
> Wie man das ausschließen/nachweisen kann: Siehe oben.

Mach doch mal bitte vor, wie du den Linkerscript dafür aufbauen willst.

(Davon abgesehen, AVR-GCC-Nutzer müssen sich normalerweise nicht mit 
selbst gefummelten Linkerscripten herumschlagen.)
von Niklas G. (erlkoenig) Benutzerseite


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).
von Rolf (rolf22)


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rolf schrieb:
> Ein 1 Byte großes Speicherleck in einer Schleife

Wie erzeugst du selbiges, wenn kein malloc() benutzt wird?
von Rbx (rcx)


Lesenswert?

Niklas G. schrieb:
> Das passiert beim compilieren automatisch.
Na dann könnte TO auch den Asm-Code posten.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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
von Andy P. (andy_p241)


Lesenswert?

Johann L. schrieb:
> Welche Compilerversion ist das? Welche LibC? Binutils?
 toolchain-atmelavr @ 1.70300.191015 (7.3.0)
von Andy P. (andy_p241)


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?
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

-S statt -c

-fsave-temps behält sämtliche Zwischendateien, statt sie zu löschen

Schwieriger wird es allerdings, wenn du auch -flto benutzt; dann wird 
nach dem Linken der Compiler nochmal angeworfen, und das Resultat hat 
mit dem ursprünglichen Code nicht mehr viel zu tun. Dann geht nur noch 
Disassemblieren.
von Niklas G. (erlkoenig) Benutzerseite


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
von Rolf (rolf22)


Lesenswert?

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.  ;-)
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.
von Andy P. (andy_p241)



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
von Dieter S. (ds1)


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
von Andreas M. (amesser)


Lesenswert?

Niklas G. schrieb:
> Aber das Programm nutzt den Heap anscheinend überhaupt nicht, und

Ich habe mindestens ein "New" im Quelltext gefunden :-)
von Andreas M. (amesser)


Lesenswert?

Und da free und malloc im Assembler Output enthalten sind...
von Johann L. (gjlayde) Benutzerseite


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
von Andy P. (andy_p241)


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.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Andreas M. schrieb:
> Ich habe mindestens ein "New" im Quelltext gefunden :-)

Es gibt mehrere malloc / new Aufrufe (asm statisch) für folgende 
Blockgrößen in Bytes:

41 (5×), 14, 2, 67, 22, 4*num_actions.
: Bearbeitet durch User
von Rolf (rolf22)


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"?
von Andy P. (andy_p241)


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.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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!
von Andy P. (andy_p241)


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?
von Rbx (rcx)


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 ;)
von Ob S. (Firma: 1984now) (observer)


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.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf schrieb:
> dass malloc() ja auch in irgendwelchen fremden Libs versteckt
> sein kann, wovon man als Programmierer gar nichts weiß bzw. wissen kann.

Hat ja jeder, der nicht ins Map-File schauen will, sein Recht auf 
Unwissenheit.
von Niklas G. (erlkoenig) Benutzerseite


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?
von Oliver S. (oliverso)


Lesenswert?

Rbx schrieb:
> Andy P. schrieb:
>> Ergibt sich im Assembler
>> ein anderer Code abhängig davon ob ich eine Referenz oder einen Zeiger
>> verwende?
>
> Ja, ich glaube

Auf Assemblerebene sind Referenz und Zeiger das gleiche.

Oliver
von Dieter S. (ds1)


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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
von Uwe (uhi)


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.
von Niklas G. (erlkoenig) Benutzerseite


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.
von Rbx (rcx)


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.
von Udo S. (urschmitt)


Lesenswert?

Rbx schrieb:
> Referenzen kann man ja als
> Compilermarkierungen betrachten

??? Referenzen und Zeiger sind in Assembler indirekte Adressierung.
von Oliver S. (oliverso)


Lesenswert?

Rbx schrieb:
> Eventuell wäre die Übersetzung nach
> C ein einfacherer Schritt, der Erfolg verspricht.

Ganz sicher nicht.

Oliver
von Niklas G. (erlkoenig) Benutzerseite


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.
von Rbx (rcx)


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.
von Udo S. (urschmitt)


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:
> Kommt auf die Perspektive an. Referenzen kann man ja als
> Compilermarkierungen betrachten
von Niklas G. (erlkoenig) Benutzerseite


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#?
von Oliver S. (oliverso)


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
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Man kann ein C++ Programm nicht einfach nach C übersetzen

Zu Zeiten von CFront vor 30 Jahren ging das noch. :-)
von Oliver S. (oliverso)


Lesenswert?

Das dürfte ungefähr auch der Zeitraum sein, aus den RBXes 
C++-„Kenntnisse“ stammen.

Oliver
von Dieter S. (ds1)


Lesenswert?

Jörg W. schrieb:
>
> Zu Zeiten von CFront vor 30 Jahren ging das noch. :-)

Damit war schon etwas früher Schluss als die Exceptions kamen:

https://en.wikipedia.org/wiki/Cfront

Apples MPW (Macintosh Pro­grammer's Workshop) hat den CFront lange 
verwendet.
von Andy P. (andy_p241)


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.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Andy P. schrieb:
> Ich
> glaube eher dass das während der Programmausführung passiert, und zwar
> genau dann, und nur dann, wenn ein Datenpaket empfangen wurde.

Ja, aber wenn radio.DATA ins Nirvana zeigt kann natürlich alles mögliche 
passieren.
von Dieter S. (ds1)


Lesenswert?

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?
von Dieter S. (ds1)


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
von Johann L. (gjlayde) Benutzerseite


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:
> 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.
von Andy P. (andy_p241)


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.
von Andy P. (andy_p241)


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.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Andy P. schrieb:
> die tools geben Version 7.3.0 aus.

Ok lassen wir das.  Binutils- und Libc-Version sind wohl zu schwierig...
von Andy P. (andy_p241)


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.
von Andy P. (andy_p241)


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.
von Dieter S. (ds1)


Lesenswert?

Andy P. schrieb:
>
> Ich verstehe die Frage nicht.

Du hast eine Änderung in radio_send() gemacht. Verwendest Du Code mit 
dieser Änderung um die Pakete zu versenden oder ist es jedes mal der 
selbe Code ohne die Modifikation?
von Andy P. (andy_p241)


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.
von Niklas G. (erlkoenig) Benutzerseite


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 ;-)
von Johann L. (gjlayde) Benutzerseite


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
von Johann L. (gjlayde) Benutzerseite


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.
von Rbx (rcx)


Lesenswert?

Tja, und wenn sie nicht gestorben sind, dann debuggen sie noch 
heute...oder morgen, oder übermorgen..oder..

https://www.mikrocontroller.net/articles/AVR-Tutorial:_Stack
https://wokwi.com/arduino
von Nick (b620ys)


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.
von Nick (b620ys)


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.
von Johann L. (gjlayde) Benutzerseite


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++;
Das ist doch alles nur Kosmethik die ü-ber-haupt-nix mit dem Problem zu 
tun hat.  Der damit erzeugte Binärcode bleibt aufs Bit unverändert.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Nick schrieb:
> Mit dem avg-gcc scheint es zu gehen.

Kannst du mal das selbe Programm mit unterschiedlichen Stack-Größen 
kompilieren und zeigen, was das für Auswirkungen auf die erzeugte .bin 
Datei hat?
von Dieter S. (ds1)


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
von Sheeva P. (sheevaplug)


Lesenswert?

Rbx schrieb:
> In Assembler hast du das Problem nicht -

Dafür 1000 andere.

> und darüberhinaus einen viel
> besseren Überblick über Zusammenhänge,

Genau, weil Spaghetticode aus Mnemonics ja bekanntlich sooo 
übersichtlich ist...
von Andy P. (andy_p241)


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
zugegeben, das ist nicht konsequent. 
10
11
12
> bool fec_codec::decode_byte(unsigned char code, unsigned char &dest, 
13
> char &num_errors)
14
> {
15
>     num_errors = 8;
16
>     for (unsigned char i=0; i<16; i++) // go over all 16 codes, 
17
> representing 0...9,a...f
18
>     {
19
>         unsigned char count = this->bitcount(code ^ 
20
> this->hamming_codes[i]);
21
>         if (count <= 1)
22
>         {
23
>             num_errors = count;
24
>             dest = i;
25
>             return true;
26
> // num_errors kann 1 sein, aber trotzdem wird true zurückgeliefert. Hää?
27
> // Ist 1 Fehler kein Fehler? Wenn ja, dann explizit als Kommentar 
28
> schreiben.
29
> 
30
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).
31
32
> // ----
33
> char errors;
34
zugegeben, das ist blöd.
35
36
> uint8_t errors // kann nie negativ werden.
37
> // Besser uint_fast8, wenn man schon so auf Kante nähen will.
38
> 
39
> // ----
40
>     dest[2] = this->hamming_codes [value &0xf];
41
>     dest[1] = this->hamming_codes [(value>>4) &0xf];
42
>     dest[0] = this->hamming_codes [(value>>8) &0xf];
43
> 
44
> // Gibt es eine sinnvolle Erklärung den index von 2 ... 0 laufen zu 
45
> lassen?
46
47
nein. Der Code wurde aus Python übersetzt. In Python geht das mit einem Einzeiler. Wäre der Code anders herum besser?
48
49
> 
50
> // ----
51
>     for (byte i=0; i<num_bytes; i++)
52
>     {
53
>         if (this->decode_byte(encoded_msg[i], c))
54
>         {
55
>             decval <<= 4;
56
>             decval |= c;
57
>         }
58
>         else
59
>         {
60
>             success = false;
61
>         }
62
>     }
63
> 
64
> // Gibt es einen sinnvollen Grund weiter zu machen, auch wenn schon
65
> // festgestellt wurde, dass das daneben gegangen ist (success = false)
66
> // und decval nicht mehr weiter verändert wird?
67
> // ->
68
> return false; // statt das success sinnlos mitzuschleppen und überhaupt 
69
> zu verwenden.
70
>

Ja, es gibt einen sinnvollen Grund. Es handelt sich hier um einen String 
von Rohdaten, die pro Byte 4 gültige Datenbits auswerfen. Ich möchte 
hier nicht die in dem String codierten Daten komplett verwerfen, nur 
weil ein einzelnes Nibble falsch detektiert wurde. Allerdings, und da 
möchte ich dir für den Hinweis danken, sollte dann auch eine 
Fehlermeldung dergestalt ausgegeben werden, aus der ersichtlich ist an 
welcher Stelle sich ein falsches Nibble befindet.
>
> Ich weise nochmal auf den Debugger hin, abhängig vom verwendeten
> Programmer und der IDE. Dass der TO den verwendet hat und die exakte
> Stelle wo es kracht gefunden hat, konnte ich bis jetzt nicht lesen. Ist
> mir aber auch zunehmend egal.
Der TO hat die exakte Stelle gefunden wo es kracht, allerdings nicht mit 
dem Debugger, sondern über assert(). Der Debugger hat im ersten Anlauf 
versagt, weil der Code im DEBUG Modus kompiliert wurde und dies den 
Fehler komplett kaschiert hat. Erst der Hinweis dass man auch im RELEASE 
Mode einen Debugger verwenden kann, hat weiter geholfen. Allerdings 
zickt mein Setup in diesem Modus, und ich muss mir sehr viel mehr Zeit 
nehmen das hinzubekommen, welche ich erst wieder im August haben werde.


> Das solls gewesen sein. MMn sollte der TO zuerst mal an seiner
> Einstellung zum Programmieren arbeiten, damit würde er deutlich weiter
> kommen.
Der TO hat Programme von professionellen Programmierern gesehen die 
wesentlich chaotischer dahergekommen sind, absolut unkommentiert waren 
und manches waren unlesbare Spaghetti. Der TO hat es erlebt dass ein 
Praktikant einen Code abgeliefert hat, den die Profis dann unverändert 
übernommen haben, und darauf ihren eigenen Stiefel aufgesetzt haben, 
weil einfach keine Zeit war, den Code aufzuräumen, weil er einfach gut 
funktioniert hat.

Ich bin motiviert, ehrgeizig, aufgeschlossen, wissbegierig, aber mit 
Sicherheit kein professioneller Programmierer. Mit welcher "Einstellung 
zum Programmieren" komme ich deutlich weiter? Kannst du das bitte etwas 
präzisieren?
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Andy P. schrieb:
> Allerdings
> zickt mein Setup in diesem Modus

Definiere "zickt"? Es ist normal dass beim Debuggen im Release-Modus die 
ausgeführte Zeile wild herum springt, weil der Compiler die einzelnen 
Anweisungen umsortiert. Es funktioniert aber trotzdem.
von Dieter S. (ds1)


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).
von Dieter S. (ds1)


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
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Krasse Analyse, Dieter!  Dafür dürfte das Attribut "bizarr" mehr als 
gerechtfertigt sein.
von Johann L. (gjlayde) Benutzerseite


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
von Veit D. (devil-elec)


Lesenswert?

Johann L. schrieb:

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

Damit macht ihr mich ja arbeitslos.  :-)
Ist der 8.5.1er deine Hand optimierte Toolchain?
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Damit macht ihr mich ja arbeitslos.

Wieso das denn?

> Ist der 8.5.1er deine Hand optimierte Toolchain?

Nicht optimiert.  Aber mit einigen ausgewählten, rückportierten PRs à la 
README.
von Veit D. (devil-elec)


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.
von Andy P. (andy_p241)


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.
von Johann L. (gjlayde) Benutzerseite


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/
von 900ss (900ss)


Lesenswert?

Jörg W. schrieb:
> Krasse Analyse, Dieter!  Dafür dürfte das Attribut "bizarr" mehr als
> gerechtfertigt sein.

Dem kann ich noch nur anschließen. Daumen hoch. Wirklich erstaunlich wie 
und dass du (Dieter) das gefunden hast.
von Dieter S. (ds1)


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
von Johann L. (gjlayde) Benutzerseite


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.
von Andy P. (andy_p241)


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?
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Andy P. schrieb:
> ja, irgendwas fehlt dem Linker, kann es gerade nicht lokalisieren:

> (.init9+0x0): undefined reference to `main'

Offensichtlich fehlt ihm ein main() …
von Johann L. (gjlayde) Benutzerseite


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...
von Andy P. (andy_p241)


Lesenswert?

Vielen Dank für die Zusammenfassung. Sehr interessant, ich hatte mir 
noch nie um Toolchains Gedanken gemacht, nur genutzt.

Johann L. schrieb:
> Mit der avr-gcc-8.5.1_2026-07-22_mingw32.tar.xz sollte das nicht mehr
> auftreten.

Diese Version scheint ja von heute zu sein. Wo kann ich sie 
herunterladen?
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Andy P. schrieb:
> Johann L. schrieb:
>> Mit der avr-gcc-8.5.1_2026-07-22_mingw32.tar.xz sollte das nicht mehr
>> auftreten.
>
> Diese Version scheint ja von heute zu sein. Wo kann ich sie
> herunterladen?

https://sourceforge.net/projects/winavr/files/avr-gcc/
von Veit D. (devil-elec)


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.
von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Die bislang einzig funktionierende und das unabhängig
> einer IDE.
> https://github.com/modm-io/avr-libstdcpp
> Man muss es nur überlegt verwenden.

Ist aber halt nicht vollständig. Ob eine vollständige C++ lib mit 
exceptions und allem anderen auf einem AVR sinnvoll ist, ist natürlich 
diskutabel.

Oliver
: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


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
von Niklas G. (erlkoenig) Benutzerseite


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...?
von Johann L. (gjlayde) Benutzerseite


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
von 900ss (900ss)


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
von Niklas G. (erlkoenig) Benutzerseite


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
von Rbx (rcx)


Lesenswert?

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.
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rbx schrieb:
> Ich musste so lachen. Ich hatte mich gleich mal umgesehen, fand u.a. das
> hier:

Was hat das mit Coroutinen zu tun?
von Johann L. (gjlayde) Benutzerseite


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
von Rbx (rcx)


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. ;)
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.
von Rbx (rcx)


Lesenswert?

Johann L. schrieb:
> Ok, du hast also keinen blassen Schimmer was C++ Coroutinen überhaupt
> sind.

Du wohl scheinbar auch nicht.
von Johann L. (gjlayde) Benutzerseite


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!
von Andy P. (andy_p241)


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.
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Andy P. schrieb:
> aber ich würde vorschlagen diese Diskussion, so interessant ich sie auch
> finde, in einem extra Thread fortzusetzen

Ja, das wäre der vernünftige Weg dafür.
Beitrag #8077865 wurde vom Autor gelöscht.
von Veit D. (devil-elec)


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.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.