Erhoffe mir Tipps wie man Fehler bei der Speicherallokation auf die Spur
kommt.
Bin dabei, einen relativ komplexen C-Code (26 Dateien) aus dem Bereich
digitaler Signalverarbeitung für den PC auf einen 32Bit uC (RP2350) zu
portieren.
Im ersten Schritt erst mal alles PC-spezifische wie GUI,
Dateioperationen und RTC-Anhängigkeiten entfernt bzw ersetzt. Der
abgespeckte Code funktioniert als Consolenanwendung noch immer und
bleibt nach Analyse mit Valgrind/ massif mit ca 200 kByte RAM auch im
Rahmen des Controllers.
Auf dem uC, wo auch noch eine andere Anwendung läuft, stürzt der ab.
Leider bekomme ich den Debugger dort nicht zum Laufen. Ausgaben auf
dessen Display sind möglich. Compiler / Linker zeigen keine Fehler an.
Ich vermute das Speicher überschrieben wird. Der Code strotzt vor Makros
und Stukturen, die wiederum zahlreiche Arrays enthalten. Zunächst wird
in einer Initialisierungprozedur dynamisch Speicher allokiert.
Der Teil läuft fehlerfrei durch.
Im nächsten Schritt mit Signaldaten gefüllt und gerechnet. Das geht
schief.
Könnte mir vorstellen, dass z.B. sizeof(von irgendwas) bei der
Allokation auf dem uC fälschlicher weise zu klein ausfällt und auf dem
PC dagegen passt.
Ein kleiner Codeschnipsel, wie das in etwa aussieht. KISS_FFT_MALLOC ist
ein Makro, da steckt am Ende malloc hinter.
Wulf D. schrieb:> Leider bekomme ich den Debugger dort nicht zum Laufen.
Hast Du einen freien UART? Logging kann sehr hilfreich sein.
Wulf D. schrieb:> Zunächst wird in einer Initialisierungprozedur dynamisch Speicher> allokiert.
Wird zwischendurch auch welcher freigegeben? Dann kann es zu
Fragmentierung kommen, so dass es knallt, sobald Du wieder einen
grösseren zusammenhängenden Speicherblock brauchst.
Fehlende Freigaben könnte natürlich ein Grund sein, der auf dem PC wegen
GByte an RAM nicht auffällt.
Ob der Code schon beim ersten Durchlauf abschmiert oder erst später weiß
ich gar nicht. Vermutung ist, dass nur einmalig bei der Initialisierung
allokiert wird.
Werde mal nach einem Durchlauf verriegeln und schauen ob das geht.
Mit der UART muss ich mal schauen. Ansonsten sind auch (asynchrone)
Display-Ausgaben drin.
Wulf D. schrieb:> sizeof(kiss_fft_cpx) * ( nfft * 3 / 2)
Das muss m.E. nicht immer passen. Vergleiche dort einfach den Zugriff
"hinters" letzte Element mit dem Byte-Ptr und Zugriff + memneeded
Bruno V. schrieb:>> sizeof(kiss_fft_cpx) * ( nfft * 3 / 2)>> Das muss m.E. nicht immer passen. Vergleiche dort einfach den Zugriff> "hinters" letzte Element mit dem Byte-Ptr und Zugriff + memneeded
Verstehe nicht richtig was du meinst. Könnte natürlich den mit diesem
Konstrukt berechneten Speicherbedarf auf das Display des uC ausgeben und
mit dem Wert vom PC vergleichen.
Allerdings ist der ganze Code mit solchen Konstrukten übersät. Was auf
dem PC auch funktioniert, die Testdaten werden richtig berechnet.
Wulf D. schrieb:> Fehlende Freigaben könnte natürlich ein Grund sein, der auf dem PC wegen> GByte an RAM nicht auffällt.
Ich meinte nicht mal Speicherlecks, die natürlich auch ein Problem sein
können, sondern Speicherfragmentierung.
Wenn Du z.B. 256kB zur Verfügung hast und 4x 64kB anforderst, sind nach
dem Freigeben des zweiten und vierten Blocks zwar wieder 128kB frei,
aber mehr als 64kB am Stück wirst Du dann nicht mehr bekommen.
Hi
Gibts in deiner Bibliothek Anzeichen für
https://en.wikipedia.org/wiki/Type_punning? Dann könnte irgendwo ein
unaligned 32 Bit Zugriff versteckt sein. Cortex-M0 mag das nicht. Zur
Diagnose mal einen HardFault Handler einhängen.
Matthias
Moin,
Wulf D. schrieb:> Auf dem PC kann ich debuggen, aber da gibt es auch keinen Fehler> und der> Code läuft beliebig oft durch.
Das schrubst du ja schon. Ich meinte eher, das binary fuer deinen
Zielprozessor via qemu aufm PC laufen zu lassen. Oder hast das schon
probiert?
Gruss
WK
Hmmm schrieb:> Wenn Du z.B. 256kB zur Verfügung hast und 4x 64kB anforderst, sind nach> dem Freigeben des zweiten und vierten Blocks zwar wieder 128kB frei,> aber mehr als 64kB am Stück wirst Du dann nicht mehr bekommen.
Grundsätzlich stimme ich dir zu. Da aber von einer 200K Speichernutzung
gesprochen wurde, der 2350 aber über ein halbes MB hat, müsste die
Software schon so extrem schlecht geschrieben sein, dass es wirklich
gleich beim zweiten Blick auffallen sollte.
Wenn wirklich gar nix Gescheites zum Debuggen da ist, einfach nach jedem
malloc/calloc den Zeiger prüfen und bei Nichtgefallen ne Lampe
anschalten.
Falsche Größe allozieren könnte ich mir aber auch vorstellen.
Dergute W. schrieb:> Das schrubst du ja schon. Ich meinte eher, das binary fuer deinen> Zielprozessor via qemu aufm PC laufen zu lassen. Oder hast das schon> probiert?
Ok, jetzt verstanden: hab nachgeschaut, für qemu ist die RP2350
Unterstützung zwar in Arbeit, aber da gibt es noch nichts.
Μαtthias W. schrieb:> Gibts in deiner Bibliothek Anzeichen für> https://en.wikipedia.org/wiki/Type_punning? Dann könnte irgendwo ein> unaligned 32 Bit Zugriff versteckt sein. Cortex-M0 mag das nicht. Zur> Diagnose mal einen HardFault Handler einhängen.
An einem Ringbuffer habe ich das gesehen, da funktioniert es
nachweislich.
Ansonsten muss ich den Code genau danach absuchen. Wegen der zahlreichen
Makros ziemlich unübersichtlich.
HardFault Handler kenne ich nicht, muss mich einlesen.
Μαtthias W. schrieb:> unaligned 32 Bit Zugriff versteckt sein. Cortex-M0 mag das nicht.
Das ist ein Cortex-M33.
»The Cortex-M33 processor supports unaligned accesses.«
Wenn du es mit address sanitizer
(https://github.com/google/sanitizers/wiki/addresssanitizer) kompilierst
und laufen lässt, ist auch alles schick? Das wäre Werkzeug erster Wahl
um Speicherfehler auf einem High Level OS aufzudecken.
Mach’s nicht ohne Debugger auf dem Zielsystem. Das ist gut investiertes
Geld bzw Zeit, gerade wenn es ein kommerzielles Projekt ist. Für einen
verplemperten Entwicklertag kann man sich bereits einen JLink kaufen und
muss nicht mit Basteldebuggern anfangen. Printf ist mittelfristig die
teuerste Art, zu debuggen.
Danke, den address sanitizer kenne ich auch nicht, probiere den aus.
Ist kein kommerzielles Projekt, rein Hobby. Nutze eigentlich die
Pico-Probe und die funktioniert bei kleinen Sachen auch bestens.
Dass es hier nicht geht, liegt vermutlich am Pi-Pico-SDK. Ist die
aktuelle Version, aber schlecht gepflegt, d.h. mit Bugs. Damit USB im
Release-Build überhaupt funktioniert, musste ich im TinyUSB Submodul ein
commit reinpatchen.
Sobald die Initialisierung vom SDK im Debug-Mode durchläuft, hängt die
CPU in einem trap fest.
Habe erstmal einige Tipps abzuarbeiten, reicht bis zum WE - Danke!
Wulf D. schrieb:> Verstehe nicht richtig was du meinst.> memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * (
nfft * 3 / 2);
> st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded);
Es gibt also eine Struktur vom Type kiss_fftr_cfg. Die Enthält ein Array
(nennen wir es 'a', den Namen kann ich nicht raten) mit n elementen
(vermutlich n=nfft oder so).
1
/* byte-ptr basteln */
2
char*p=st;
3
intn=...nfft??;
4
5
/* check if groß genug */
6
if(p+memneeded<&st->a[n])ZuendeDieRoteLaterne();
Mit ZuendeDieRoteLaterne = LED, Display, Uart, Portpin oder irgendwas
...
Anmerkung: Es ist immer blöd, wenn der Speicherbedarf der Struktur a von
sizeof(Struktur b) + komische Rechnungen ("sizeof(Struktur c) n 3 /
2") abhängig ist.
Norbert schrieb:> Μαtthias W. schrieb:>> unaligned 32 Bit Zugriff versteckt sein. Cortex-M0 mag das nicht.>> Das ist ein Cortex-M33.> »The Cortex-M33 processor supports unaligned accesses.«
Muss man das wie bei M3 oder M4 ggfs. aktivieren?
Zwei Dinge ausprobiert:
- erstmal den Code so verriegelt, dass der nur einmal durchlaufen kann.
Schafft der nicht, stürzt vorher ab. Damit entfallen Dinge wie
unterschlagene Freigaben oder Speicherfragmentierung.
- AddressSanitizer: PC-Code mit "-fsanitize=address" übersetzt und
environment variable LSAN_OPTIONS=verbosity=1:log_threads=1 gesetzt.
Einen vollständigen Datensatz verarbeitet, der Code wird dabei einmal
Initialisiert und Speicher allokiert, aber anschließend mehrmals
durchlaufen.
Am Ende Speicher wieder freigegeben.
Die Sanitizer-Ausgabe ist angehängt. Meldet Fehler, aber weiß noch nicht
ob die aus der Rechnerumgebung (Ubuntu mit VS Code) oder aus dem zu
untersuchenden Programm stammen.
Wulf D. schrieb:> Ansonsten muss ich den Code genau danach absuchen. Wegen der zahlreichen> Makros ziemlich unübersichtlich.
Jeder halbwegs brauchbare C-Compiler lässt sich davon überzeugen den
Code nach der Makro-Expansion durch den Preprozessor zu zeigen. Z.B. -E
beim GCC.
Das wird kein schöner Code sein, aber es ist der Code den der
eigentliche Compiler sieht. Eventuell noch durch einen C Beautifier
laufen lassen um ihn etwas lesbarer zu gestalten.