Page 1 of 1

What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 6:30 am
by gamingjam60
I am developing a x86 architecture based 64 bit Operating System which is using limine-8.6.0 bootloader. I have enabled GDT by

Code: Select all

global gdt_flush
gdt_flush:
   LGDT  [RDI]
   jmp reloadSegments
   RET


global reloadSegments
reloadSegments:
   ; Reload CS register:
   PUSH 0x08                 ; Push code segment to stack, 0x08 is a stand-in for your code segment
   LEA RAX, [rel reload_CS]  ; Load address of reload_CS into RAX, LEA (Load Effective Address), 
   PUSH RAX                  ; Push this value to the stack
   RETFQ                     ; Perform a far return, RETFQ or LRETQ depending on syntax


reload_CS:
   ; Reload data segment registers
   MOV   AX, 0x10 ; 0x10 is a stand-in for your data segment
   MOV   DS, AX
   MOV   ES, AX
   MOV   FS, AX
   MOV   GS, AX
   MOV   SS, AX
   RET

Code: Select all

struct gdt_entry
{// 64-Bit
    uint16_t limit_low;       // Lower 16 bits of the segment limit
    uint16_t base_low;        // Lower 16 bits of the base address
    uint8_t base_middle;      // Next 8 bits of the base address
    uint8_t access;           // Access byte
    uint8_t granularity;      // Flags and upper limit
    uint8_t base_high;        // Next 8 bits of the base address
} __attribute__((packed));
typedef struct gdt_entry gdt_entry_t;

// Structure for GDTR
struct gdtr {
    uint16_t limit;
    uint64_t base; // Use uint64_t for 64-bit systems
} __attribute__((packed));
typedef struct gdtr gdtr_t;
initialized 5 GDT by

Code: Select all

void gdt_setup( uint8_t idx, uint64_t base, uint32_t limit, uint8_t access, uint8_t granularity){
    gdt_entries[idx].limit_low    = limit & 0xFFFF;       // 16 bit
    gdt_entries[idx].base_low     = base  & 0xFFFF;       // 16 bit
    gdt_entries[idx].base_middle  = (base >> 16) & 0xFF;  // 8 bit
    gdt_entries[idx].access       = access;
    gdt_entries[idx].granularity  = (limit >> 16) & 0x0F; // Set limit : lower 4 bit
    gdt_entries[idx].granularity |= granularity & 0xF0;   // Set Flags : upper 4 bit
    gdt_entries[idx].base_high    = (base >> 24) & 0xFF;  // 8 bit
}

void init_gdt(){
    //gdt_setup(index, base, limit, access, granularity)
    gdt_setup(0, 0, 0x0, 0x0, 0x0);         // null descriptor selector : 0x0
    gdt_setup(1, 0, 0xFFFF, 0x9A, 0xA0);    // kernel mode code segment, selector : 0x8
    gdt_setup(2, 0, 0xFFFF, 0x92, 0xA0);    // kernel mode data segment, selector : 0x10
    gdt_setup(3, 0, 0xFFFF, 0xFA, 0xA0);    // user mode code segment, selector : 0x18 
    gdt_setup(4, 0, 0xFFFF, 0xF2, 0xA0);    // user mode data segment, selector : 0x20

    // Calculate the GDT limit and base address
    gdtr_instance.limit = (uint16_t) (5 * sizeof(gdt_entry_t) - 1); // sizeof(gdt_entries) = sizeof(gdt_entry_t) * 5 = 40
    gdtr_instance.base = (uint64_t) &gdt_entries;

    gdt_flush((gdtr_t *) &gdtr_instance);

    print("Successfully GDT Enabled!\n");
}
Here 5 selector there is no more gdt entry although ISR is showing interrupt 13 with error code 8904 (0x22c8) which show the interrupt caused by externel event. The serror code also showing the error caused by 1113th GDT selector entry.Here I also checked the corresponding RIS value is 0xFFFFFFFF80005684 This address in objdump is showing ffffffff80005684: 48 cf iretq so the problem may releted iretq.I also checked 16 byte stack alignment before calling isr_handler and irq_handler which are defined in idt.c file.

loading idt by

Code: Select all

;
; This prgrame will load idt in idtr
; The ISR's which not push any error code we are pushing a dummy error code 0
; 0x10 is represent kernel data selector in GDT
; 0x8 is represent kernel code selector in GDT
; https://stackoverflow.com/questions/79282721/why-is-isr-common-stub-not-calling-my-isr-handler-function?noredirect=1#comment139806502_79282721
;


[extern isr_handler]        ; defined in idt.c
[extern irq_handler]        ; defined in idt.c 


;
; These macros will store the current registers into stack and restore from stack
; The order is follow by registers_t structure defined in util.h 
;

; Save all segment and general purpose registers
%macro SAVE_REGISTERS 0
    push rax
    push rbx
    push rcx
    push rdx
    push rbp
    push rsi
    push rdi

    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15

    push gs
    push fs

    mov ax, es ; we can not push es directly into stack
    push rax

    mov ax, ds ; we can not push ds directly into stack
    push rax
%endmacro


; Restore all segment and general purpose registers
%macro RESTORE_REGISTERS 0
    pop rax     ; Restore `ds` (was pushed last)
    mov ds, ax

    pop rax     ; Restore `es`
    mov es, ax

    pop fs      ; Restore `fs`
    pop gs

    pop r15
    pop r14
    pop r13
    pop r12
    pop r11
    pop r10
    pop r9
    pop r8

    pop rdi
    pop rsi
    pop rbp
    pop rdx
    pop rcx
    pop rbx
    pop rax
%endmacro


section .text
[global idt_flush]
idt_flush:
    lidt [rdi]              ; Load the IDT pointer
    ret

; Setup Interrupt Service Routine(ISR)
%macro ISR_NOERRCODE 1
    [global isr%1]
    isr%1:
        push 0               ; Pushing Dummy error code into stack
        push %1              ; Pushing Interrupt number into stack
        jmp isr_common_stub
%endmacro

%macro ISR_ERRCODE 1
    [global isr%1]
    isr%1:
                             ; don't need to push error code as it is autometically push
        push %1              ; Pusing Interrupt number only into the stack
        jmp isr_common_stub
%endmacro


isr_common_stub:
    SAVE_REGISTERS

    ; Print stack pointer before alignment
    ; mov rax, rsp
    ; call print_hex
    ; print("\n")

    and rsp, ~(0xF) ; Ensure 16-byte stack alignment

    ; Print stack pointer after alignment
    ; mov rax, rsp
    ; call print_hex
    ; print("\n")

    mov ax, 0x10             ; Load the kernel data segment descriptor
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov rdi, rsp             ; Pass the current stack pointer to `isr_handler`
    cld                      ; Required by AMD64 System V ABI
    call isr_handler
    
    RESTORE_REGISTERS
    add rsp, 16
    iretq                     ; Return from Interrupt


ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
ISR_NOERRCODE 4
ISR_NOERRCODE 5
ISR_NOERRCODE 6     
ISR_NOERRCODE 7

ISR_ERRCODE   8
ISR_NOERRCODE 9 
ISR_ERRCODE   10
ISR_ERRCODE   11
ISR_ERRCODE   12
ISR_ERRCODE   13
ISR_ERRCODE   14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_NOERRCODE 17
ISR_NOERRCODE 18
ISR_NOERRCODE 19
ISR_NOERRCODE 20
ISR_NOERRCODE 21
ISR_NOERRCODE 22
ISR_NOERRCODE 23
ISR_NOERRCODE 24
ISR_NOERRCODE 25
ISR_NOERRCODE 26
ISR_NOERRCODE 27
ISR_NOERRCODE 28
ISR_NOERRCODE 29
ISR_NOERRCODE 30
ISR_NOERRCODE 31

ISR_NOERRCODE 128   ; System Call
ISR_NOERRCODE 177   ; System Call


; Setup Interrupt Request(IRQ)
%macro IRQ 2
    [global irq%1]
    irq%1:
        push 0        ; Push a dummy error code  
        push %2       ; Push irq code
        jmp irq_common_stub
%endmacro


; This is a stub that we have created for IRQ based ISRs. This calls
irq_common_stub:
    SAVE_REGISTERS

    ; and rsp, ~(0xF) ; Ensure 16-byte stack alignment

    mov ax, 0x10    ; Load the Kernel Data Segment descriptor!
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov rdi, rsp    ; Pass stack pointer to `irq_handler`
    cld             ; Required by AMD64 System V ABI
    call irq_handler

    RESTORE_REGISTERS
    add rsp, 16     ; Clean up pushed error code and IRQ number 
    iretq           ; Return from Interrupt



IRQ   0,    32
IRQ   1,    33
IRQ   2,    34
IRQ   3,    35
IRQ   4,    36
IRQ   5,    37
IRQ   6,    38
IRQ   7,    39
IRQ   8,    40
IRQ   9,    41
IRQ  10,    42
IRQ  11,    43
IRQ  12,    44
IRQ  13,    45
IRQ  14,    46
IRQ  15,    47
When I am running in QEmu it is showing nothing but when I am running in Virtualbox Showing following error:

Code: Select all

No kernel modules found!
Successfully GDT Enabled!
Successfully IDT Enabled!
Error Code (Decimal): 8904
Error Code (Hexadecimal): 0x22C8
Decoding Error Code:
External: The fault was not caused by an external event. Table: The fault involves the Global Descriptor Table (GDT). Index: The fault involves a segment selector in the GDT/LDT. Selector Index: 1113
The fault was caused by a non-null segment selector.
Summary:
The fault likely occurred due to an invalid or misconfigured GDT/LDT entry. Check the segment selector index (1113) in the corresponding descriptor table. recieved interrupt: 13
General protection fault (pushes an error code)
Error Code: 8904
CS: 0x8, RIP: 0xFFFFFFFF80005684
Stack Contents:
[0xFFFF800003F51FB0] = 0x0
=
[0xFFFF800003F51FB8]
0xFFFFFFFF800022C8
[0xFFFF800003F51FC0] = 0x8
[0xFFFF800003F51FC8] = 0x10246
[0xFFFF800003F51FD0] = 0xFFFF800003F51FE0
[0xFFFF800003F51FD8] = 0x10
[0xFFFF800003F51FE0] = 0xFFFF800003F51FF0
[0xFFFF800003F51FE8] = 0xFFFFFFFF8000ZZBO
[0xFFFF800003F51FF0] = 0x0
[0xFFFF800003F51FF8] = 0x0
[0xFFFF800003F520001 = 0x800000015CD00037
[0xFFFF800003F52008] = 0xFFFF
[0xFFFF800003F52010) =
0x0
[0xFFFF800003F52018) =
[0xFFFF800003F520201 =
0x0 0x0
[0xFFFF800003F52028] = 0x0
[0xFFFF800003F52030] = 0x0
[0xFFFF800003F52038] = 0x0
[0xFFFF800003F52040] = 0x0
System Halted!
registers_t structure defined by

Code: Select all

typedef struct registers
{
    uint64_t rax, rbx, rcx, rdx, rbp, rsi, rdi;    // General-purpose registers
    uint64_t r8, r9, r10, r11, r12, r13, r14, r15; // General-purpose registers
    uint64_t gs, fs, es, ds;                       // Segment registers

    uint64_t int_no, err_code;                     // Interrupt number and error code (if applicable)
    uint64_t iret_rip, iret_cs, iret_rflags, iret_rsp, iret_ss; // CPU state
} registers_t;
What is the reason of this error? Edit : It take little time to show the error. The full code repository in https://github.com/baponkar/KeblaOS.

stackoverflow version of this qustion : https://stackoverflow.com/questions/793 ... 3-in-my-x8

I have tried to change SAVE_REGISTERS, and RESTORE_REGISTERS by 16 byte stack alignment and checked gdt.c, gdt.h, gdt_load.asm, idt.c, idt.h and idt_load.asm files but unable to detect the cause of this problem. I know virtualbox is more near of actual hardware so this problem will be available in real machine so I am desparate to resolve this issue.

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 6:52 am
by sebihepp
When you align your stack with and rsp, ~(0xF) then you can't just add 16 to rsp to reverse it.
Maybe rsp was 0xFFF3 (just some example value). After alignment it is 0xFFF0, but when you add 16 (0x10) again, it will be 0x10000, instead of 0xFFF3 again.

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 7:54 am
by gamingjam60
But alignment is made after storing registers state.

Code: Select all

SAVE_REGISTERS
 and rsp, ~(0xF)
 Now I am changing the value of rsp
 call irq_handler
 RESTORE_REGISTERS
 add rsp, 16 ; as irq_handler is pushes error_code and interrupt_no
 
Here I have changed rsp but restore it by calling RESTORE_REGISTERS function.

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 8:10 am
by gamingjam60
You are right so I am using

Code: Select all

...
 rsp, ~(0xF)
push rsp
....
pop rsp
RESTORE_REGISTERS
...
Now it is showing Zero division error at RIP : 0xD

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 8:33 am
by sebihepp
I must admit that I am new to stack alignment, but look at my comments:
gamingjam60 wrote: Mon Jan 06, 2025 8:10 am You are right so I am using

Code: Select all

...
and rsp, ~(0xF) # RSP is aligned after this, lets say 0x1FF0
push rsp # Here you push rsp (0x1FF0) to the stack at address 0x1FE8, rsp is now 0x1FE8
....
pop rsp # Here you restore rsp, rsp is now 0x1FF0 - still not the old address
RESTORE_REGISTERS
...
Now it is showing Zero division error at RIP : 0xD
I think something like this is needed, but I might overcomplicate things

Code: Select all

SAVE_REGISTERS
mov rax, rsp # Save RSP before aligning
and rsp, ~(0x7) # Align, but to an 8 byte boundary, because we push rax afterwards, which will in turn make the stack 16byte aligned
push rax # Push RSP before aligning to the actual aligned address

# Do stuff

pop rsp # get the old rsp address before aligning back to rsp
RESTORE_REGISTERS
AFAIK, in long mode the processor aligns the stack automatically when an interrupt appears. If you make sure you only push in 16byte steps, you don't need to align at all.

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 10:33 am
by nullplan
In 64-bit mode, the CPU aligns the stack pointer down to a 16-byte boundary unconditionally when an interrupt is invoked. Says so in the manuals (e.g. AMD APM v2, chapter 8.9). So to achieve the correct alignment for a C function, you only need to ensure that you push an even number of words. The interrupt frame with error code is exactly 6 words. If you push the other 15 GPRs, that is an odd number of words, so just push another word at the end and the RSP will be 16-bytes aligned.

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 11:20 am
by gamingjam60
Thankyou for your correct solution

The CPU state (iret_rip, iret_cs, iret_rflags, iret_rsp, iret_ss) is pushed first. These values are already on the stack when the interrupt occurs, so we copy them to the correct positions in the registers_t structure.
So I have changed the following :

Code: Select all

; Save all segment and general purpose registers
%macro SAVE_REGISTERS 0
    ; Save CPU state (iret_* fields)
    push qword [rsp + 5*8]  ; iret_ss
    push qword [rsp + 5*8]  ; iret_rsp
    push qword [rsp + 5*8]  ; iret_rflags
    push qword [rsp + 5*8]  ; iret_cs
    push qword [rsp + 5*8]  ; iret_rip

    ; Save interrupt number and error code
    push qword [rsp + 2*8]  ; err_code
    push qword [rsp + 2*8]  ; int_no

    ; Save segment registers
    mov ax, ds
    push rax                ; ds
    mov ax, es
    push rax                ; es
    push fs                 ; fs
    push gs                 ; gs

    ; Save general-purpose registers
    push r15
    push r14
    push r13
    push r12
    push r11
    push r10
    push r9
    push r8
    push rdi
    push rsi
    push rbp
    push rdx
    push rcx
    push rbx
    push rax
%endmacro


; Restore all segment and general purpose registers
%macro RESTORE_REGISTERS 0
    ; Restore general-purpose registers
    pop rax
    pop rbx
    pop rcx
    pop rdx
    pop rbp
    pop rsi
    pop rdi
    pop r8
    pop r9
    pop r10
    pop r11
    pop r12
    pop r13
    pop r14
    pop r15

    ; Restore segment registers
    pop gs
    pop fs
    pop rax
    mov es, ax
    pop rax
    mov ds, ax

    ; Restore interrupt number and error code
    add rsp, 16             ; Skip int_no and err_code (they are not restored)

    ; Restore CPU state (iret_* fields)
    add rsp, 40             ; Skip iret_rip, iret_cs, iret_rflags, iret_rsp, iret_ss
%endmacro
removeed 16 byte stack alignment

Code: Select all

 and rsp, ~(0xF) 
from isr_common_stab and irq_common_stab

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 11:31 am
by Octocontrabass
gamingjam60 wrote: Mon Jan 06, 2025 11:20 amThese values are already on the stack when the interrupt occurs, so we copy them to the correct positions in the registers_t structure.
Huh? That doesn't make any sense. They should already be in the correct positions. If your registers_t struct doesn't match, you need to change the struct.

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 11:43 am
by gamingjam60
nullplan wrote: Mon Jan 06, 2025 10:33 am In 64-bit mode, the CPU aligns the stack pointer down to a 16-byte boundary unconditionally when an interrupt is invoked. Says so in the manuals (e.g. AMD APM v2, chapter 8.9). So to achieve the correct alignment for a C function, you only need to ensure that you push an even number of words. The interrupt frame with error code is exactly 6 words. If you push the other 15 GPRs, that is an odd number of words, so just push another word at the end and the RSP will be 16-bytes aligned.
Now Keyboard is not working. :(

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 12:37 pm
by Octocontrabass
gamingjam60 wrote: Mon Jan 06, 2025 11:43 amNow Keyboard is not working. :(
Did pushing another word change your stack layout so it doesn't match the registers_t struct anymore?

Re: What is the reason of general protection fault due to GDT selector 1113 in my x86_64 OS?

Posted: Mon Jan 06, 2025 1:32 pm
by nullplan
gamingjam60 wrote: Mon Jan 06, 2025 11:43 am Now Keyboard is not working. :(
Sorry, I didn't look at your code too closely, but you were talking about stack alignment before, so that's just what came to mind. Now that I do come to look at your code I notice the tell-tale signs of someone copying a tutorial off the internet, and that tutorial is wrong. The type registers_t seems familiar, as is the idea of passing it by value to the ISR handler. That is wrong.

It is wrong for two reasons. For one, arguments are volatile memory of the called function, so the compiler can generate code to overwrite the registers. For two, you don't know the ABI of passing such a large structure to a function, do you?

You should change the argument of isr_handler() to a pointer to registers_t, then set RDI to the correct pointer before calling that function. Which in your case would be [rsp+8].