Interrupt software 0x80 fire GPF

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
MikyBart
Posts: 9
Joined: Tue Sep 03, 2024 9:21 am
Libera.chat IRC: MikyBart

Interrupt software 0x80 fire GPF

Post 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
User avatar
iansjack
Member
Member
Posts: 4668
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Interrupt software 0x80 fire GPF

Post 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.
MikyBart
Posts: 9
Joined: Tue Sep 03, 2024 9:21 am
Libera.chat IRC: MikyBart

Re: Interrupt software 0x80 fire GPF

Post by MikyBart »

Ok, thanks for the advice
Octocontrabass
Member
Member
Posts: 5440
Joined: Mon Mar 25, 2013 7:01 pm

Re: Interrupt software 0x80 fire GPF

Post 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.
User avatar
iansjack
Member
Member
Posts: 4668
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Interrupt software 0x80 fire GPF

Post 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?
MichaelPetch
Member
Member
Posts: 734
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Interrupt software 0x80 fire GPF

Post 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?
MikyBart
Posts: 9
Joined: Tue Sep 03, 2024 9:21 am
Libera.chat IRC: MikyBart

Re: Interrupt software 0x80 fire GPF

Post 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
MichaelPetch
Member
Member
Posts: 734
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Interrupt software 0x80 fire GPF

Post 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.
MikyBart
Posts: 9
Joined: Tue Sep 03, 2024 9:21 am
Libera.chat IRC: MikyBart

Re: Interrupt software 0x80 fire GPF

Post 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
MichaelPetch
Member
Member
Posts: 734
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Interrupt software 0x80 fire GPF

Post 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.
MikyBart
Posts: 9
Joined: Tue Sep 03, 2024 9:21 am
Libera.chat IRC: MikyBart

Re: Interrupt software 0x80 fire GPF

Post 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
MichaelPetch
Member
Member
Posts: 734
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Interrupt software 0x80 fire GPF

Post 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
Post Reply