Page 1 of 1
Interrupt software 0x80 fire GPF
Posted: Tue Oct 01, 2024 12:27 pm
by MikyBart
Hi all, I have implemented a software interrupt handler on gate 0x80 and set the trap gates for division by zero etc.
Every time a software interrupt is executed on my handler (syscall) a Gpf (general protection fault) is also generated.
Here is my GDT.
Code: Select all
BITS 64
SECTION .rodata
gdt64:
dq 0
.code: equ $ - gdt64
dq (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
.data: equ $ - gdt64
dq (1<<44) | (1<<47) | (1<<41)
.pointer:
dw .pointer - gdt64 - 1
dq gdt64
GLOBAL load_GDT
EXTERN debug_msg
SECTION .text
load_GDT:
cli
lgdt [gdt64.pointer]
sti
ret
Re: Interrupt software 0x80 fire GPF
Posted: Tue Oct 01, 2024 12:39 pm
by iansjack
Difficult to comment on as you don’t show your code.
As you know that the gpf is occurring when you call a software interrupt it should be trivial to trace the faulting instruction. Run your code under a debugger, set a breakpoint on the interrupt call and single-step until the exception.
Re: Interrupt software 0x80 fire GPF
Posted: Tue Oct 01, 2024 1:24 pm
by MikyBart
Ok, thanks for the advice
Re: Interrupt software 0x80 fire GPF
Posted: Tue Oct 01, 2024 1:27 pm
by Octocontrabass
You don't even need to trace anything. The exception itself tells you which instruction caused the exception: just check the return address on the stack.
Re: Interrupt software 0x80 fire GPF
Posted: Tue Oct 01, 2024 1:40 pm
by iansjack
You don’t need to trace anything, but I find it more helpful to see how one got to a particular situation rather than just seeing a snapshot. Time spent learning how to use a debugger on simple problems pays dividends when the problems are not so straightforward.
Each to their own.
As a matter of interest, how do you know that you are getting a gpf?
Re: Interrupt software 0x80 fire GPF
Posted: Tue Oct 01, 2024 4:38 pm
by MichaelPetch
If testing on QEMU you can run with the `-d int -no-shutdown -no-reboot` options and it will dump interrupt/exception information showing you the program counter (PC/RIP) where the fault occurred. v=0d exception would be a GPF and the error code e=#### will be useful. If you could provide about the last 100 lines of the output or the last few exceptions/interrupts it would be helpful. Have you put your code on a service like Github?
One thing I did notice is that you do an `sti` right after the `lgdt` instruction. If you haven't already done an `lidt` before that, you could get an interrupt (IRQ) come in after the GDTR is loaded but before the IDTR is loaded which could cause a GPF.
Edit: if I had looked at your GDT the lack of TSS and segment for ring 3 would have told me you aren't doing this from user mode, so you can ignore this for now: Since you are doing software interrupts, does that also mean you are doing int 0x80 from usermode (ring 3)? If so, did you change the DPL in the gate descriptor for 0x80 to 3? If DPL is 0 and you do an `int 0x80` from ring 3 you will get a GPF. Note: the DPL check only occurs when the `int` instruction is being used. External interrupts (IRQs) for example have no DPL check when they occur in ring 3. If a division by zero is raised DPL isn't checked, but if you do an `int 0x00` it is checked.
Edit 2: After you do the `lgdt` do you load the CS segment register with a FAR JMP and reload DS, SS, ES, FS, and GS?
Re: Interrupt software 0x80 fire GPF
Posted: Wed Oct 02, 2024 11:57 pm
by MikyBart
No for now only from level 0
I tried to reload the segments but the kernel crashes.
I wanted to not consider loading my own gdt and use the bootloader's one (limine) but then I settled on my own gdt.
My routine to install the syscall handler
Code: Select all
IDT_Install_Syscall:
push rax
push rdi
push rsi
mov rdi, IDT+(128*16)
mov rax, ISR_Syscall
push rax ; save the exception gate to the stack for later use
stosw ; store the low word (15:0) of the address
mov ax, 0x0008
stosw ; store the segment selector
mov ax, 0x8E00
stosw ; store exception gate marker
pop rax ; get the exception gate back
shr rax, 16
stosw ; store the high word (31:16) of the address
shr rax, 16
stosd ; store the extra high dword (63:32) of the address.
xor rax, rax
stosd ; reserved
mov rsi, idt_syscall_install
call debug_msg
pop rsi
pop rdi
pop rax
ret
thanks for your reply
Re: Interrupt software 0x80 fire GPF
Posted: Thu Oct 03, 2024 1:36 am
by MichaelPetch
Okay, if I understand correctly. You are not actually setting CS, DS, ES, FS, GS, and SS because you had it faulting. This is going to be a problem (correct me if I have this wrong). If you are using the selectors that Limine had - when the first interrupt/exception occurs the CS value that Limine originally loaded in CS (I believe Limine's 64-bit code segment is 0x28) will be pushed onto the stack so at the first IRETQ reloading 0x28 will be outside the size of your own GDT and should GPF.
If you aren't loading the segments after LGDT, you should go back to that point (put that code back in) to determine why that was failing.
If I have misinterpreted your last message let me know. What would be helpful is to run it in QEMU with the `-d int -no-shut-down -no-reboot` options and give us a complete dump of the last few exceptions/interrupts, or provide your code in Github (or some other service).
The placement of your STI as I commented about earlier could also be causing problems if you enabled interrupts before you loaded your IDTR and have the IDTs filled out, you could run into trouble (unless you have masked off all the external interrupts so they won't be received by the CPU). Without seeing a complete example it is going to be difficult to troubleshoot.
I just noticed your GDT is in `.rodata` . If your GDT is actually in a page marked as read only (in memory) then you'd get a page fault when the CPU attempts to set the `accessed` bit in the GDT entries (ie: when you go to load them). If you set the `accessed` bit in the GDT entries to 1 then you can avoid that problem.
Re: Interrupt software 0x80 fire GPF
Posted: Thu Oct 03, 2024 3:45 am
by MikyBart
Thank you very much for your help.
Do you suggest me to use the limine GDT and set the selector to 0x28?
Here is the dump of qemu
Code: Select all
SMM: after RSM
EAX=000000b5 EBX=000eb756 ECX=00001234 EDX=000000ff
ESI=00000000 EDI=000d9000 EBP=00008a0e ESP=0000fc5c
EIP=000eb756 EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 000f61e0 00000037
IDT= 000f621e 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000044 CCD=00000000 CCO=EFLAGS
EFER=0000000000000000
SMM: enter
EAX=000000b5 EBX=00007e28 ECX=00005678 EDX=00000002
ESI=00000000 EDI=000ede34 EBP=0000fd94 ESP=000e8d94
EIP=000f7e25 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 000f61e0 00000037
IDT= 000f621e 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000000f0 CCD=000e8d80 CCO=ADDL
EFER=0000000000000000
SMM: after RSM
EAX=000000b5 EBX=00007e28 ECX=00005678 EDX=00000002
ESI=00000000 EDI=000ede34 EBP=0000fd94 ESP=000e8d94
EIP=00007e28 EFL=00000006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =d900 000d9000 0000ffff 00009300
CS =f000 000f0000 0000ffff 00009b00
SS =d900 000d9000 0000ffff 00009300
DS =d900 000d9000 0000ffff 00009300
FS =0000 00000000 0000ffff 00009300
GS =0000 00000000 0000ffff 00009300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT= 00015c18 00000037
IDT= 00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000004 CCD=00000001 CCO=EFLAGS
EFER=0000000000000000
Servicing hardware INT=0x08
0: v=80 e=0000 i=1 cpl=0 IP=0028:ffffffff800001e5 pc=ffffffff800001e5 SP=0030:ffff80007ff6cff0 env->regs[R_EAX]=000000000000000b
RAX=000000000000000b RBX=ffff8000fd000000 RCX=ffffffff80002158 RDX=00000000000003fa
RSI=0000000000000000 RDI=ffffffff80007231 RBP=ffff80007ff6cff0 RSP=ffff80007ff6cff0
R8 =ffff8000fd035eb4 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff800001e5 RFL=00000246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
CS =0028 0000000000000000 00000000 00209b00 DPL=0 CS64 [-RA]
SS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
DS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
FS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
GS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
LDT=0000 0000000000000000 00000000 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT= ffffffff80001934 00000017
IDT= ffffffff800072b6 00000fff
CR0=80010011 CR2=0000000000000000 CR3=000000007ff5c000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000044 CCD=ffff80007ff70000 CCO=EFLAGS
EFER=0000000000000d00
check_exception old: 0xffffffff new 0xd
1: v=0d e=0028 i=0 cpl=0 IP=0008:ffffffff80000841 pc=ffffffff80000841 SP=0030:ffff80007ff6cfc8 env->regs[R_EAX]=000000000000000b
RAX=000000000000000b RBX=ffff8000fd000000 RCX=ffffffff80002158 RDX=00000000000003fa
RSI=0000000000000000 RDI=ffffffff80007231 RBP=ffff80007ff6cff0 RSP=ffff80007ff6cfc8
R8 =ffff8000fd035eb4 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff80000841 RFL=00000083 [--S---C] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
DS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
FS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
GS =0030 0000000000000000 00000000 00009300 DPL=0 DS [-WA]
LDT=0000 0000000000000000 00000000 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT= ffffffff80001934 00000017
IDT= ffffffff800072b6 00000fff
CR0=80010011 CR2=0000000000000000 CR3=000000007ff5c000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000081 CCD=0000000000000000 CCO=EFLAGS
EFER=0000000000000d00
and here are my simple service routines (I still have to build them well of course)
Code: Select all
ISR_Division_by_Zero:
cli
push rax
push r8
push r9
push rsi
mov rsi, DivisionByZero
call debug_msg
pop rsi
pop r9
pop r8
pop rax
.halt: hlt
jmp .halt ; Infinite loop
iretq
ISR_Invalid_Opcode:
cli
push rax
push r8
push r9
push rsi
mov rsi, InvalidOpCode
call debug_msg
pop rsi
pop r9
pop r8
pop rax
.halt: hlt
jmp .halt ; Infinite loop
iretq
ISR_GPF:
cli
push rax
push rsi
mov rsi, GeneralProtectionFault
Call debug_msg
pop rsi
pop rax
.halt: hlt
jmp .halt ; Infinite loop
iretq
ISR_Page_Fault:
cli
pop word [error_code_high]
pop word [error_code_low]
push rax
push r8
push r9
push rsi
mov rsi, PageFault
call debug_msg
pop rsi
pop r9
pop r8
pop rax
.halt: hlt
jmp .halt ; Infinite loop
iretq
ISR_Alignment_Check:
cli
push rax
push r8
push r9
push rsi
mov rsi, AlignmentCheck
call debug_msg
pop rsi
pop r9
pop r8
pop rax
.halt: hlt
jmp .halt ; Infinite loop
iretq
ISR_keyboard:
push rax
push rsi
mov rsi, keyb
call debug_msg
pop rsi
xor rax, rax
in al, 0x60 ; MUST read byte from keyboard (else no more interrupts).
mov [keyboard_scancode], al
mov al, PIC_EOI ; Send EOI (End of Interrupt) command
out PIC1_COMMAND, al
pop rax
iretq
ISR_systimer:
push rax
mov al, PIC_EOI ; Send EOI (End of Interrupt) command
out PIC1_COMMAND, al
pop rax
inc qword [systimer_ticks]
inc qword [tasktimer_ticks]
push rsi
mov rsi, TimerTics
call debug_msg
pop rsi
iretq
interrupt_gate:
pusha64
push rsi
mov rsi, sysc
call debug_msg
pop rsi
mov al, 0x20 ; Acknowledge the IRQ
out 0x20, al
popa64
iretq
Re: Interrupt software 0x80 fire GPF
Posted: Thu Oct 03, 2024 9:37 am
by MichaelPetch
So you can see that this happened:
Code: Select all
0: v=80 e=0000 i=1 cpl=0 IP=0028:ffffffff800001e5 pc=ffffffff800001e5 SP=0030:ffff80007ff6cff0 env->regs[R_EAX]=000000000000000b
v=80 (hex) is the interrupt number. I assume 0x80 is your system call. At the point of the exception call in the dump `CS =0028`. That is to be expected if you never reloaded CS after you did `lgdt`. The `gdt` address in the dump suggests a memory address in your own kernel so I assume that you are still using your own `gdt` here. Okay so `v=80` occurred and then you get:
Code: Select all
1: v=0d e=0028 i=0 cpl=0 IP=0008:ffffffff80000841 pc=ffffffff80000841 SP=0030:ffff80007ff6cfc8 env->regs[R_EAX]=000000000000000b
This is the GPF exception. (v=0d). The error code e=0028 tells us that there was an exception trying to use selector index 5 (0x28) in the GDT. See
https://wiki.osdev.org/Exceptions#Selector_Error_Code on decoding GPF error codes. This is what I'd expect to happen the moment IRETQ executes and tries to reload the Limine CS of 0x28. Your own GDT is only 0x18 bytes long so it is faulting there. You will also see that when this GPF happened CS=0008. This was because your ISR entry in the IDT has a selector of 0x0008 for the code segment (which is correct for your GDT). You could verify the instruction at pc(RIP)=0xffffffff800001e5 is an IRETQ instruction.
So for your question, should you use your own GDT or Limine? That is up to you, but if you ever want to get into ring 3 (user mode) you will need your own TSS so I do recommend you have your own GDT. I use my own GDT. But if you use your own then after LGDT you are going to have to do a FAR JMP to set CS to 0x0008 and then reload the data segment registers with 0x0010. Until you load CS with 0x0008 you will get this GPF If you use your own GDT.
If you modify your code to reload CS with 0x08 and it faults with a page fault (v=0e) then you are going to have to see my previous comment about the GDT being in a read only page. You can either move it to the `.data` section (read/write page) instead of `.rodata` (read only) OR set the ACCESSED bit in your GDT entries (0x08, and 0x10) to 1.
Edit: In 64-bit mode the error code pushed on the stack is a 64-bit value. I noticed some of your ISR's treat it as 2 2-byte words (4 bytes). If you make your entries in the IDT interrupt gates then interrupts/exceptions will be turned off by the CPU when your ISR handler is called. There is no need for a CLI at the top. The IRETQ will restore the RFLAGS register (including the interrupt flag) to what it was before the interrupt/exception occurred.
Re: Interrupt software 0x80 fire GPF
Posted: Thu Oct 03, 2024 11:48 am
by MikyBart
Thank you very much for your helpful advice.
by eliminating the call to the function that loaded my GDT and using the bootloader one everything works, but the problem is that then I also have to set a TSS so I will have to create my GDT.
thanks again
Re: Interrupt software 0x80 fire GPF
Posted: Thu Oct 03, 2024 12:39 pm
by MichaelPetch
Once you are going to need a TSS you should be using your own GDT. Limine doesn't supply a TSS entry in GDT (or an associated TSS). For that reason I would be loading your own GDT then use a FAR JMP to set CS to 0x0008 and load the data segment registers with 0x0010.
While I mentioned you can do a FAR JMP, it should be noted that a direct FAR JMP ptr16:64 isn't supported by the CPU, but there is an indirect JMP m16:64. An alternative that works just as well is a RETFQ to set CS. Your code could look like:
Code: Select all
load_GDT:
cli
lgdt [gdt64.pointer]
; Reload CS via a RETFQ. This simulates a ptr16:64 FAR JMP
; jmp ptr16:64 isn't actually a supported instruction.
lea rax, [rel .reload_cs]
push 0x08
push rax
retfq
.reload_cs:
; Reload the data segment registers
mov eax, 0x10
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
sti
ret
Re: Interrupt software 0x80 fire GPF
Posted: Thu Oct 10, 2024 4:19 am
by MikyBart
Thank you very much for your help.