User:Johnburger/Demo/Ints/Fault
The following code uses the Ints.Names
table to display all the register names, fetch their values from the relevant locations, and display them in Hex.
When the register data is on the stack, it is called a "Frame". It was quite complicated to establish the Frame in a standardized manner, and some hoops were jumped through to get the memory layout consistent so that different display code could use the same Ints.Names
table. To allow that to happen, some extra routines were provided that other code could call to ensure this consistency: see .Frame.Create
and .Frame.Destroy
.
And then .ShowRegs
itself had to handle fetching the register values from the correct location: either the stack frame or from a TSS alias. The Ints.Names
table has both indexes.
Demo/Ints/Fault.inc
; ; Ints/Fault.inc ; ; This is the general-purpose Fault handler. It simply writes out as much ; information as it can find onto the screen, to allow poor you to work out ; where the problem is! Ints.Fault: PUSHAD ; Save all registers CALL .Frame.Create ; Create Stack Frame for display XOR AX,AX ; No TSS! MOV FS,AX MOV EDI,[ESP+64] ; Load Interrupt number CALL .ShowRegs CALL .Frame.Destroy ; Destroy created Stack Frame POPAD ; Note this doesn't replace ESP! ADD ESP,8 ; Ignore Int and Error values IRETD ; And return ;............................................................................... ; This function pushes registers on the stack for display purposes - oh, and so ; I can use some of them! To standardise the stack layout and make the Ints.Names ; table consistent, other code may call it. ; ; Of course, the trick with using a function to save registers is that you call ; the function, then return from it, popping the values you just saved. But with ; assembly, you can perform a trick: POP the return address first, save what ; needs saving, PUSH the return address, and then RET. Actually, the last two ; steps can be done away with: just JMP to the saved register! Ints.Fault.Frame.Create: POP EDX ; Pop off return address PUSH DS ; So these values can go there PUSH ES PUSH FS PUSH GS PUSH SS TEST WORD [ESP+64],x86.Seg.RPL3 ; From different Privilege Level? JZ .SamePL ; No MOV EAX,[ESP+76] ; Yes, so get old SS MOV [ESP],EAX ; Replace my value MOV EAX,[ESP+72] ; Get old ESP MOV [ESP+32],EAX ; Replace my value .SamePL: STR AX ; Include faulting task PUSH EAX SLDT AX ; And faulting LDT PUSH EAX MOV EAX,CR3 ; And Page Directory Base Register PUSH EAX JMP EDX ; Return ;............................................................................... Ints.Fault.Frame.Destroy: POP EDX ; Pop off return address ADD ESP,16 ; Ignore saved CR3, LDT, TR and SS POP GS ; Pop everything else POP FS POP ES POP DS JMP EDX ; Return ;............................................................................... ; This function is called externally (FAR), so that it can perform the NEAR call Ints.Fault.External: CALL Ints.Fault.ShowRegs RETF ;............................................................................... Ints.Fault.ShowRegs: MOV EBP,ESP ; Save for display purposes CALL Ints.System.Init PUSH EAX PUSH CS POP DS ; Point to code, for Ints.Names MOV AX,Selector(GDT.VGA, GDT, RPL0) MOV ES,AX ; Point to screen with ES MOV ESI,Ints.Names ; Point to Names SHL EDI,1 ; Turn IntNum into screen address INC BYTE [ES:EDI] ; Dingle(tm) that this INT happened MOV AH,Dev.VGA.RedBack | Dev.VGA.White ; White-on-red! ADD EDI,VGA.Cols * 2 ; On next row CLD ; Work forwards .Dump: MOV ECX,Ints.Names.Label ; Width of label; zero high INC ESI ; Ignore Debug pos .Name: LODSB ; Get character STOSW ; Store attrib+char LOOP .Name ; For each character MOV AL,':' STOSW LODSB ; Get size of register MOV CL,AL ; Into counter MOV EDX,FS ; From TSS? TEST EDX,EDX ; Defined? JNZ .InTSS ; Yes. Go look at it .OnStack: LODSB ; Get position on stack INC ESI ; Ignore position in TSS MOVZX EDX,AL ; Into memory offset .FromStack: MOV EDX,[EBP+EDX] ; Get value from stack JMP .Show .InTSS: INC ESI ; Ignore Stack position LODSB ; Get position in TSS (FS) MOVZX EDX,AL ; Into memory offset CMP AL,x86.TSS.CR3 ; Down low? JB .FromStack ; Yes. Must be on stack instead MOV EDX,[FS:EDX] ; No. So get value to show .Show: PUSH EDI ; Save starting location CALL Ints.Hex ; Display as Hex POP EDI ADD EDI,(VGA.Cols-Ints.Names.Label-1)*2 ; Next row CMP ESI,Ints.Names.End ; End of registers? JB .Dump ; No, so continue HLT ; Nothing more to do ; If interrupts are disabled, the HLT will "zombie" the PC, requiring a power ; off. VMware detects this condition, but it lets you look at the screen first! ; If interrupts are enabled, one will wake us up and continue from here, which ; will then return and resume the faulting instruction (presumably). Note that ; it may also invoke a fault-"storm", generating new faults that will overwrite ; what's just been written. In other words, what you see is the last thing that ; happened, not the first! To only see the first, uncomment the following line; ; JMP $-1 ; Here's an example of what could happen: Say the User Code forgets to RET from ; a function, and that function is at the end of the Code Segment. The next ; instruction fetch will be past the end of CS, so a GPF will be raised. All of ; this .Fault code will display the problem (it might take you a bit to work out ; that CS has overflowed, but anyway...) and then this code will clean up and ; IRET to the problem area. But that's past CS, so now it is the .Fault's IRET ; that's the culprit! A new GPF will be generated, displaying the new details, ; blaming this very code! POP EBX CALL Ints.System.Done RET