Autor Thema: Interrupts überschreiben lokale Variablen?  (Gelesen 8594 mal)

OsDevNewbie

  • Beiträge: 282
    • Profil anzeigen
    • YourOS Kernel
Gespeichert
« am: 26. July 2013, 20:03 »
Hallo liebe Kollegen,
ich habe momentan Probleme mit meinem Multitasking. Irgendwie werden Daten auf dem Stack überschrieben. Ich programmiere meinen Kernel für den Long Mode (64-Bit Mode). Ich habe, um mein Multitaskingsystem zu testen zwei Tasks erstellt, die einfach Sachen ausgeben mit denen man identifieren kann, welcher Task gerade etwas ausgibt. Zuerst aber gab nachdem der zweite Task an der Reihe war, der erste nie mehr etwas aus, obwohl er auch an die Reihe kam. Ich dachte vielleicht ist etwas falsch an der Sleep-Funkion, die ich in beiden Tasks verwendete. Deswegen änderte ich die Ausgabe in "A" bei Task A und "B" bei Task B. Jetzt kommt aber ein Page Fault, wenn Task A wieder an der Reihe ist, d.h. jeder Task kommt einmal an die Reihe und danach krachts.
Nach einigem Debuggen bin ich darauf gestossen, dass beim Laden einer lokalen Variablen vom Stack, plötzlich der Wert eines Segementregisters auftaucht und dieser dann als Addresse verwendet wird (0x10).
Mir ist dann aufgefallen, dass der Compiler (gcc) zwar bei jeder Funktion einen Stackframe anlegt und zwar wie folgt:
push %rbp
mov %rsp,%rbp
Folgender Code wird aber nur hinzugefügt, wenn innerhalb der Funktion eine andere Funktion aufgerufen wird:
sub $Wert,%rsp
Und deshalb frage ich mich, was passiert, wenn eine Funktion durch einen Interrupt abgebrochen wird, bei dem der Compiler vom Stackpointer nichts abzieht? Denn eigentlich müsste doch dann die CPU und der Interrupthandler die lokalen Daten der Funktion überschreibt. Ist das so oder ist das nur eine falsche Überlegung von mir?
Und wenn das stimmt, wie kann man sowas verhindern?
Danke.
Viele Grüsse
OsDevNewbie

Ein Computer ohne Betriebsystem ist nicht mehr wert als ein Haufen Schrott.
Ein Computer ist eine Maschine, die einem Lebewesen das kostbarste klaut, was sie selber nicht hat:
DIE ZEIT DES LEBENS

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 27. July 2013, 16:45 »
Ja, der Interrupt Handler wird den Stackinhalt überschreiben. Der Compiler ändert den Wert in esp nicht, weil er glaubt, dass er die Red Zone nutzen darf. Du musst dem Compiler den Parameter --mno-red-zone übergeben, um das zu unterbinden.
« Letzte Änderung: 27. July 2013, 16:53 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 28. July 2013, 12:01 »
Gibt es irgendwo eine passende Stelle im Wiki, wo man das erwähnen könnte? Klingt nach einer bösen Falle für x86_64.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

streetrunner

  • Beiträge: 67
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 28. July 2013, 12:30 »
Vielleicht wäre es gut eine neue Seite zu erstellen wo man die gängigsten Compiler-Flags auflistet und kurz erklärt. Da könnte man auch -mno-red-zone mit reinpacken und direkt dazu schreiben dass es wohl besser ist dieses Flag für x86_64 zu nutzen.

Sonst könnte man auch hier http://www.lowlevel.eu/wiki/Long_Mode einfach einen neuen Unterpunkt "Probleme im Long Mode" o.Ä. aufmachen.

OsDevNewbie

  • Beiträge: 282
    • Profil anzeigen
    • YourOS Kernel
Gespeichert
« Antwort #4 am: 28. July 2013, 15:51 »
Wie kann man den dieses Flag umgehen? Also wie wird das von Windows oder Linux bearbeitet, bzw. wie muss ich das in meinem Kernel handlen, wenn ein Interrupt aufgerufen wird? Ich möchte gerne dieses Flag umgehen, da es ja anscheinend auch unter Linux funktioniert, aber wie bringe ich die CPU dazu erst 128 Bytes zu überspringen, bevor sie beginnt ihre Sachen auf den Stack zu schreiben?
Viele Grüsse
OsDevNewbie

Ein Computer ohne Betriebsystem ist nicht mehr wert als ein Haufen Schrott.
Ein Computer ist eine Maschine, die einem Lebewesen das kostbarste klaut, was sie selber nicht hat:
DIE ZEIT DES LEBENS

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 28. July 2013, 16:11 »
Für den Userspace ist es ja kein Problem, weil du da einen Ring- und damit auch einen Stackwechsel hast.

Beim Kernel musst du das Flag wohl oder übel benutzen. Linux hat es in arch/x86/Makefile auch drin:
KBUILD_CFLAGS += -mno-red-zone
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #6 am: 31. July 2013, 13:16 »
Auch für den Kernel kann man im LM einen Stackwechsel erreichen. Man kann über die IST (Interrupt Stack Table) jedem Interrupt einen von insgesamt bis zu 7 festen Stacks zuweisen. Dann muss man nur sicherstellen, dass sich nie zwei Interrupts mit dem selben Stack in die Quere kommen.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 31. July 2013, 13:49 »
Ich habe noch ne einen LM-Kernel gebastelt, aber ich nehme an, für Exceptions wird das schwer zu garantieren sein.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 31. July 2013, 14:46 »
Eigentlich ist es für Exceptions relativ einfach. Bis auf den DoubleFault können die sich alle den selben Stack teilen.

Edit: Schein bar habe ich da den Double Fault falsch verstanden. Damit ist dass dann doch nicht so einfach wie ich dachte. Außer man schreibt einfach fehlerfreie Exception-Handler  :-D.
« Letzte Änderung: 31. July 2013, 15:51 von MNemo »
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

OsDevNewbie

  • Beiträge: 282
    • Profil anzeigen
    • YourOS Kernel
Gespeichert
« Antwort #9 am: 31. July 2013, 15:10 »
Danke für eure Tipps.

Auch für den Kernel kann man im LM einen Stackwechsel erreichen. Man kann über die IST (Interrupt Stack Table) jedem Interrupt einen von insgesamt bis zu 7 festen Stacks zuweisen. Dann muss man nur sicherstellen, dass sich nie zwei Interrupts mit dem selben Stack in die Quere kommen.
Danke dass du mich erinnerst, dass es ja auch noch die IST gibt.

Eigentlich ist es für Exceptions relativ einfach. Bis auf den DoubleFault können die sich alle den selben Stack teilen.
Warum kann sich der DOuble Fault keinen Stack teilen?

Noch eine Frage nebenbei: Wie gross macht ihr euren Stack für den Kernel? Der wird ja eigentlich nur zum Initialisieren benutzt und später nicht mehr. Bei ist er 1MB gross. Könnte man den noch verkleinern? Wie stellt man fest, wieviel maximal vom Stack gebraucht wird? Wie gross sind eure Interrupts-Stacks?
Viele Grüsse
OsDevNewbie

Ein Computer ohne Betriebsystem ist nicht mehr wert als ein Haufen Schrott.
Ein Computer ist eine Maschine, die einem Lebewesen das kostbarste klaut, was sie selber nicht hat:
DIE ZEIT DES LEBENS

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #10 am: 31. July 2013, 15:34 »
Warum kann sich der DOuble Fault keinen Stack teilen?
Weil der Double Fault ausgelöst wird, wenn der Exception handler einer anderen Exception eine weitere Exception auslöst. Der Stack wird also schon benutzt, und würde überschrieben werden. Wenn man den Stack von der Original Exception nicht mehr braucht, weil man so wieso nur noch den Bildschirm rot macht, ist das kein Problem. Aber wenn man den Stack evtl. noch für nen StackTrace braucht, wäre es schlecht den zu überschreiben.
Edit: Ok. Ist natürlich zum größten teil Blödsinn. Einen #DF wohl nur wenn es noch vor dem eigentlichen Exception-Handler noch eine Weitere Exception gibt. Lange pausen im OS-dev sind einfach nicht gut.

Zitat
Noch eine Frage nebenbei: Wie gross macht ihr euren Stack für den Kernel? Der wird ja eigentlich nur zum Initialisieren benutzt und später nicht mehr. Bei ist er 1MB gross. Könnte man den noch verkleinern? Wie stellt man fest, wieviel maximal vom Stack gebraucht wird? Wie gross sind eure Interrupts-Stacks?
Ich mache meine immer 4k groß. Bis jetzt war das (glaub) ich auch immer genug (aber so komplex ist mein Kernel auch nicht). Aber wie viel ich wirklich brauche weiß ich auch nicht.
« Letzte Änderung: 31. July 2013, 15:55 von MNemo »
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

Dimension

  • Beiträge: 155
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 31. July 2013, 16:02 »
Ein 4096 Bytes großer Stack reicht für 42 Funktionsaufrufe mit einem Äquivalent von 10 64-Bit Ints als Parameter oder lokale Variablen. Interrupts sollten nicht re-entrant sein, also nicht während einer ISR auftreten dürfen. Ich setze den Stack an den Segmentanfang um Overflows zu erkennen. In der Mainloop meines Schedulers wird der Kernel-Stack nach jedem Task zurückgesetzt, da hier eh keine lokalen Variablen liegen.

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #12 am: 01. August 2013, 08:48 »
Edit: Schein bar habe ich da den Double Fault falsch verstanden. Damit ist dass dann doch nicht so einfach wie ich dachte. Außer man schreibt einfach fehlerfreie Exception-Handler  :-D.
Exceptions bedeuten nicht immer Fehler. Du musst dazu noch auf jeden planmäßigen Einsatz von Exceptions verzichten. Also kein Swapping oder COW für irgendwelchen Kernelcode, zum Beispiel. Bei einem Mikrokernel macht das nichts, bei einem Monolithen tut es vermutlich auf die Dauer weh.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

 

Einloggen