This module provides two generic routines to handle the complexities of installing and handling Faults when a complete Task context is required for them.
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.
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
- 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
CALLs the generic Fault handler - a
FARcall since it's in the Global Interrupt Code Segment, rather than this Local Code;
- Upon return, it fixes up the Stack and
RETurns for the next Fault.
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!
Here's the scenario:
- The Stack is very close to its Limit.
- A Timer interrupt occurs, which
EIPonto the Stack. The Stack is now full!
- The Timer handler needs
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.
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
After that, the PIC should be sufficiently "satisfied" to let more Keyboard interrupts through - and we can use the Debugger!
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!
; ; 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