📚 AOS Docs

Global Descriptor Table (GDT)

Memory Segmentation in x86_64

Overview

The Global Descriptor Table (GDT) is a fundamental data structure that defines memory segments and their protection levels in x86 and x86_64 architectures. In AOS, the GDT establishes the foundation for protected mode operation and 64-bit long mode execution.

What is the GDT?

The GDT is a table in memory that contains segment descriptors. Each descriptor defines:

In 64-bit mode, the GDT plays a reduced role compared to 32-bit systems, but it's still mandatory.

Purpose in AOS

Purpose Impact
Establish memory protection Prevents user programs from accessing kernel memory
Define privilege levels Separates Ring 0 (kernel) from Ring 3 (user mode)
Mandatory for processor CPU requires GDT to function in protected mode
Context switching Loaded when switching between kernel and user code

GDT Structure in AOS

Descriptor Table Definition

// From include/gdt.h
struct gdt_entry {
    uint16_t limit_low;      // Lower 16 bits of limit
    uint16_t base_low;       // Lower 16 bits of base address
    uint8_t  base_middle;    // Middle 8 bits of base address
    uint8_t  access;         // Access byte (privilege, type)
    uint8_t  granularity;    // Granularity and upper limit
    uint8_t  base_high;      // Upper 8 bits of base address
} __attribute__((packed));

struct gdt_ptr {
    uint16_t limit;          // Limit of the GDT itself
    uint64_t base;           // Pointer to GDT in memory
} __attribute__((packed));

Access Byte Breakdown

Access Byte Format

  • Bit 7: Present (1 = segment is valid)
  • Bit 6: Privilege Level (0 = Ring 0, 3 = Ring 3)
  • Bit 5: User-defined
  • Bits 4-0: Type (Code, Data, etc.)

AOS GDT Configuration

AOS uses a minimal 3-entry GDT:

Index Type Purpose Access Flags
0 Null Required (must be 0) 0x00 0x00
1 Code Kernel code segment 0x9A 0x20 (64-bit)
2 Data Kernel data segment 0x92 0x00

Access Values Explained

Code segment (0x9A): 10011010
  • Bit 7: Present (1)
  • Bit 6: Ring 0 (0)
  • Bit 4: Executable (1)
Data segment (0x92): 10010010
  • Bit 7: Present (1)
  • Bit 6: Ring 0 (0)
  • Bit 1: Read/Write (1)

Implementation Walkthrough

Step 1: Define GDT and Pointer

// From src/arch/x86_64/gdt.c
struct gdt_entry gdt[3];        // Our 3-entry GDT
struct gdt_ptr gp;              // Pointer for LGDT instruction

Step 2: GDT Gate Setting Function

void gdt_set_gate(int num, uint32_t base, uint32_t limit, 
                  uint8_t access, uint8_t gran) {
    // Set base address
    gdt[num].base_low    = (base & 0xFFFF);           // Bits 0-15
    gdt[num].base_middle = (base >> 16) & 0xFF;       // Bits 16-23
    gdt[num].base_high   = (base >> 24) & 0xFF;       // Bits 24-31
    
    // Set limit
    gdt[num].limit_low   = (limit & 0xFFFF);          // Bits 0-15
    gdt[num].granularity = (limit >> 16) & 0x0F;      // Bits 16-19
    
    // Set granularity flags
    gdt[num].granularity |= gran & 0xF0;
    
    // Set access byte
    gdt[num].access = access;
}

⚠️ Important: Initialization Order

The GDT must be initialized FIRST before any other CPU structures (IDT, TSS, etc.). The CPU requires a valid GDT to function in protected mode.

Step 3: Initialize GDT

void init_gdt() {
    // Set GDT pointer (limit and base address)
    gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
    gp.base  = (uint64_t)&gdt;
    
    // Entry 0: Null descriptor (required)
    gdt_set_gate(0, 0, 0, 0, 0);
    
    // Entry 1: Kernel code segment (64-bit)
    gdt_set_gate(1, 0, 0, 
                 GDT_ACCESS_PRESENT | GDT_ACCESS_RING0 | GDT_ACCESS_CODE,
                 GDT_FLAG_64BIT);
    
    // Entry 2: Kernel data segment
    gdt_set_gate(2, 0, 0,
                 GDT_ACCESS_PRESENT | GDT_ACCESS_RING0 | GDT_ACCESS_DATA,
                 0);
    
    // Load GDT into CPU via assembly instruction
    gdt_flush((uint64_t)&gp);
}

Segment Selectors

When you load a segment register (CS, DS, ES, etc.), you provide a segment selector:

Bits Name Purpose
15-3 Index Which GDT entry (0-8191)
2 TI 0 = GDT, 1 = LDT
1-0 RPL Privilege Level (0-3)

Examples:

  • 0x08 = Index 1, GDT, Ring 0 (kernel code)
  • 0x10 = Index 2, GDT, Ring 0 (kernel data)
  • 0x1B = Index 3, GDT, Ring 3 (user code)

Memory Addresses in 64-bit Mode

In 64-bit long mode, base addresses and limits are effectively ignored:

The GDT's primary function becomes privilege level control rather than memory segmentation.

Key Takeaways

  • ✓ GDT is mandatory for processor operation
  • ✓ AOS uses minimal 3-entry table
  • ✓ Segment descriptors define memory protection
  • ✓ 64-bit mode simplifies segmentation
  • ✓ LGDT instruction loads the GDT into CPU
  • ✓ Far JMP/RET instructions update CS after GDT load
  • ✓ Future Ring 3 support will require additional entries