x86_64 GDT
The Global Descriptor Table is a data structure in the x86_64 architecture for a few minor things but it is mostly legacy baggage
The GDT should be among, if not the first thing ever initialized as it is needed for many things to work like ring3 and paging to be enabled although when you are in long mode / 64bit mode you will already have a basic GDT setup by the firmware to enable this to get into long mode properly. You will still want to define your own to be certain what is put there
Creating the structure
First of all, we need a structure in C to represent the GDT to be loaded into the GDT register to tell the CPU where it is.
Here's a table to illustrate the entries we will need for a bare bones setup but more will be needed later:
| Segment | Mode | Access | Granularity | Description |
|---|---|---|---|---|
| Null | 32/64-bit | 0x00 | 0x00 | Required null descriptor |
| Kernel Code | 64-bit | 0x9A | 0x20 | Ring 0 code segment |
| Kernel Data | 64-bit | 0x92 | 0x00 | Ring 0 data segment |
These are the actual structures you will use in C
struct __attribute__((packed)) GDTEntry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t granularity;
uint8_t base_high;
};
struct __attribute__((packed)) GDTR {
uint16_t limit;
uint64_t base;
};
// allow for more entries in the future as you will need it
// for now three will be used
static struct GDTEntry gdt[7];
static struct GDTR gdtr;
Filling the entries and loading the GDT
The following code will let you set up a very basic GDT for your kernel to use and will load it into the CPU.
This will now set up the proper entries for ring3 to work or for the TSS to work as the entries needed for them will be covered later.
The large bit of inline assembly is needed to properly get the CPU to load the GDT properly by triggering an update of the CS register with a far return
void gdt_fill_entry (int num, uint8_t access, uint8_t granularity, uint32_t base, uint32_t limit) {
gdt[num].limit_low = limit & 0xFFFF;
gdt[num].base_low = base & 0xFFFF;
gdt[num].base_mid = (base >> 16) & 0xFF;
gdt[num].access = access;
gdt[num].granularity = ((limit >> 16) & 0x0F) | (granularity & 0xF0);
gdt[num].base_high = (base >> 24) & 0xFF;
}
void setup_gdt() {
gdt_fill_entry (0, 0, 0, 0, 0);
gdt_fill_entry (1, 0x9A, 0x20, 0, 0);
gdt_fill_entry (2, 0x92, 0x00, 0, 0);
gdtr.limit = sizeof(gdt) - 1;
gdtr.base = (uint64_t)&gdt;
asm volatile ("lgdt %0" : : "m"(gdtr));
asm volatile (
"mov $0x10, %%ax \n"
"mov %%ax, %%ds \n"
"mov %%ax, %%ss \n"
"mov %%ax, %%es \n"
"mov %%ax, %%fs \n"
"mov %%ax, %%gs \n"
"pushq $0x08 \n"
"lea 1f(%%rip), %%rax \n"
"pushq %%rax \n"
"lretq \n"
"1:\n"
: : : "rax"
);
asm volatile ("ltr %%ax" ::"a"(0x18));
}