Forum: Mikrocontroller und Digitale Elektronik ATMega32 Stack overflow. Wie vermeiden/analysieren?


von Andreas (Gast)


Lesenswert?

Hallo,

ich bin bei meinem C-Programm (GCC) für den ATMega32 auf ein Problem
gestoßen. Es ist immer an der gleichen Stelle hängen geblieben. Das
Problem ist sogar mit dem AVRStudio Simulator reproduzierbar.

Das ganze liegt wohl an der Stack Größe. Zumindest kam im Simulator
einmal (!)  eine Meldung "Stack overflow"(Sonst lief das Programm
einfach nicht weiter). Das kann sehr gut sein, da mein SRAM ziemlich
voll ist und das ganz auch nach 2 verschachtelten Funktionsaufrufen
passiert. Nun habe ich dazu 2 Fragen:

- Wie kann ich mir die stack größe/stack verbrauch anzeigen lassen? Im
AVRStudio Simulator gibt es zwar einen Stack Monitor, der ist aber
disabled.

- Welche generellen Ansätze gibt es, um den SRAM Verbrauch zu
reduzieren? Gibt es vielleicht irgendwo eine Art Checkliste?

Ich hoffe Ihr könnt mir helfen. Ich krieg langsam Panik, dass mein
Programm nicht auf den MCU passt. Flash reicht locker, aber SRAM ist
echt eng.

Gruß
Andreas

von A.K. (Gast)


Lesenswert?

"Welche generellen Ansätze gibt es, um den SRAM Verbrauch zu
reduzieren?"

Intensives Nachdenken.

Genauer wird's leider nicht, nicht ohne ein Mindestmass an Information
darüber, was du in das RAM alles reinsteckst.

von crazy horse (Gast)


Lesenswert?

Generelle Regeln gibts nur wenige.
-immer den kleinsten möglichen Datentyp benutzen (oft reicht ein Bit
statt einem char)
-auf globale Variablen soweit wie möglich verzichten, statt dessen
lokale Variable verwenden (overlay)
-Verschachtelungstiefe beachten/begrenzen
-in einer ISR wenn möglich keine weiteren Ints zulassen
-evtl. Sicherungsstrategie des Compilers für ISRs beobachten, manche
Compiler gehen da mit dem Holzhammer durch, dann per
Asemblerprogrammierung zu Fuss die Sicherung selbst durchführen,
fehlerträchtig!
-Konstanten in den flash auslagern
-nicht alle Annehmlichkeiten des Compilers nutzen (stdio.h/printf()
z.B. kostet reichlich flash und auch einiges an RAM
-wenn möglich auf float verzichten
-schlechter Ausweg: eeprom-Bereich benutzen, langsam und begrenzte
Schreibzyklenzahl, also für z.B. loop-Variablen denkbar ungeeignet :-),
gibt aber durchaus Anwendungsfälle dafür, z.B. device-Adresse wird nur
bei Programmstart eingelesen. Sowas kann man durchaus in den  eeprom
auslagern
-allerletzter Notnagel: den I/O-Bereich benutzen, wenn die zugehörige
Hardware nicht benutzt wird, einige Adressen können als RAM missbraucht
werden.

Das Wichtigste: der eigene Programmierstil. Dazu gehört, sich ab und zu
das erzeugte Assemblerprogramm kritisch zu betrachten.

Noch was vergessen? Sicher.

von Daniel N. (Gast)


Lesenswert?

spiel mit dem gameboy

von Peter D. (peda)


Lesenswert?

Ist ja alles schon gesagt, trotzdem hier noch mal die wichtigsten:

- strukturiertes Programmieren (mit Plan)
- kleinstmöglicher Typ
- lokale Variablen, wenn irgend möglich
- Konstanten in Flash


Allgemein sollte man mit Variablen sparsam sein, z.B. wenn a nicht mehr
gebraucht wird, schreibt man statt:

c = a + b;

besser:

a += b;

spart sich also c komplett ein.

Besonders in Schleifen und Bedingungen fällt es dem Compiler schwer,
selber festzustellen, ob er ne Variable noch für später aufheben muß
oder nicht.

Gleichzeitig sind weniger Variablen auch besser lesbar.


Peter

von Qwerty (Gast)


Lesenswert?

Zur Analyse fällt mir noch ein: SRAM mit einem charakteristischen
Pattern vorinitialisieren (klassisch: die 32-Bit-Worte 0xDEADBEEF oder
0xBAADCAFE). Wenn man sich dann einen Memory-Dump ansieht (im Simulator
oder Debugger), erkennt man leicht auf einen Blick die noch nicht
benutzten Bereiche. Das kann man auch im Programm automatisieren: Man
definiert eine "Grenzschicht" am oberen RAM-Ende, die man mit einem
Pattern füllt, und prüft im Programm gelegentlich, ob das Pattern noch
unmodifiziert drinsteht. Wenn nicht --> Stack overflow exception
auslösen.

von tom (Gast)


Lesenswert?

Hi,

eine Anmerkung zum Beispiel von Peter:

> c = a + b;
>
> besser:
>
> a += b;

Die ehutigen Compiler, zu denen auch der GCC gehört, optimieren so
etwas ohnehin in "Grund und Boden" ;-) - zumindest insofern die
Optimierung nicht abgeschaltet ist! Genaue Auskunft, wieviele lokale
Variablen (die bekanntlich auf dem Stack landen) tatsächlich (!)
angelegt werden, läßt sich i.d.R. am einfachsten dadurch bestimmen, das
man nur bis zum Assemblerquelltext compiliert und sich die entsprechende
Funktion danach anschaut - Assemblerkenntnisse vorausgesetzt.

Oder ebend Debugger: Stichwort Stackpointer. Das allerdings ist nur ein
Snapshot der gerade aktuellen Situation und muß nicht unbedingt den
Worst case darstellen!

@Andreas: wenn Du einen Verdacht hast, wo ungefähr der Überlauf
stattfindet, ist nur der Debugger zu empfehlen, aber auch dies setzt
u.U. recht detaillierte Kentnisse, was wo passiert, voraus.

Die "sicherste", allerdings auch hirnschmalzintensivste Variante ist
die oben propagierte Methode des kritischen Reviews!

Schönen Tag noch,
Thomas

von Andreas (Gast)


Lesenswert?

Hallo,

danke für die Antworten. Im Prinzip sind mir die Mechanismen
einigermaßen klar. Irgendwie fehlen mir aber noch Methoden um den
Speicherverbrauch zu analysieren, d.h. Methoden um die Speicherfresser
zu finden.

Ich habe mit bisher zunächst immer an der Ausgabe vom Compiler
orientiert:
AVR Memory Usage:
-----------------
Device: atmega32

Program:     210 bytes (0.6% Full)
(.text + .data + .bootloader)

Data:         20 bytes (1.0% Full)
(.data + .bss + .noinit)


Gibt "Data" überhaupt den SRAM verbrauch an? Diese Information ist
aber bei hohem Stack Verbrauch nutzlos, oder? Zumindest war bei meinem
urprünglichen Problem noch ca. 18% frei. Es wäre sicherlich hilfreich,
den Stack überwachen zu können, d.h. wenn ich im Simulator eine
Funktion aufrufe soll irgendwo angezeigt werden, wieviel auf dem Stack
gesichert wurde.


@Qwerty
Das hört sich interessant an. Wie initialisiere ich denn unter C das
SRAM und wie schaue ich mir im Simulator den Memory dump an? Wenn ich
mit im AvrStudio unter View->Memory1 "Data" anschaue steht da fast
nix drin. Das bleibt auch bei jedem Programm gleich.

von Qwerty (Gast)


Lesenswert?

Ich habe es noch nie auf dem AVR bzw. mit dem avr-gcc realisiert, aber
üblicherweise gibt es eine Möglichkeit dem Compiler eine
(Assembler-)Funktion "unterzujubeln", die noch vor dem C-Startup-Code
abläuft. Vielleicht hilft der Abschnitt über "Memory Sections" in der
avr-libc-Doku an der Stelle weiter, oder einer der AVR-Profis hier im
Forum kann das genauer ausführen.

Diese Funktion beschreibt dann einfach alle SRAM-Adressen mit dem
gewünschten Pattern. Daher ist es wichtig, dass sie vor dem C-Startup
läuft, damit sie dessen Initialisierungen nicht kaputtmacht.

von inoffizieller WM-Rahul (Gast)


Lesenswert?

Kann es sein, dass du irgendwas rekursiv machst?

von Profi (Gast)


Lesenswert?

die data-Angabe dürften eher Konstanten sein.

Es gibt noch einen Ausweg: den ATMEGA644, ist hübsch kompatibel zum
32er, hat aber doppelt RAM und ROM.
Aber es besteht die Gefahr, dass das Problem nur vertuscht wird und
später wieder auftritt.

Ohne Deinen Code können wir keine Aussage treffen.

Ein Stackfresser sind rekursive Programmaufrufe, z.B. ein
Unterprogramm, welches sich selbst (mit geänderten Parametern) wieder
aufruft. Sowas passiert auch mal unbeabsichtigt.

von Ssss S. (sssssss)


Lesenswert?

Hi!

benutzt du viele printf()s ?
Wenn ja dann schau dir mal in der avrlib die entsprechenden Funktionen

an die den text im Progmem speichern...
Sonst fressen viele (große) Printf()s richtig ram ;)

Bye, Simon

von Andreas (Gast)


Lesenswert?

Rekursive Funktionsaufrufe habe ich keine. Das mit dem ATMega644 spielt
auch keine Rolle, da das Programm später eh auf nem ATMega128 läuft.

Allerdings habe ich ja schon die ersten 2k SRAM "verbraten" und wenn
ich nun gleich mit dem ATMega128 weitermache sind auch die irgendwann
voll. Und auch wenn mein Programm recht umfangreich sein wird,
rechtfertigt das auf keinen Fall einen ATMega256.


Mein Problem ist immernoch, den Verbauch vom SRAM überhaupt zu sehen!!

von Qwerty (Gast)


Lesenswert?

Ich kenne (leider?) das AvrStudio nicht, daher kann ich dir nicht sagen,
wie man dort im Simulator das SRAM ansehen kann. Das müsste sich aber
finden lassen (Doku?). Eventuell gibt es auch eine Memory-Fill-Funktion
(sollte eigentlich jeder Simulator und Debugger können), mit der du im
Simulator das SRAM vorbelegen kannst. Dann kannst du das von mir
beschriebene Verfahren auch ohne Code-Änderung nachspielen: Code in
Simulator laden (nicht automatisch bis main() laufen lassen, falls
AvrStudio das normalerweise macht!), SRAM mit Pattern füllen, Programm
bis main() laufen lassen --> jetzt ist das Pattern im unteren SRAM
durch die C-Init-Routine überschrieben worden. Nach ein paar
Funktionsaufrufen sollte dann auch "von oben" das Pattern teilweise
verschwinden (dort, wo der Stack hingewachsen ist).

von inoffizieller WM-Rahul (Gast)


Lesenswert?

Es gibt im AVR-Studio eine Ansicht "Memory". Da kann man sich das
EEPROM, SRAM und vielleicht sogar den FLASH-Inhalt ansehen.

von Andreas (Gast)


Lesenswert?

Ja, aber das geht nicht. Wenn ich bei Memory mir "Data" anschaue steht
da immer das gleich drin und es ändert sich auch nix.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Anbei ne Testroutine für den AVR-GCC, funktioniert aber nur, wenn man
kein malloc benutzt (fopen benutzt malloc !).

Einmal am Anfang aufrufen, um das Pattern zu füllen und dann später, um
zu testen, bis wo das Pattern noch steht.

Ich habe 0x77 genommen, da das im RAM-Dump am besten auffällt.


Peter

von Ale (Gast)


Lesenswert?

Was kann helfen, es ist wenn du ein Stackdump machst, so du kanst sehen
wie war die Stack vor du hast die letzte Funktion angerufen.
Vielleicht, du kannst auch ein teil diese Porgamm mit dem PC debuggen
(für x86 kompiliert)

von Andreas (Gast)


Lesenswert?

Danke Peter, das ist schonmal eine Hilfe.

Aber wieso kann ich im simulator den Speicherverbrauch nicht sehen?
Oder reicht es, wenn ich den verbleibenden Stack als indikator für den
Speicherverbrauch nehme?

@Ale
Wie kann ich einen Stackdump während des simulierens machen?

von Andreas (Gast)


Lesenswert?

OK,
ich bin jetzt an einem anderen Rechner. Hier ist ein neueres SP vom
AVRStudio drauf. Hier kann ich mir den Speicher von meinem Programm
während der Simulation anzeigen lassen.

Leider habe ich etwas erschreckendes festgestellt. Das Programm bleibt
trotz genügend freiem Speicher immernoch hängen. Ich habe mein Programm
mit dem ATMega128 simuliert. Gestern hat es noch funktioniert, aber
jetzt bleibt es auch an der selben Stelle hängen und zwar bei einem
"sprintf" Befehl...

Kann es sein, dass hier ein bug vorliegt?

von Peter D. (peda)


Lesenswert?

"jetzt bleibt es auch an der selben Stelle hängen und zwar bei einem
"sprintf" Befehl..."

Ui, das klingt nach nem Speicherzuweisungsproblem.

Entweder Du hast zu wenig Speicher oder gar keinen reserviert, oder der
Pointer zeigt in den Wald, auf den Du schreibst.

Besonders bei Zahlenausgabe muß man immer den Worst-Case (maximale
Zeichenzahl) einkalkulieren.


Peter

von Andreas (Gast)


Lesenswert?

Die Anweisung sieht wie folgt aus:

char sTemp[20]="";
double dZahl;

dZahl = 3.1e8;

sprintf(sTemp,"%.6e", dZahl);


Auf jeden Fall habe ich mal folgendes getan. Ich hab die alte WinAvr
Installation umbenannt und die neuste installiert. Dann habe ich alles
neu kompiliert. Danach funktioniert es. Nun habe ich die alte wieder
umbenannt und nochmal neu kompiliert. Danach ging es wieder nicht ...


Die Funktion stack_free() zeigt mir in beiden fällen vor der Anweisung
einen Wert von 24 an. Ist zwar nicht viel, aber bei der neueren Version
tut es ja auch....

von Peter D. (peda)


Lesenswert?

Dann gibt es noch Murphies Gesetz:

Der Fehler liegt nie da, wo man ihn vermutet bzw. wo er sich bemerkbar
macht.

Also irgendwo viel früher wird die Bombe gelegt.

Außerdem sind 1% frei wirklich nicht sehr viel.


Peter

von Andreas (Gast)


Lesenswert?

Ich hab jetzt mal ein wenig mit meinem code gespielt, aber wirklich
weniger SRAM-Verbrauch habe ich dadurch auch nicht. So viele globale
Variablen habe ich nicht. Ein paar 8-Bit Variablen. Strings habe ich
zwar ne Menge, die bleiben aber im flash.

Irgendwie finde ich die Speicherfresser nicht ...

von Daniel Messerli (Gast)


Lesenswert?

Hallo,

verwendet man Srings, wie zum Beispiel:

sprintf(s,"Hallo");

dann frisst der AVR GCC Compiler 6 Bytes vom RAM!
Um diese RAM Verschwendung zu verhindern, schreibt
man das folgendermassen um:

{
  uint8_t i;
  char s[32];
  char f[32];

  i=0;

  f[i]='H';i++;
  f[i]='a';i++;
  f[i]='l';i++;
  f[i]='l';i++;
  f[i]='0';i++;
  f[i]=0;i++;       // Null terminieren nicht vergessen!

  sprintf(s,f);
}


Macht man innerhalb einer Funktion zusätzlich geschweifte Klammern,
dann sind die lokalen Variablen nur innerhalb gültig und auf dem
Stack vorhanden. Ausserhalb ist dieser Stack Bereich dann wieder
frei!

Weil ich meist ein Display habe und auf den RS232 Schnittstellen
Text Protokolle fahre, brauche ich viele Strings. Ich muss dann
alle Srings so programmieren, damit ich genug Speicher habe.

Ich hoffe, dass dies hilft.

Grüsse

von Stefan F. (Gast)


Lesenswert?

> Ich hoffe, dass dies hilft.
Wohl kaum, wer wartet 13 Jahre lang auf eine Antwort?

Abgesehen davon ist dein Lösungvorschlag gaga. Die vernünftige Lösung 
wurde bereits oben genannt: z.B. sprintf_P() in Kombination mit PSTR().

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.