Forum: Mikrocontroller und Digitale Elektronik Tipps zur Speicheranalyse gesucht


von Wulf D. (holler)


Lesenswert?

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.
1
kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize);
2
memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2);
3
4
if (lenmem == NULL) {    
5
    st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded);
6
} else {
7
    if (*lenmem >= memneeded)
8
           st = (kiss_fftr_cfg) mem;
9
    *lenmem = memneeded;
10
}
11
if (!st)
12
    return NULL;

Ist jemand schon mal bei einer Portierung auf ähnliche Probleme gestoßen 
oder hat Tipps wie man sich dem effektiv annähert?

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Vielleicht was mit qemu auf'm PC basteln und das binary dann dort 
laufenlassen/debuggen?

Gruss
WK

von Wulf D. (holler)


Lesenswert?

Auf dem PC kann ich debuggen, aber da gibt es auch keinen Fehler und der 
Code läuft beliebig oft durch.

von Hmmm (hmmm)


Lesenswert?

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.

von Wulf D. (holler)


Lesenswert?

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.

von Bruno V. (bruno_v)


Lesenswert?

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

von Wulf D. (holler)


Lesenswert?

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.

von Hmmm (hmmm)


Lesenswert?

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.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

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

von Dergute W. (derguteweka)


Lesenswert?

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

von Norbert (der_norbert)


Lesenswert?

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.

von Wulf D. (holler)


Lesenswert?

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.

von Wulf D. (holler)


Lesenswert?

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

: Bearbeitet durch User
von Pete K. (pete77)


Lesenswert?

Was sagt denn der Compiler? Keine Warnungen/Fehler? Überprüfe mal den 
Warn-Level und schau Dir dann die Warnings an.

von Norbert (der_norbert)


Lesenswert?

Μα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.«

von Richard W. (richardw)


Lesenswert?

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.

von Wulf D. (holler)


Lesenswert?

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!

von Bruno V. (bruno_v)


Lesenswert?

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
int n = ...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.

: Bearbeitet durch User
von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

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?

von Wulf D. (holler)


Angehängte Dateien:

Lesenswert?

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.

von Rbx (rcx)


Lesenswert?

Wulf D. schrieb:
> Ich vermute das Speicher überschrieben wird.

Könnte man das Programm auch in Fortran übersetzen?

von Hannes J. (pnuebergang)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wulf D. schrieb:
> Erhoffe mir Tipps wie man Fehler bei der Speicherallokation auf die Spur
> kommt.

Sanitizer? Im GCC -fsanitize=

https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

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.