User:Johnburger/Demo/Exec/Ints/Fault

From OSDev Wiki
Jump to navigation Jump to search

This module provides two generic routines to handle the complexities of installing and handling Faults when a complete Task context is required for them.

Exec.Ints.Fault:

This function sets up a mini-environment inside the Executive's LDT:

  1. It allocates RAM for a Stack for the Handler, and adds it to the LDT;
  2. It allocates RAM for a TSS, and initializes it to point to the Fault Handler;
  3. It adds the new TSS to the GDT, and an alias to the TSS in the LDT;
  4. It updates the Fault's entry in the IDT to point to the new TSS with a Task Gate.

Exec.Ints.Fault.Handler:

This function is assumed to be called by the Handler installed above:

  1. It checks to see if Interrupts are OK (see below);
  2. It then references its own TSS alias, to fish out the Backlink Task field. It's a pity that Task State Segments aren't readable by Supervisor code - this is what the Alias created above is for;
  3. It then brute-force copies the Descriptor for the Faulting TSS from the GDT into the (reserved) Fault Alias - that copies the .Base and .Limit fields across;
  4. It then updates the Fault Alias to be a mere Data Descriptor, all ready for the generic Fault handler to fish out the register values for the Faulting Task;
  5. It then CALLs the generic Fault handler - a FAR call since it's in the Global Interrupt Code Segment, rather than this Local Code;
  6. Upon return, it fixes up the Stack and RETurns for the next Fault.

Extant Interrupts

I assume that the user may want to invoke the Debugger while the registers are being shown, which requires the Keyboard - but unfortunately the Fault might have occurred during the servicing of an interrupt!

Scenario

Here's the scenario:

  • The Stack is very close to its Limit.
  • A Timer interrupt occurs, which PUSHes EFlags, CS and EIP onto the Stack. The Stack is now full!
  • The Timer handler needs EAX, so PUSHes it - which underflows the Stack causing a Stack Fault exception.
  • The CPU switches Tasks to the Stack Fault Handler, which calls this generic Handler, which calls the generic Fault handler.

But at this point the Timer interrupt is still extant: there's a new Stack ready and able to handle the interrupt, but no way of re-triggering it! That's not so bad for the Timer interrupt, but it has a higher priority inside the Priority Interrupt Controller (PIC) than the Keyboard interrupt - so no Keypresses are going to get handled either. And of course it might have been the Keyboard interrupt that caused the Stack Fault, with the same effect: no Keypresses.

"Solution"

A naïve programmer might just emit an End-Of-Interrupt (EOI) command (or two!) to the PIC - but that's definitely not the correct strategy. The PIC can be interrogated to find out if there are any Interrupts in-service - which is what this code does.

First, it isolates whether either the Timer or Keyboard interrupts are extant - they're the first two, with the highest priority, and I'm only interested in the Keyboard for this Demonstrator anyway. If the Timer interrupt is extant, it simply does an EOI - there'll be another in a millisecond anyway - and goes back to look again. If the Keyboard interrupt is extant, it calls its handler with an INT instruction.

After that, the PIC should be sufficiently "satisfied" to let more Keyboard interrupts through - and we can use the Debugger!

Caution

Note that I do not advocate doing this in a real system. I coded it here because I wanted to be able to debug the various memory structures after a Stack Fault, and I wanted my keyboard back!

Demo/Exec/Ints/Fault.inc

;
; Exec/Ints/Fault.inc
;

; This module is the generic Fault handler, for Faults that need their own
; runtime environment: Stack, TSS, etc. It presumes the following:
; 1) The LDT has three consecutive entries for the Fault Handler:
;    a) Stack - This will be created
;    b) TSS   - This will be created and set up.
;    c) Alias - This will be used by the Fault handler to alias the Faulting TSS
; 2) The .Fault function installs the Handler using the provided values.
; 3) The .Handler function will be called by the specific Fault handler.

Exec.Ints.Fault:

Exec.Ints.Fault.Stack.Size   EQU   1000h        ; Give ourselves PLENTY of room!

%push Ints.Fault ; Let's not leave these %defines just lying around...

%define         %$IDT.Entry     EBP - 4
%define         %$LDT.Entry     EBP - 8
%define         %$Handler       EBP - 12
%define         %$Base          EBP - 16
%define         %$TSS           EBP - 20

                ENTER           20, 0

                MOV             [%$IDT.Entry],EAX
                MOV             [%$LDT.Entry],EBX
                MOV             [%$Handler],EDX

                MOV             AX,Selector(Exec.LDT.Alias, LDT, RPL0)
                MOV             ES,AX         ; Point to LDT alias

                ; Allocate Stack for Fault handler
                MOV             ECX,Exec.Ints.Fault.Stack.Size
                CALL            Exec.Alloc.RAM
                TEST            EAX,EAX
                JZ              .End

                ; Store in LDT
                MOV             DL,Type.Mem(Stack, DPL0, RW)
                MOV             DH,Gran.Mem(Byte,  Big)
                CALL            Exec.Alloc.DT.Mem

                ; Allocate TSS
                SLDT            AX            ; My LDT
                CALL            Exec.Alloc.TSS
                TEST            EAX,EAX
                JZ              .End

                ; Remember its Base
                MOV             [%$Base],EAX

                MOV             EBX,[%$LDT.Entry]
                OR              BX,WORD x86.Seg.TI      ; Index inside LDT
                MOV             EDX,[%$Handler]

                MOV             [ES:x86.TSS.SS],BX
                MOV             [ES:x86.TSS.CS],CS
                MOV             [ES:x86.TSS.EIP],EDX

                MOV             AH,System.TSS   ; Oh BOY is it a System TSS!
                CALL            Exec.Alloc.TSS.Enable

                MOV             [%$TSS],EBX

                MOV             AX,Selector(Exec.LDT.Alias, LDT, RPL0)
                MOV             ES,AX             ; Point to LDT alias again

                MOV             EAX,[%$Base]
                MOV             EBX,[%$LDT.Entry]
                ADD             EBX,x86.Desc_size ; Point to .TSS alias in LDT
                MOV             DL,Type.Mem(Data, DPL0, NoRW) ; TSS Alias
                MOV             DH,Gran.Mem(Byte, Small)
                CALL            Exec.Alloc.DT.Mem

                ; Finally, update IDT Entry to tell it it's now handled by a TSS
                XOR             EAX,EAX
                MOV             EBX,[%$IDT.Entry]
                MOV             ECX,[%$TSS]
                MOV             DL,Type.Sys(Task, DPL0, 286) ; No '386 variant
                CALL            Exec.Alloc.IDT
.End:
                LEAVE
                RET
%pop
;...............................................................................
.Handler:
                STI                           ; Interrupts may have been off

.TestIRQs:
                ; Worse, Fault may have happened during an IRQ handler!
                ; Isolate IRQ handlers that have started but aren't acknowledged
                MOV             AL,Dev.PIC.Cmd.ISR
                OUT             Dev.PIC.A.Cmd,AL
                JMP             $+2             ; Wait for I/O bus
                IN              AL,Dev.PIC.A.Cmd

                AND             AL,Dev.PIC.A.Key|Dev.PIC.A.Timer; Only worried about these
                JZ              .NoInterrupts    ; Nothing to worry about!
                CMP             AL,Dev.PIC.A.Key ; If keypress outstanding...
                JE              .KeyInterrupt    ; Handle it
                MOV             AL,Dev.PIC.Cmd.EOI ; Acknowledge
                OUT             Dev.PIC.A.Cmd,AL ; current IRQ (should be Timer)
                JMP             .TestIRQs        ; And keep looking

.KeyInterrupt:
                INT             (IDT.Key-IDT)/x86.Desc_size ; Re-invoke the Key interrupt
; Continue
.NoInterrupts:
                MOV             AX,Selector(GDT.Alias, GDT, RPL0)
                MOV             DS,AX         ; Need information from GDT
                MOV             AX,Selector(Exec.LDT.Alias, LDT, RPL0)
                MOV             ES,AX         ; Need to update alias in LDT
                LEA             AX,[BX + x86.Seg.TI] ; Need my TSS alias in LDT
                MOV             FS,AX

                ADD             EBX,x86.Desc_size ; Point to TSS alias in LDT

                MOVZX           ESI,WORD [FS:x86.TSS.Back] ; Who called me?

                PUSH            ESI               ; Need this on Stack to display Task

                ; Copy faulting TSS definition, then alter .Type to make it an alias
                CLD                               ; Work forwards
                MOV             ECX,x86.Desc_size / 4      ; Number of DWords
                MOV             EDI,EBX
                REP             MOVSD             ; Brute force details across!
                MOV             [ES:EBX+x86.Desc.Type],BYTE Type.Mem(Data, DPL0, NoRW)

                LEA             AX,[BX + x86.Seg.TI] ; TSS alias in LDT
                MOV             FS,AX

                MOV             EDI,[ESP+8]       ; Load Interrupt number
                CALL            GDT.Ints : Ints.Fault.External

                ADD             ESP,4             ; Ignore Back Task
                RET