The Global Descriptor Table: How Segmentation Refuses to Die in Long Mode

2026-05-30

You've been told x86-64 "got rid of segmentation." That's a marketing simplification. The Global Descriptor Table (GDT) is still mandatory, still consulted on every privilege transition, and still the reason your syscall instruction works at all. Understanding it explains a surprising amount of why the kernel/user boundary looks the way it does.

The GDT is an array of 8-byte descriptors in memory. The CPU finds it via the GDTR register, loaded with lgdt. Segment registers (cs, ds, ss, fs, gs) hold 16-bit selectors: an index into the GDT plus a 2-bit Requested Privilege Level (RPL). When you load cs, the CPU walks the GDT, reads the descriptor, and caches its attributes in a hidden register.

In 64-bit mode, the base/limit fields in code/data descriptors are ignored — addresses are flat. But the descriptor still encodes:

A minimal Linux GDT has roughly: null descriptor, kernel code (ring 0), kernel data, user code (ring 3), user data, plus a TSS descriptor (16 bytes in long mode). The TSS in 64-bit mode no longer holds task state — it holds the rsp0 field, which is the kernel stack pointer the CPU loads on a ring 3→0 transition. No TSS, no usable kernel.

Concrete example: when syscall executes, the CPU loads cs from IA32_STAR[47:32] and ss from STAR+8 — no GDT walk. But the values must match GDT entries laid out in a specific order (kernel code, kernel data, then user code at STAR+16, user data at STAR+24). Linux pins them at GDT indices 2 and 5 for exactly this reason. Get the layout wrong and sysret faults.

Rule of thumb: every privilege transition (int, syscall, exception, IRQ) reloads cs and ss from somewhere — IDT, STAR MSR, or TSS — and every one of those "somewheres" ultimately points back into the GDT layout. A 5-entry table you set up once at boot dictates the structure of every ring crossing for the life of the machine.

Also: fs and gs bases are used in 64-bit mode (TLS, per-CPU data) — but they're loaded from MSRs (FS_BASE, GS_BASE), not the GDT. Segmentation died for everyone except the people who needed it.

See it in action: Check out 8.4 Group descriptor table by cjumpdotcom to see this theory applied.
Key Takeaway: The GDT survives in long mode as a tiny, mandatory table that defines privilege levels and anchors the kernel stack switch — flat addressing didn't kill segmentation, it just hid it.

All newsletters