The Interrupt Descriptor Table: How the CPU Finds Your Handler in 256 Slots

2026-05-31

When a divide-by-zero, page fault, or hardware interrupt fires, the CPU needs to jump to your handler within nanoseconds. It doesn't search a list or call a function — it indexes a single fixed table: the Interrupt Descriptor Table (IDT). The IDTR register holds the table's base address and limit; vector number times 16 gives you the entry.

On x86-64, the IDT has 256 entries, each 16 bytes. Vectors 0–31 are CPU exceptions (0=#DE divide error, 6=#UD invalid opcode, 13=#GP, 14=#PF page fault, 18=#MC machine check). Vectors 32–255 are software-assignable: legacy IRQs land at 32–47, the Linux system call vector historically sat at 0x80, and IPIs/APIC interrupts occupy the high end (0xFB local timer, 0xFF spurious).

Each entry is a gate descriptor packing: a 64-bit handler offset (split across three fields for historical reasons), a 16-bit code segment selector, a 3-bit IST index, a type field (0xE=interrupt gate, 0xF=trap gate — the difference is whether IF gets cleared on entry), a DPL (descriptor privilege level — set to 3 for INT 0x80 so userspace can invoke it), and a present bit.

The IST (Interrupt Stack Table) field is critical for reliability. Normally, an interrupt switches to the kernel stack from the TSS's RSP0. But what if the kernel stack itself is corrupt, or you're handling a double fault caused by a stack overflow? IST 1–7 in the TSS provide known-good stacks. Linux dedicates IST stacks to #DF (double fault), #NMI, #MC, and #DB precisely so these survive any conceivable stack disaster.

Real-world example: When Linux boots, idt_setup_traps() in arch/x86/kernel/idt.c writes all 32 exception handlers. The page fault handler at vector 14 points to asm_exc_page_fault. When your process touches an unmapped address, the MMU raises #PF, the CPU reads IDTR, fetches entry 14 (offset 14×16 = 224 bytes into the table), pushes the error code and SS:RSP:RFLAGS:CS:RIP, optionally switches stacks via IST, and jumps — all in microcode, no software involvement until your handler runs.

Rule of thumb: IDT entry address = IDTR.base + (vector × 16). Want to inspect it live? In a kernel debugger: sidt dumps the base/limit, then read 16 bytes per vector. The handler offset is reassembled from bytes [0:2] | [6:8] | [8:12] — a layout preserved from 32-bit gates for backward compatibility.

Key Takeaway: The IDT is a 4 KB array the CPU indexes by vector number to find your handler, with IST stacks ensuring fault handlers survive even when the kernel stack is hosed.

All newsletters