Hallo,
oft kommt es vor, dass ein Programm einen Stack overflow erzeugt. Ich
frage mich, warum die µC Hersteller nicht eine Schutzschaltung in der
Hardware implementieren.
Ich stelle mir das so vor: Es gibt zwei Register, zum einen den normalen
Stack Pointer und zum anderen ein Vergleichsregister. Sobald der Stack
Pointer den Wert des Vergleichsregister entspricht, wird ein Interrupt
Flag gesetzt. Wenn das "Stack Schutz Interrupt" Enable Bit gesetzt ist,
die ISR aufgerufen.
Die Ansteuerung ist also genauso wie bei anderen Interrupts.
Natürlich benötigt die ISR auch X Bytes auf dem Stack. Daher darf das
Vergleichsregister nicht auf das Ende des Stacks zeigen sondern
mindestens X Bytes vorher.
Ein Beispiel (Addressen in dezimaler Schreibweise):
Der Stack befindet sich im Bereich von Adresse 00 bis Adresse 99, also
100 Bytes. Der Stack wächst nach unten. Die ISR benötigt 10 Bytes. Ich
stelle das Vergleichsregister auf Adresse 10. Sobald es einen Stack
overflow gibt, wird die Stack ISR aufgerufen, in der ich dann einen
Motor ausschalte, dem Benutzer über LCD einen entsprechenden Fehlercode
anzeige und den Fehlercode im EEPROM abspeichere.
Gruß
PP
Nur fehlerhafte Programme erzeugen Stack Overflows. Ein richtiges
fertiges Programm nutzt nie den kompletten Stack aus. Ich finde es in
µCs daher nicht so wichtig.
Kannst Du aber per Software nachrüsten wenn Du sowas brauchst. Du kannst
Dir jederzeit den Stackpointer anschauen bevor Du was auf den Stack
schmeißt.
Ben _ schrieb:> Nur fehlerhafte Programme erzeugen Stack Overflows.
Tja, das ist so eine Frage....
Prinzipiell hast du natürlich recht, es ist unter Umständen aber gar
nicht so einfach, eine Fehlerfreiheit in diese Richtung zu
testen/garantieren.
Klar, bei einfachen Programmen kein Problem.
Die tatsächliche benutzte worst-case-Stacktiefe ist nicht so einfach zu
erkennen.
Es gibt Software, die läuft lange Zeit stabil und scheinbar fehlerfrei.
Und dann kommt es eben doch zu Abstürzen...
Interrupt schön und gut - Um den originalen Program Counter und die
Register zu speichern wird aber wieder der Stack benutzt, der ohnehin
schon übergelaufen ist.
Der PIC18F67K22 z.b. hat die Möglichkeit, bei einem Overflow einen Reset
vom µC zu machen oder nicht. Wenn nicht, können die Bits STKFUL und
STKUNF im STKPTR-Register. Datenblatt Seite 90. Ist aber glaube ich kein
Interrupt, nur ein Status-Bit.
Aber eigentlich nicht bei den kleinen Controllern wo man das so einfach
per "Vergleichsregister" lösen möchte.
Außerdem ists doch kein Problem den Stack Pointer mit einem Minimalwert
zu vergleichen und bei Unterschreitung eine Meldung zu machen. Das
brauche ich bei den kleinen µCs nicht in Hardware.
> Interrupt schön und gut - Um den originalen Program Counter und die> Register zu speichern wird aber wieder der Stack benutzt, der ohnehin> schon übergelaufen ist.
Deswegen wollte er das "Vergleichsregister" ja auch auf den Minimalwert
plus das was er für diesen Interrupt selber braucht setzen.
Ben _ schrieb:> Außerdem ists doch kein Problem den Stack Pointer mit einem Minimalwert> zu vergleichen und bei Unterschreitung eine Meldung zu machen. Das> brauche ich bei den kleinen µCs nicht in Hardware.
Normalweise macht man das nicht so, da du nicht sicher sein kannst das
du den minimalwert erwischt hast....
Eine Taktik ist es, die Grenze des Stacks zu "Watermarken", also
irgendwelche Werte reinschreiben (z.b 0xdeadbeef ;-)) und zyklisch zu
sehen ob diese noch existiert.
Man kann auch den Stack in der MCU-Init-Funktion mit einem speziellen
Wert füllen, dann lässt sich auch die Stack-Usage ermitteln, den so ein
Stack-Problem ist eigentlich während des Debuggings schon zu finden....
>> Interrupt schön und gut - Um den originalen Program Counter und die>> Register zu speichern wird aber wieder der Stack benutzt, der ohnehin>> schon übergelaufen ist.> Deswegen wollte er das "Vergleichsregister" ja auch auf den Minimalwert> plus das was er für diesen Interrupt selber braucht setzen.
Ja ist mir auch grad aufgefallen, die info war aus meinem Stack schon
wieder verschwunden wo ich geantwortet hab :D
Macht schon halbwegs Sinn, grad während des Entwickelns... Wobei ich
ehrlich gesagt noch nie den Fall hatte, dass mir der Stack übergelaufen
ist
dann schachtelst du nicht zu tief, und benutzt irq enapbles etc.
ist immer auch eine Frage der programstruktur und damit des
programmierstils und der Erfahrung.
mach geügegen ifs und calls im irq handler und u bkommst bei
ausrechender irq folge jeden stack durch die Bodenwanne/Kellerdecke
mann kann natürlich auch den stackpointer in jeder ruoutine und jedem
irq checken und den max/min wert in dafür freigehaltenen register packen
ist zwar ballast läst aber ahnen wohin die reise geht.
ein unaufgeräumter stack dagegen hängt das System nach wenigen bugs auf
;-)
Namaste
ach ja, besonders efficient:
nesty der dritten art, im Ring verkette Verschachtelung mit
Parameterübergabe auf dem Stack. Der Überlauf benötigt nur wenige
Prozessortakte; bei Basicprogramierern immer wieder gern verwendet.
aber auch von C aus erreichbar. Wenn der Compiler schläft weil der
tricky Programmierer alle Warnungen und Errormeldungen ausschaltet ;-)
So lässt sich mit etwas Glück schon der Compiler aufhängen. lol
Namaste
Winfried J. schrieb:> mach geügegen ifs und calls im irq handler und u bkommst bei> ausrechender irq folge jeden stack durch die Bodenwanne/Kellerdecke
nein, denn bei den Atmels ist kann (ohne hack) nur eine ISR gleichzeitg
aktiv sein.
Habe nicht alles durch gelesen, aber du kannst doch die Adresse des
Stack in den Z-pointer laden, auslesen und nachschauen ob dort Daten
liegen. Wenn ja haste n Stack overflow wenn nicht ist alles in Ordnung.
Rüdiger schrieb:> Habe nicht alles durch gelesen, aber du kannst doch die Adresse des> Stack in den Z-pointer laden, auslesen und nachschauen ob dort Daten> liegen.
und woran erkennt man ob dort daten liegen?
(Es gibt keine leeren bytes.)
Winfried J. schrieb:> dann schachtelst du nicht zu tief, und benutzt irq enapbles etc.> ist immer auch eine Frage der programstruktur und damit des> programmierstils und der Erfahrung.>> mach geügegen ifs und calls im irq handler und u bkommst bei> ausrechender irq folge jeden stack durch die Bodenwanne/Kellerdecke
Das ist mir schon klar; ich programmier ja nur seit über 15 Jahren :D
Stackprobleme kenn ich eigentlich nur vom uc, und da sind es keine
Probleme, wenn man von vorn herein dran denkt (zb es nicht auf
unendliche Rekursionen ankommen lässt, usw).
Vielleicht hab ichs falsch ausgedrückt: Mir ist noch nie ein Stack
unabsichtlich übergelaufen :-)
Hi!
Das ging ja schnell mit den Antworten :-) Danke!
tobi schrieb:> Eine Taktik ist es, die Grenze des Stacks zu "Watermarken", also> irgendwelche Werte reinschreiben (z.b 0xdeadbeef ;-)) und zyklisch zu> sehen ob diese noch existiert.> Man kann auch den Stack in der MCU-Init-Funktion mit einem speziellen> Wert füllen, dann lässt sich auch die Stack-Usage ermitteln, den so ein> Stack-Problem ist eigentlich während des Debuggings schon zu finden....
Das geht natürlich. Viele Entwicklungsumgebungen berechnen auch die
maximale Stack Verwendung für die "normalen" (also ohne ISRs)
Funktionen. Aber wie hier schon mehrfach angesprochen, lässt sich die
maximale Stack Verwendung nicht immer so einfach feststellen.
Es gibt gerade für den Bereich der Sicherheitstechnik viele Controller,
die verschiedene Schutzfunktionen in der Hardware eingebaut haben.
Z.B. kann der Watchdog nicht durch das Löschen eines Bits ausgeschaltet
werden, man muss vorher eine Art Passwort in ein bestimmtes Register
schreiben. Oder bei manchen lässt sich ein Timer nicht einfach durch das
Löschen eines Bits stoppen, sondern man muss das Bit auslesen und es
direkt im nächsten Takt setzen, um es zu löschen.
Ganz naiv stelle ich mir vor, dass es die obigen Lösungen für Watchdog
und Timer komplizierter in der Hardware zu realisieren sind als ein
Vergleichsregister. Gibt es einen Grund, den ich übersehen habe?
Gruß
PP
Rüdiger schrieb:> Mein Fehler .. schauen ob dort 0xFF steht ..
Die müsstest du aber zuvor (im startup code) selbst erstmal dahin
schaffen. Von "Natur" aus steht SRAM nämlich nach dem Einschalten
auf "irgendwas".
Bei ARM/Cortex findet man 2 separate StackPointer. Und, je nach Größe,
eine (einfache) MMU. Das System selbst nutzt den einen SP, die
Anwender-Software den anderen. Addressiert die Anwender-Software oder
deren SP etwas außerhalb des zulässigen Bereiches (nicht zugewiesenes
RAM oder womöglich sogar nicht existente Addressbereiche) dann gibt es
eine MemFault() Exception.
Da das System aufgrund seines eigenen SP noch aktiv ist, kann es
auswerten, was da zuletzt passiert ist.
Es ist aber nicht unbedingt nötig auf einem entsprechenden Chip ein
System ans Laufen zu bringen, denn man kann diese Exceptions auch mit
dem Debugger auslesen und im Stacktrace nachsehen, was da zuletzt
passiert ist.
Auf kleinen Systemen, oder einfach gehaltenen größeren Systemen hat es
sich tatsächlich bewährt, den Stack selbst (oder den Stack eines jeden
Threads) mit einem fröhlichen 0xdeadbeef zu initialisieren. Dann jagt
man die Software durch alle möglichen Konstellationen und schaut
regelmäßig nach, wie der oder die Stack(s) ausgelastet wurden.
Bei ganz kleinen uCs (AVR/PIC) gibt es eine Sonder-Konstellation, denn
normalerweise füllt man das RAM (Heap) von unten nach oben und der SP
sitzt initial am Ende des RAMs, wächst also dem Heap entgegen. Man kann
eine Sicherheitszone einbauen, die mit einem Sonderwort initialisiert
wird und die weder von der einen, noch von der anderen Seite her
überschrieben werden darf. Passiert das doch, kann man oft mit etwas
Mühe und einem Debugger im RAM nachsehen, wer es war und woher er kam.
Auf der einen Seite landen Daten, die man vielleicht wieder erkennt,
oder oben trudeln Frames ein, die Rücksprungadressen von Funktionen
haben.
Da gabe es mal ein richtig gutes Dokument, aber ich finde es leider
nicht auf die Schnelle... irgend was mit Crash Forensics oder Exception
Forensics...
Jedenfalls, diese Überwachungen anhand von Tokens ala 0xdeadbeef
brauchen Zeit und stellen für kleine uCs einen gewissen Aufwand dar.
Daher kann man sie entweder nur im DEBUG ausführen (Normalerweise
braucht DEBUG Code mehr Platz, man verschenkt also ggf. etwas, wenn man
dort den Platz für Heap und Stacks einrichtet) oder man baut einen
Kompromiss. So ist es in z.B. Nut/OS gelöst:
Im DEBUG kann man bis aufs Byte genau feststellen, wie viel Stack ein
Thread genutzt hat. Im RELEASE kann man nur noch feststellen, dass er
die letzten 32 Bytes angekratzt hat und selbst das kann man optional
auch noch abschalten, wenn man sich sicher ist, dass in Zukunft alles
gut geht.
Gruß, Ulrich
Bei einer Softwareentwicklung, sollte man immer auch darauf schauen, was
der Stack macht. Schachteltiefen ermitteln. Bei manchen µC ist es kein
Problem, wenn sie z.B. einen 16-bit-Stack haben. Der 8051 hat z.B. nur
128 bzw. 256 Bytes internes RAM, was mit dem Stapel geteilt wird. Da muß
man schon sorgfältiger sein.
Wenn man eine 8051-Schaltung mit großem externem RAM hat, kann man also
mal überlegen, ob man Variablen vom internen ins externe RAM
ausgliedert, und dem Stack etwas mehr Freiraum gönnt.
Ein C-Programm hat oft zwar eine so genannte hypothetische
Stackmaschine, die einen Softwarestack im RAM anlegt. LCALL sieht man
aber im Code immer noch reichlich, und da wird der Hardwarestack
beansprucht.
Bei Keil, sah ich in den Einstellungsregisterkarten schon mal, daß man
mit einer Option Stack-Check-Code zur Software hinzu fügen kann. Das
bedeutet aber auch längere Laufzeiten.
Programmiert man in Assembler, hat man sowieso alles manuell im Griff.
Ich schrieb da schon mal eine eigene Stack-Check-Funktion, die wirklich
aus jeder Funktion heraus aufgerufen wird. Am Ende, wenn alles stimmt,
kann man diese wieder entfernen.
Die Schachteltiefe wird umso größer, je mehr man ein Programm in Details
zerlegt. Und je mehr Interrupts man hat, die sich in der Priorität auch
noch durchbrechen können. Da muß man etwas überlegen, in wie weit das
nötig ist.
Randvoll bis zur Obergrenze sollte man den Stapel nicht machen, und
immer etwas Luft lassen, vielleicht 20%. Für Eventualitäten, die man
übersah.
Wenn der Stack nicht reicht, hat man den falschen µC für seine
Anwendung. Stackgröße ist genau so ein Parameter, wie RAM- und
ROM-Größe. Wenn es zu wenig ist, braucht man einen größeren.
Stack-Overflows passieren wohl auch in professionellen gekauften
Programmen. Früher öfter als heute. Das sind die Fälle, wo sich z.B. das
Programm aufhängt.
Rüdiger schrieb:> Mein Fehler .. schauen ob dort 0xFF steht ..
Das ist ein bischen zu einfach gedacht.
Was hier nämlich alle übersehen ist, dass es auch noch den Heap gibt.
Das SRAM ist in 4 Zonen eingeteilt
+------------+-----------+--------------+---------------+
| Variablen | Heap | unbenutzt | Stack |
+------------+-----------+--------------+---------------+
Der Bereich "Variablen" ist fix und kann exakt vorherbestimmt werden.
Die Bereiche "Heap" und "Stack" sind aber variabel und wachsen in den
Bereich 'unbenutzt' hinein und zwar je nachdem wie der Programmfluss
durch das Programm von statten geht. Erst wenn sich "Heap" und "Stack"
anfangen zu überlappen, dann hat man einen Stack Overflow.
Detektieren kann man das, aber dazu benötigt man nicht unbedingt
Hardware. Man muss lediglich bei jeder Vergrößerung des Stacks
nachsehen, ob der Stackpointer kleiner als der Heappointer wird, der
anzeigt, wo die letzte vom Heap benutzte Speicheradresse liegt. Und auch
umgekehrt: bei jeder Speicherallokierung vom Heap muss nachgesehen
werden, ob man dadurch in den Stackbereich hineingewachsen wäre. Aber
all das kostet natürlich auch Laufzeit.
Hallo Karl Heinz,
das habe ich doch in meiner Erläuterung geschrieben.
Ich bin aber nicht davon ausgegangen, dass jeder, der einen Controller
programmiert gleich mit malloc() und free() um sich wirft. Das sind eher
die Fälle in denen ein (RT)OS eingesetzt wird. Zudem waren diese beiden
Funktionen früher eher gruselig oder garnicht in den diversen
Hersteller-Paketen integriert.
Lokale Variablen einer Funktion landen nun mal eher auf dem Stack und
der Anfänger wird eher den Stack in die .bss und .text Sections rauschen
lassen als in den Heap. Und die lokalen Variablen fangen nur zu gerne
schon in der mail() an.
Gruß, Ulrich
Der springende Punkt ist, dass dir eine Befüllung des Speichers nur
begrenzten Nutzen bringt.
Du kannst ein und denselben Speicher sowohl für Heap als auch für Stack
benutzen. Nur eben nicht zur gleichen Zeit. Aber hintereinander spricht
nichts dagegen.
Eine saubere Erkennung eines Stack Overflowes macht man daher, indem man
Stack Pointer mit dem Top Of Memory Wert vergleicht. Nur leider muss man
das dann bei jeder Stackpointer Manipulation machen (üblicherweise im
Function-Prolog, wenn die funktionslokalen Variablen angelegt werden)
bzw bei jeder Veränderung des Top of Memory Wertes (also beim malloc und
free).
> Ich bin aber nicht davon ausgegangen, dass jeder, der einen> Controller programmiert gleich mit malloc() und free() um sich wirft.
Wenn du eine universelle Lösung direkt in den Systemlibraries machen
willst, dann musst du aber davon ausgehen.
Hi!
Danke für die vielen Antworten.
Wenn ich es richtig verstanden habe, kann ich es so zusammenfassen:
Es gibt sowohl Hardware- als auch Softwarelösungen, um einen
Stack-Overflow zu detektieren. Die Softwarelösung benötigt etwas
Rechenleistung, kann aber prinzipiell auf jedem Controller verwendet
werden. Die Hardwarelösung wird bei größeren Controller eingebaut, bei
kleinen lohnt sich der Aufwand nicht.
Gruß
PP
Paulchen Panther schrieb:> ...bei kleinen lohnt sich der Aufwand nicht
Das würde ich jetzt so nicht sagen, aber man kann bei kleinen
Controllern in kleinen Projekten sicherlich eher eine korrekte
Abschätzung machen mit der man auch richtig liegt.
Du must Dir also die Frage stellen, kann ich mir eine Abschätzung
leisten und liege ich damit richtig oder kann ich nicht richtig schätzen
und sollte wenigstens im DEBUG eine Überwachung implementieren (und wenn
sie noch so einfach ist.)
Je aufwändiger das Projekt oder je wichtiger seine Funktionsgarantie,
desto dringlicher ist eine Speicher-Überprüfung.
Ist eine Fall zu Fall Entscheidung :)
Als Zwischending kann man eine periodische Abfrage des Stackpointers
eventuell im Vergleich zum Heap machen. War so viele Jahre bei Apple.
Wurde der Abstand zu klein, wurden vom Memory Manager ältere Code-Blöcke
rausgeworfen (die dann zu einem späteren Zeitpunkt notfalls automatisch
wieder geladen werden). Funzte gut.