Global Descriptor Table initialization in x86_64 OS

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
User avatar
gamingjam60
Posts: 4
Joined: Sat Aug 24, 2024 10:06 pm
Libera.chat IRC: gamingjam60
GitHub: https://github.com/baponkar

Global Descriptor Table initialization in x86_64 OS

Post by gamingjam60 »

I want to enable GDT in x86_64 OS. I have written following codes to enable the GDT.

gdt.h

Code: Select all

struct gdt_entry
{// 128 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
    uint32_t base_upper;      // Upper 32 bits of the base address
    uint32_t reserved;        // Reserved (must be zero)
} __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;
gdt.c

Code: Select all

extern void gdt_flush(gdtr_t *gdtr_instance);
extern void reloadSegments();

gdt_entry_t gdt_entries[5];
gdtr_t gdtr_instance;

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
    gdt_entries[idx].base_upper   = (base >> 32) & 0xFFFFFFFF; // 32 bit
    gdt_entries[idx].reserved     = 0;                         // 32 bit
}


void init_gdt(){
    // index = 0, base = 0, limit = 0, access = 0, granularity = 0
    gdt_setup(0, 0, 0x0, 0x0, 0x0);             // null descriptor selector : 0x0
    // index = 1, base = 0, limit = 0xFFFFFFFF, access = 0x9A = 154, granularity = 0xA0 = 160
    gdt_setup(1, 0, 0xFFFFF, 0x9A, 0xA0);    // kernel mode code segment, selector : 0x8

    // index = 2, base = 0, limit = 0xFFFFFFFF, access = 0x92 = 146, granularity = 0xA0 = 160
    gdt_setup(2, 0, 0xFFFFF, 0x92, 0xA0);    // kernel mode data segment, selector : 0x10

    // index = 3, base = 0, limit = 0xFFFFFFFF, access = 0xFA = 250, granularity = 0xA0 = 160
    gdt_setup(3, 0, 0xFFFFF, 0xFA, 0xA0);    // user mode code segment, selector : 0x18 

    // index = 4, base = 0, limit = 0xFFFFFFFF, access = 0xF2 = 242, granularity = 0xA0 = 160
    gdt_setup(4, 0, 0xFFFFF, 0xF2, 0xA0);    // user mode data segment, selector : 0x20

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

    gdt_flush((gdtr_t *) &gdtr_instance);
    reloadSegments();
}
I want to enable GDT in x86_64 OS. I have written following codes to enable the GDT.

Code: Select all

struct gdt_entry
{// 128 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
    uint32_t base_upper;      // Upper 32 bits of the base address
    uint32_t reserved;        // Reserved (must be zero)
} __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;
extern void gdt_flush(gdtr_t *gdtr_instance);
extern void reloadSegments();

gdt_entry_t gdt_entries[5];
gdtr_t gdtr_instance;

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
    gdt_entries[idx].base_upper   = (base >> 32) & 0xFFFFFFFF; // 32 bit
    gdt_entries[idx].reserved     = 0;                         // 32 bit
}


void init_gdt(){
    // index = 0, base = 0, limit = 0, access = 0, granularity = 0
    gdt_setup(0, 0, 0x0, 0x0, 0x0);             // null descriptor selector : 0x0
    // index = 1, base = 0, limit = 0xFFFFFFFF, access = 0x9A = 154, granularity = 0xA0 = 160
    gdt_setup(1, 0, 0xFFFFF, 0x9A, 0xA0);    // kernel mode code segment, selector : 0x8

    // index = 2, base = 0, limit = 0xFFFFFFFF, access = 0x92 = 146, granularity = 0xA0 = 160
    gdt_setup(2, 0, 0xFFFFF, 0x92, 0xA0);    // kernel mode data segment, selector : 0x10

    // index = 3, base = 0, limit = 0xFFFFFFFF, access = 0xFA = 250, granularity = 0xA0 = 160
    gdt_setup(3, 0, 0xFFFFF, 0xFA, 0xA0);    // user mode code segment, selector : 0x18 

    // index = 4, base = 0, limit = 0xFFFFFFFF, access = 0xF2 = 242, granularity = 0xA0 = 160
    gdt_setup(4, 0, 0xFFFFF, 0xF2, 0xA0);    // user mode data segment, selector : 0x20

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

    gdt_flush((gdtr_t *) &gdtr_instance);
    reloadSegments();
}
To load GDT into GDTR register
gdt.s

Code: Select all

section .text
global gdt_flush
gdt_flush:
   MOV RAX, [RDI] ;Getting gdtr_instance pointer from externel gdt.c 
   LGDT  [RAX]
   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
   LRETQ                     ; 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
The full code is in [here](https://github.com/baponkar/KeblaOS/tre ... src/x86_64).

reloadSegments function is causing the crash the system. I also checked GDB I have seen There is no 4th entry although I have entered gdt_setup(4, 0, 0xFFFFF, 0xF2, 0xA0); inside of init_gdt() function and also I have seen unnecessary 255th entry also present although I have used gdtr_instance.limit = (uint16_t) (sizeof(gdt_entry_t) * 5 - 1);.

I have seen following GDB output :

Code: Select all

gdt_setup(idx=0 '\000', base=0, limit=0, access=0 '\000', granularity=0 '\000') at src/x86_64/gdt.c :19
gdt_setup(idx=255 '\377', base=18446603339441663968, limit=2147491364, access=255 '\377', granularity=119 'w') at src/x86_64/gdt.c :30
gdt_setup(idx=1 '\001', base=0, limit=4294967295, access=154 '\232', granularity=160 '\240') at src/x86_64/gdt.c :41
gdt_setup(idx=255 '\377', base=18446603339441663968, limit=2147491364, access=255 '\377', granularity=181 '\265') at src/x86_64/gdt.c :44
gdt_setup(idx=2 '\002', base=0, limit=4294967295, access=146 '\222', granularity=160 '\240') at src/x86_64/gdt.c :19
gdt_setup(idx=3 '\003', base=0, limit=4294967295, access=250 '\372', granularity=160 '\240') at src/x86_64/gdt.c :19
gdt_setup(idx=255 '\377', base=18446603339441663968, limit=2147491364, access=255 '\377', granularity=243 '\363') at src/x86_64/gdt.c :32
Qemu Log is showing Triple fault. The Qemu log :

Code: Select all

CPU Reset (CPU 0)
RAX=ffffffff80000010 RBX=0000000000000000 RCX=00000000000000c0 RDX=0000000000000000
RSI=0000000000000000 RDI=ffffffff80006990 RBP=ffff8000bff36fe0 RSP=ffff8000bff36fc8
R8 =00000000000000c0 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff80003445 RFL=00000082 [--S----] 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=     0000000000000000 00000000
IDT=     0000000000000000 00000000
CR0=80010011 CR2=0000000000000000 CR3=00000000bff26000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=ffffffff8000694c CCD=ffffffff8000698c CCO=ADDQ
EFER=0000000000000d00
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000
XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000
XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000
XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000
XMM08=0000000000000000 0000000000000000 XMM09=0000000000000000 0000000000000000
XMM10=0000000000000000 0000000000000000 XMM11=0000000000000000 0000000000000000
XMM12=0000000000000000 0000000000000000 XMM13=0000000000000000 0000000000000000
XMM14=0000000000000000 0000000000000000 XMM15=0000000000000000 0000000000000000
     7: v=03 e=0000 i=1 cpl=0 IP=0008:00000000000efb0a pc=00000000000efb0a SP=0010:0000000000000fc8 env->regs[R_EAX]=00000000000f6106
EAX=000f6106 EBX=000f3e0a ECX=00000000 EDX=00000cf9
ESI=00000000 EDI=00100000 EBP=00000000 ESP=00000fc8
EIP=000efb0a EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6180 00000037
IDT=     000f61be 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000f61c8 CCD=00009e34 CCO=SUBL
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     8: v=0d e=001a i=0 cpl=0 IP=0008:00000000000efb0a pc=00000000000efb0a SP=0010:0000000000000fc8 env->regs[R_EAX]=00000000000f6106
EAX=000f6106 EBX=000f3e0a ECX=00000000 EDX=00000cf9
ESI=00000000 EDI=00100000 EBP=00000000 ESP=00000fc8
EIP=000efb0a EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6180 00000037
IDT=     000f61be 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000f61c8 CCD=00009e34 CCO=SUBL
EFER=0000000000000000
check_exception old: 0xd new 0xd
     9: v=08 e=0000 i=0 cpl=0 IP=0008:00000000000efb0a pc=00000000000efb0a SP=0010:0000000000000fc8 env->regs[R_EAX]=00000000000f6106
EAX=000f6106 EBX=000f3e0a ECX=00000000 EDX=00000cf9
ESI=00000000 EDI=00100000 EBP=00000000 ESP=00000fc8
EIP=000efb0a EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     000f6180 00000037
IDT=     000f61be 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000f61c8 CCD=00009e34 CCO=SUBL
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Triple fault
I create gdt_entry structure from here.I don't understand what is causing the problem?

Do I not need the uint32_t base_upper; and uint32_t reserved; section in the gdt_entry_t structure what is causing the problem as it is remaining zero.

I need help to resolve the issue.

I also have written a [stackoverflow](https://stackoverflow.com/questions/792 ... -64-system) question.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Global Descriptor Table initialization in x86_64 OS

Post by MichaelPetch »

In long mode GDT entries are 64-bit (8 bytes) EXCEPT for System Segments (LDT and TSS segments) which are 128 bits (16 bytes).
rdos
Member
Member
Posts: 3297
Joined: Wed Oct 01, 2008 1:55 pm

Re: Global Descriptor Table initialization in x86_64 OS

Post by rdos »

There is no difference between protected mode and long mode GDT other than the possibility to map it above 4G. The contents are the same and so is the function. Long mode defines new selector types, some which are two-entry,l but those can be used regardless if the GDT is constructed in protected mode or long mode.
MichaelPetch
Member
Member
Posts: 797
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Global Descriptor Table initialization in x86_64 OS

Post by MichaelPetch »

I've made a pull request here: https://github.com/baponkar/KeblaOS/pull/2 . It fixes:
  • `gdt_flush` was doing a level of indirection it shouldn't have which resulted in a bogus address being used by `LGDT`.
  • GDT entries in long mode are 64-bit (8 byte) except for system segments (TSS and LDT entries) which are 128-bit (16 bytes).
  • Create a new function `gdt_setup_sysseg` to create a TSS or LDT entry that span 2 consecutive 8 byte GDT entries.
  • In NASM syntax `LRETQ` needs to be `RETFQ` in `reloadSegments`. This fixes an exception caused by the return not being generated. `LRETQ` is treated as a label and not an instruction by NASM. This was the cause of a warning during assembling similar to `src/x86_64/gdt.s:18: warning: label alone on a line without a colon might be in error [-w+label-orphan]`
  • Other minor cleanup.
Post Reply