Nein, das war nicht meines, sondern das "übersetzte" von tyndur.
Hm, aus kernel2... mit dem hab ich nichts zu tun, deswegen hab ich das nicht erkannt..
Ich hatte darum gebeten zu prüfen, ob ich von AT&T sauber nach Intel übersetzt habe.
Meines sieht so aus:
[...]
Sorry, hab ich übersehen. Sieht aber in Ordnung aus.
Wenn allerdings ein regs log bei einer exception stattfindet, stimmen die Werte nicht zusammen, keine Ahnung warum.
Du musst beachten, dass manche Exceptions einen Fehlercode auf den Stack legen, und manche nicht. (Und IRQs auch nicht.) Bei Exceptions ohne Fehlercode und IRQs solltest du direkt am Anfang 0 auf den Stack legen. Irgendwie so sollte das bei dir aussehen:
; Beispielsweise legt Exception 0 (Division durch 0) keinen Fehlercode
; auf den Stack, deswegen machen wir das.
exception_stub_0:
push dword 0 ; Ersatz für Fehlercode
push dword 0 ; Nummer des Interrupts
jmp irq_common_stub
...
; Bei Exception 8 legt die CPU selbst einen Fehlercode auf den Stack, also machen wir das *nicht*
exception_stub_8:
push dword 8 ; Nummer des Interrupts
jmp irq_common_stub
...
; IRQs legen keinen Fehlercode auf den Stack, deswegen machen wir das.
irq_stub_0:
push dword 0 ; Ersatz für Fehlercode
push dword 32 ; Nummer des Interrupts (IRQ 0 ist z.B. nach Interrupt 32 gemappt)
jmp irq_common_stub
...
Diese kurzen 2- bis 3-zeiligen Stubs springen zum "größeren" Stub, der dann die ganzen Register sichert, und zur C-Routine springt. Die hab ich mal "irq_common_stub" genannt, weil die aus deinem vorherigen Post (vermutlich) genau passen sollte (wichtig ist das add esp, 8, das die pushs wieder rückgängig macht).
Ich hoffe das ist so einigermaßen verständlich.
Allerdings eignet sich das nicht, um esp zu ändern, weil bei einem popa esp nicht geladen wird. Deswegen hat handle_int den Rückgabewert für esp.
Moment, das ist wichtig. Du meinst, wenn ich pusha/popa durchführe, ohne esp wie bei euch als ersten Parameter auf dem Stack an eine C-Funktion zu geben, hat eine esp-Änderung im irq_handler keine Wirkung, weil durch popa das alte esp wieder zurück geladen wird?
Nein, der Grund ein anderer. PUSHA legt zwar die Werte der 8 Register inkl. ESP auf den Stack, aber POPA überspringt ESP einfach beim auslesen. In den Intel Manuals (Volume 2 - Instruction Set Reference) ist das so beschrieben:
POPA:
EDI <- Pop();
ESI <- Pop();
EBP <- Pop();
Increment ESP by 4; (* Skip next 4 bytes of stack *)
EBX <- Pop();
EDX <- Pop();
ECX <- Pop();
EAX <- Pop();
POPA lädt ESP also gar nicht. Das liegt zwischen EBP und EBX, aber wird einfach übersprungen.
Ich weiß nicht mehr, ob ich das war, der die Interrupt Handler (in kernel(1)) geschrieben hat, aber wenn ich es gewesen wäre, dann hätte ich kein POP ESP verwendet, weil mir das zu unübersichtlich geworden wäre. Einfach direkt den Stack wechseln mittels des Rückgabewertes an einer Stelle, die wenig Probleme verursacht, ist hingegen recht einfach.
Direkt nach dem Aufruf der C-Routine ist halt nichts besonderes auf dem Stack, und dies erleichert es später für die Erstellung von neuen Tasks den Stack "aus dem Nichts" aufzubauen. Der Code
dafür in tyndur ist natürlich mit der Zeit gewachsen, dennoch spiegelt er imo gut den Aufbau des Stacks wider. Vor allem muss er nicht mit irgendwelchen Tricks arbeiten um zu berücksichtigen, dass in der Mitte von irgendwas (C-Code, POP-Serie, ...) der Stack gewechselt wird.
Noch eine Frage zu meiner regs struct:
Ist das die Reihenfolge der gepushten register bis zum irq_handler oder hat dies seine eigene konstante Regel
Teils, teils. CS, EIP, EFLAGS sind immer dabei. SS und ESP nur, wenn der Interrupt nicht im Kernel Mode (also Ring 1-3) auftrat. (Jep, die Beiden sind die Werte aus dem User Mode.) Außerdem legt die CPU selbst manchmal den Fehlercode auf den Stack. Auf diese 3 bis 6 Werte und deren Anordnung hast du keinen direkten Einfluss.
Die anderen Register (General Purpose Register, Segment Register), kannst du in beliebiger Reihenfolge nach den anderen Registern auf den Stack sichern (oder auch nicht).
Der Aufbau der Stackframes ergibt sich wohl einfach daraus, dass sich eine Art natürliche Ordnung ergibt: Zuerst werden die "flüchtigsten" Register (CS:EIP, SS:ESP) gesichert, dann die "weniger flüchtigen" (EAX, ...), dann erst die Segmentregister, die sich sehr selten ändern.
Warum er vor dem iret ein sti setzt, ist mir nicht klar.
Mir ebenfalls nicht. Interrupts und deren Sperrung im Kernel sind eine Geschichte für sich ...
Ich komm ja kaum hinterher ...
Wenn ich eure Technik verwende, kann ich dann nach dem ersten Parameter esp die regs zusätzlich abfangen?
ULONG irq_handler1(ULONG esp, struct regs* r){...}
Viel besser: esp ist gleich r, denn der Parameter esp zeigt auf den Stack, genauer gesagt auf die ganzen Register. Der C-Handler sieht in tyndur im Prinzip so aus:
unsigned int handle_int(unsigned int esp)
{
struct regs * regs = *((struct regs **)esp);
// und hier kannst du mit regs weiter arbeiten.
}
Und der "eigentliche Taskswitch" ist dann das mov %eax, %esp im allgemeinen Interrupthandler-Code.
... und was ist mit eip?
Das liegt auf dem Stack.