Kann man auf dem PC in C einen Stack-overflow vorhersehen oder abfangen?
Auf einem uC z.B. 8051 kann man vorher auf den Stack-pointer SP sehen,
wie weit der noch from Crash weg ist. Oder auf ARM kann man das
Stack-Ende mit der MPU schützen, und im Memory Fault Handler den Stack
wieder herstellen, ohne dass das Program crasht.
Aber auf dem PC? Hier mal als Beispiel - ist natürlich vorher klar, das
muss crashen - aber als Test:
int main(void) { char image[1024*1024*1024]; ...
Walter T. schrieb:> Speicherschutz ist unter Windows seit Windows NT mit dabei. Bei> Linux weiß ich es nicht, ob es schon von Anfang an mit dabei war.
Er fragt nicht ob es Schutz gibt sondern wie er erkennen kann ob es
"bald" soweit ist
> char image[1024*1024*1024];
Ein GB muss auf einem 64-Bit PC nicht unbedingt crashen, der hätte
genug Adressraum. AFAIK setzt man die Stack Größe aber lange nicht so
hoch.
Auf dem PC erzeugen Zugriffe auf ungemappten Addressraum eine
Zugriffsverletzung.
Aber nicht jeder mögliche Stacküberlauf endet zwangsläufig in
ungemappten oder anderweitig gesperrtem Addressraum - er könnte z.B.
zufällig (oder gewollt) im Datensegment landen und da unbemerkt Werte
verändern.
Compiler kann man mitunter beibringen, bei Stack-Allokation keine
grossen Sprünge zu machen, sondern jede Page zu besuchen. Wenn die
Laufzeitumgebung dann eine guard page unten an den Stack setzt, dann
scheppert es mit klarer Ursache. Es gilt dann, diese Exception
abzufangen.
Ja geht mit Exceptions unter Windows.
Am Ende vom Stack gibt es eine "Guard Page", wenn du darauf
zugreifst, dann gibt es eine Exception, die du auch abfangen kannst.
Die Exception Methode hat den Nachteil, dass die Stackseiten in den
Speicher geholt werden.
Es gibt ab Windwos 8 auch die GetCurrentThreadStackLimits(),
und SetThreadStackGuarantee(), mit denen du die Stackgrösse abfragen
kannst, ohne die Seiten in den RAM zu mappen. Vor Windows 8 gibt es
die Info im Thread Environment Block (TEB)
Sollte der maximale Speicherverbrauch nicht zur Übersetzungszeit bekannt
sein?
Verschachtelte Funktionsaufrufe + lokale Variable am Stack +
verschachtelte Interruptaufrufe und deren lokale Variable am Stack.
Damit sollte der Compiler in der Lage sein den richtigen Speicherplatz
für das Stack zu reservieren. Oder liege ich da falsch?
Lothar schrieb:> Kann man auf dem PC in C einen Stack-overflow vorhersehen oder abfangen?
Die Frage ist: Was willst du vorhersagen? Wenn eine Funktion zu viel
Stack braucht, bedeutet das, dass du sie gar nicht erst aufrufen darfst.
Dazu musst du aber vorher wissen, wie viel Stack sie braucht. Und du
musst dann natürlich auch noch irgendetwas sinnvolles ohne den Aufruf
tun können.
Und wenn das alles gegeben ist und du auch die Stackgröße kennst, kannst
du das hier natürlich auch auf dem PC tun:
Lothar schrieb:> Auf einem uC z.B. 8051 kann man vorher auf den Stack-pointer SP sehen,> wie weit der noch from Crash weg ist.
udok schrieb:> Du liegst falsch, oft hängt der Stackverbrauch auch von den> Eingangsdaten ab (alloca(), Rekursionstiefe).
Soll das heißen, dass der Stackspeicherverbrauch nicht berechenbar oder
determinístisch ist?
Gerald K. schrieb:> Soll das heißen, dass der Stackspeicherverbrauch nicht berechenbar oder> determinístisch ist?
Zumindest manchmal ja. Klassisches Beispiel ist parsen von
arithmetischen Ausdrücken mit Klammern durch rekursiven Abstieg. Der
maximale Stackverbrauch hängt dann nur von den Eingabedaten ab. Im
Embedded-Bereich dürfen rekursive Funktionen deshalb oft nicht verwendet
werden.
Man kann die Rekursion im Parser zwar wegprogrammieren, damit verschiebt
sich aber das Problem nur, denn irgendwo muß die
Verschachtelungsstruktur gespeichert werden. Sprich: Wenn Du in einen
Taschenrechner mit Klammerfunktion eingibst
- 1*(1)
- 1*(0+1*(1))
- 1*(0+1*(0+1*(1)))
- ...
wird dem Taschenrechner sicher irgendwann der Speicher ausgehen müssen.
Gerald K. schrieb:> Sollte der maximale Speicherverbrauch nicht zur Übersetzungszeit bekannt> sein?> Verschachtelte Funktionsaufrufe + lokale Variable am Stack +> verschachtelte Interruptaufrufe und deren lokale Variable am Stack.> Damit sollte der Compiler in der Lage sein den richtigen Speicherplatz> für das Stack zu reservieren. Oder liege ich da falsch?
Es gibt Compiler, die das implizit machen. Z.B. der PIC-Compiler, da er
gar keinen Stack hat sondern die Variablen auf feste, aber mehrfach
belegte Adressen legt.
Die Grenzen sind Rekursion, direkt und indirekt. Direkt: Funktion a ruft
sich selbst n mal auf, Indirekt: Funktion A ruft Funktion B, Funktion B
ruft Funktion A auf.
Beides kann er (teilweise) erkennen und verbieten, schränkt also ein.
Walter T. schrieb:> Speicherschutz ist unter Windows seit Windows NT mit dabei. Bei Linux> weiß ich es nicht, ob es schon von Anfang an mit dabei war.
Bei UNIX, dem "Vorfahren" von Linux, war der Speicherschutz schon
vorhanden, als DOS/Windows davon noch gar nicht wussten.
Selbstverständlich ist der Speicherschutz Grundvoraussetzung bei
unixoiden Betriebssystemen, wozu auch Linux gehört.
Siehe auch:
https://de.wikipedia.org/wiki/Speicherschutz#Betriebssysteme_ohne_Speicherschutz
ff.
Gerald K. schrieb:> udok schrieb:>> Du liegst falsch, oft hängt der Stackverbrauch auch von den>> Eingangsdaten ab (alloca(), Rekursionstiefe).>> Soll das heißen, dass der Stackspeicherverbrauch nicht berechenbar oder> determinístisch ist?
Ja. Wenn das wichtig ist, dann muss man eben Rekursion und alloca
verbieten.
Klar, wenn Du weißt, dass Deine Rekursion maximal 10 Ebenen tief ist,
dann könnte man das trotzdem berechnen, aber wenn die Abbruchbedingung
fehlerhaft ist oder Deine Annahmen nicht stimmen geht das schief.
Wie keinLichtAufGing schreibt kann man die Rekursion natürlich in
Iterative Varianten mit Arrayzugriffen umwandeln und muss dann die
Arrayzugriffe überprüfen. Das hat den Vorteil das man richtige
Fehlermeldungen geben kann. Wie soll man mit einer "Stack voll"-Meldung
umgehen? Kann man überhaupt noch eine Fehlermeldung ausgeben? Kann man
ohne Debugger überhaupt feststellen, an welcher Stelle der Stackoverflow
passiert ist?
Gibts auf dem PC (Windows/Linux) nicht auch virtuellen Speicher?
Der kann dann doch Teile des Hauptspeichers auf Platte auslagern. Das
könnten doch auch unbenutzte Stackbereiche sein?
Pandur S. schrieb:> Bei einer embedded Entwicklung sollte man der Stackverbrauch schon beim> Design kennen.
Ja, nur hilft das nicht viel weiter - was soll ein Embedded System denn
tun, wenn es einen Stack-Overflow erkennt? Ausser sicherheitshalber
stehenzubleiben (wenn das sinnvoll möglich ist). Eine Heizungsregelung
wird nicht per EMail den Systemadministrator verständigen können.
Bei Controller-Compilern die ich verwendet habe gab es die Option, auf
Stack Overflow zu prüfen, aber das bläht natürlich den Code erheblich
auf ohne das obige Problem zu lösen.
Das geht übrigens auch ganz primitiv ohne Memory Management oder
Exceptions, man muss halt vor jeder Stack-Operation den Wert des
Stackpointers überprüfen, die Grenzen sind dem Compiler ja bekannt.
Georg
PittyJ schrieb:> Gibts auf dem PC (Windows/Linux) nicht auch virtuellen Speicher?> Der kann dann doch Teile des Hauptspeichers auf Platte auslagern. Das> könnten doch auch unbenutzte Stackbereiche sein?
Ungenutzer Speicher wird nicht ausgelagert, weil es ihn zunächst nur als
Adressraum gibt. Erst bei Zugriff wird Speicher zugeordnet.
Lothar schrieb:> Aber auf dem PC?
Auf dem PC kriegst Du die bekannte Meldung "Access violation at address
..."
Dann in den Taskmanager gehen und das Programm abschießen.
In der Regel stellen sich Programme beleidigt, wenn sie nicht den
gewünschten Speicher bekommen. Nur ganz selten prüft ein Entwickler den
Returnwert von malloc ab und versucht es nochmal mit kleineren Werten.
Aber oft ist es gar nicht zu wenig Speicher, sondern ein Pointer, der in
den Wald zeigt. D.h. ein Programmfehler des Entwicklers oder in einer
Lib.
Georg schrieb:> Pandur S. schrieb:>> Bei einer embedded Entwicklung sollte man der Stackverbrauch schon beim>> Design kennen.>> Ja, nur hilft das nicht viel weiter - was soll ein Embedded System denn> tun, wenn es einen Stack-Overflow erkennt?
Gegenfrage:
Wie soll es denn bei einem typischen Embedded System zu einem Überlauf
des Stacks kommen? Man hat eine klar begrenzte Menge an Sensoren (und
Aktoren). Man kennt deren Abtastraten und man weiß daher, wie viele
Daten pro Zeiteinheit "reinkommen". Warum sollte man dann abhängig von
einem Input jetzt auf einmal ungewöhnlich viel mehr Stack benötigen als
sonst? Und wenn es nicht von einem Input an das System abhängt, dann
weiß man es ja eben im voraus wieviel Stack man benötigen wird. Bzw. man
kann dies mit entsprechenden Tools analysieren (Static Code Analysis,
Profiling, Valgrind, ...)
Für die Werte, die man von Sensoren oder anderen Geräten empfängt, gilt
natürlich: Überprüfen auf Plausibilität. Man empfängt ja auch nicht ein
UINT8 und sagt bei einer Division dann, "Ach, da wird schon keine Null
drinstehen, weil der Sender bestimmt keine Null verschickt. Also kann
ich einfach immer sofort durch den empfangenen Wert teilen, das passt
schon." :-)
Das letzte Mal im Leben, dass ich einen Stack Overflow gesehen habe, war
als ich auf einem PC eine rekursive Funktion programmiert hab. Auf
Embedded Systemen macht man aber eh alles iterativ, von daher:
Kann es sein, dass man hier zu einer Lösung ein Problem sucht? ;-)
Mark B. schrieb:> Gegenfrage:> Wie soll es denn bei einem typischen Embedded System zu einem Überlauf> des Stacks kommen?
Bei dir selbstverständlich nicht, weil du ja niemals Fehler machst, es
soll aber auch Programmierer geben, die manchmal einen Fehler übersehen.
Teilnehmer dieses Forums natürlich ausgeschlossen.
Georg
Georg schrieb:> Bei dir selbstverständlich nicht, weil du ja niemals Fehler machst, es> soll aber auch Programmierer geben, die manchmal einen Fehler übersehen.> Teilnehmer dieses Forums natürlich ausgeschlossen.
Klar passieren Fehler. Ein Stack Overflow ist aber alles in allem doch
eher untypisch. Da gibt es andere, viel häufiger vorkommende
Fehlerklassen:
-Zugriff über nicht oder nicht korrekt initialisierten Zeiger
-Eingangswerte einer Funktion nicht auf Plausibilität geprüft
-Fehler beim Konvertieren von Datentypen
-Array-index-out-of-bound Zugriffsfehler
-Funktion wurde falsch oder unvollständig spezifiziert
-...
Mark B. schrieb:> Ein Stack Overflow ist aber alles in allem doch eher untypisch.
Kommt drauf an. In einem RTOS kannst du für jeden Thread deinen Stack
definieren. Je nach Umfang muss man da schonmal an die Grenzen gehen.
Zumindest in der Entwicklung beim Debuggen mit -Og oder -O0 kam der eine
oder andere Thread schonmal ans Limit. Untypisch oder selten sind RTOS
nicht.
Frank M. schrieb:> Bei UNIX, dem "Vorfahren" von Linux, war der Speicherschutz schon> vorhanden, als DOS/Windows davon noch gar nicht wussten.> Selbstverständlich ist der Speicherschutz Grundvoraussetzung bei> unixoiden Betriebssystemen, wozu auch Linux gehört.
Das ist sicher so.
Aber im Gegenzug kann man sagen: DOS und Windows liefen schon auf
günstigen PCs, die sich sehr viele leisten konnten, als Unix daran noch
völlig gehindert war, weil es eben dank der unbedingten Notwendigkeit
einer MMU auf teure Workstations oder extrem teure Großrechner
beschränkt war.
Nun, die 386er haben dann eine MMU bekommen und Windows hat gelernt,
diese zu nutzen. Und Unix (in Form von Linux) hat es dann etwas später
auch geschafft.
Und so kann ich heute problemlos BEIDES auf meinen PCs benutzen. Ist
doch geil, manchmal gehen Geschichten auch richtig gut aus...