Forum: Mikrocontroller und Digitale Elektronik Warum können µC kein Interrupt bei Stack Overflow auslösen?


von Paulchen Panther (Gast)


Lesenswert?

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

von stack-gast (Gast)


Lesenswert?

hi gibts eh bei größeren prozessoren, da werden dann exception geworfen.

von Ben _. (burning_silicon)


Lesenswert?

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.

von H.Joachim S. (crazyhorse)


Lesenswert?

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

von Phantomix X. (phantomix)


Lesenswert?

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.

von Michael S. (rbs_phoenix)


Lesenswert?

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.

von Ben _. (burning_silicon)


Lesenswert?

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.

von tobi (Gast)


Lesenswert?

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

von Phantomix X. (phantomix)


Lesenswert?

>> 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

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

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

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

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.

von Rüdiger (Gast)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.)

von Rüdiger (Gast)


Lesenswert?

Mein Fehler .. schauen ob dort 0xFF steht ..

von Phantomix X. (phantomix)


Lesenswert?

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 :-)

von Paulchen Panther (Gast)


Lesenswert?

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

von Michel (Gast)


Lesenswert?

Peter II schrieb:
> und woran erkennt man ob dort daten liegen?
> (Es gibt keine leeren bytes.)

Daran, dass da "nil" drinsteht..  ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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".

von Ulrich P. (uprinz)


Lesenswert?

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

von Wilhelm F. (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Ulrich P. (uprinz)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Paulchen Panther (Gast)


Lesenswert?

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

von Ulrich P. (uprinz)


Lesenswert?

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 :)

von Abdul K. (ehydra) Benutzerseite


Lesenswert?

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
Noch kein Account? Hier anmelden.