User:Johnburger/Demo/Exec/Ints/Fault
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:
- It allocates RAM for a Stack for the Handler, and adds it to the LDT;
- It allocates RAM for a TSS, and initializes it to point to the Fault Handler;
- It adds the new TSS to the GDT, and an alias to the TSS in the LDT;
- 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:
- It checks to see if Interrupts are OK (see below);
- 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;
- 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; - 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;
- It then
CALL
s the generic Fault handler - aFAR
call since it's in the Global Interrupt Code Segment, rather than this Local Code; - Upon return, it fixes up the Stack and
RET
urns 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
PUSH
esEFlags
,CS
andEIP
onto the Stack. The Stack is now full! - The Timer handler needs
EAX
, soPUSH
es 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