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
hi gibts eh bei größeren prozessoren, da werden dann exception geworfen.
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.)
Mein Fehler .. schauen ob dort 0xFF steht ..
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
Peter II schrieb: > und woran erkennt man ob dort daten liegen? > (Es gibt keine leeren bytes.) Daran, dass da "nil" drinsteht.. ;-)
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.