-
Notifications
You must be signed in to change notification settings - Fork 0
gdt descriptor format
The exhaustive, bit-by-bit reference for the 8-byte segment descriptor — every field in MyOS-Simple's three GDT entries decoded.
This is the field-level companion to the Global Descriptor Table
concept page. Where that page explains why the table exists, this page is the
authoritative decode of every bit in the descriptors installed by
boot.asm (gdt_start at boot.asm:107).
A segment descriptor is 8 bytes. The base and limit fields are split across non-contiguous byte ranges — a historical artifact of the 286 → 386 evolution.
| Byte | Bits held | Field |
|---|---|---|
| 0 | limit[15:0] low byte | Segment limit, bits 0–7 |
| 1 | limit[15:0] high byte | Segment limit, bits 8–15 |
| 2 | base[15:0] low byte | Base address, bits 0–7 |
| 3 | base[15:0] high byte | Base address, bits 8–15 |
| 4 | base[23:16] | Base address, bits 16–23 |
| 5 | access byte | Present, DPL, type — see below |
| 6 | flags (high nibble) ⎢ limit[19:16] (low nibble) | Granularity flags + top of limit |
| 7 | base[31:24] | Base address, bits 24–31 |
So the limit is 20 bits (16 + 4) and the base is 32 bits (16 + 8 + 8).
From boot.asm:107-127:
| Entry | Selector | limit | base | access | flags+limit-hi | Raw bytes (LE) |
|---|---|---|---|---|---|---|
| NULL | 0x00 |
— | — | — | — | 00 00 00 00 00 00 00 00 |
| CODE | 0x08 |
0xFFFF |
0x000000 |
0x9A (10011010b) |
0xCF (11001111b) |
FF FF 00 00 00 9A CF 00 |
| DATA | 0x10 |
0xFFFF |
0x000000 |
0x92 (10010010b) |
0xCF (11001111b) |
FF FF 00 00 00 92 CF 00 |
gdt_start:
dd 0x0
dd 0x0 ; NULL descriptor
gdt_code:
dw 0xffff ; limit[15:0]
dw 0x0 ; base[15:0]
db 0x0 ; base[23:16]
db 10011010b ; access = 0x9A
db 11001111b ; flags|limit[19:16] = 0xCF
db 0x0 ; base[31:24]
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b ; access = 0x92
db 11001111b
db 0x0Bits, high to low:
| Bit | Name | Meaning |
|---|---|---|
| 7 | P | Present — segment is valid |
| 6–5 | DPL | Descriptor Privilege Level (ring 0–3) |
| 4 | S | Descriptor type: 1 = code/data, 0 = system |
| 3 | E | Executable: 1 = code segment, 0 = data segment |
| 2 | DC | Code: conforming. Data: direction (0 = grows up) |
| 1 | RW | Code: readable. Data: writable |
| 0 | A | Accessed (set by CPU on use) |
| Bit(s) | Value | Interpretation |
|---|---|---|
| P | 1 | Present |
| DPL | 00 | Ring 0 |
| S | 1 | Code/data segment |
| E | 1 | Executable → code |
| DC | 0 | Non-conforming |
| RW | 1 | Readable |
| A | 0 | Not yet accessed |
| Bit(s) | Value | Interpretation |
|---|---|---|
| P | 1 | Present |
| DPL | 00 | Ring 0 |
| S | 1 | Code/data segment |
| E | 0 | Not executable → data |
| DC | 0 | Direction up (expand-up) |
| RW | 1 | Writable |
| A | 0 | Not yet accessed |
| Bit | Name | Meaning |
|---|---|---|
| 7 | G | Granularity: 1 = limit counts 4 KiB pages, 0 = bytes |
| 6 | D/B | Default operand size: 1 = 32-bit |
| 5 | L | 64-bit code segment: 1 = long mode (must be 0 here) |
| 4 | AVL | Available for software use |
| Bit | Value | Interpretation |
|---|---|---|
| G | 1 | 4 KiB granularity |
| D/B | 1 | 32-bit segment |
| L | 0 | Not 64-bit |
| AVL | 0 | Unused |
The low nibble of byte 6 is limit[19:16] = 0xF.
The two limit pieces combine to 0xFFFFF (20 bits). With granularity G = 1, the
limit is measured in 4 KiB units:
span = (0xFFFFF + 1) x 4096
= 0x100000 x 0x1000
= 0x100000000
= 4 GiB
So both code and data segments cover the entire 32-bit address space starting at base
0x00000000 — a flat memory model. Note the +1:
the limit field is size minus one.
lgdt loads a 6-byte pseudo-descriptor (boot.asm:129-131):
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; 2-byte LIMIT = table size minus one
dd gdt_start ; 4-byte BASE = linear address of the table| Field | Size | Value |
|---|---|---|
| Limit | 2 bytes | total table size − 1 (here 24 − 1 = 23) |
| Base | 4 bytes | linear address of gdt_start
|
A selector is a byte offset into the GDT, not an index. Its low 3 bits hold the Requested Privilege Level (bits 0–1) and the Table Indicator (bit 2). MyOS-Simple uses RPL 0 and the GDT (TI = 0), so the selectors equal the raw offsets:
| Name | Value | Points to | RPL / TI |
|---|---|---|---|
CODE_SEG |
0x08 |
CODE descriptor (entry 1) | 0 / GDT |
DATA_SEG |
0x10 |
DATA descriptor (entry 2) | 0 / GDT |
CODE_SEG equ gdt_code - gdt_start and DATA_SEG equ gdt_data - gdt_start
(boot.asm:133-134). The far jump jmp CODE_SEG:init_pm (boot.asm:45) loads CS
with 0x08; the data selectors are loaded into DS/ES/FS/GS/SS afterward.
💡 Tidbit: The descriptor layout looks bizarre because it grew. The 80286 had a 24-bit base and 16-bit limit packed into the first 6 bytes; the 80386 needed 32 bits of base and 20 of limit, so the extra bits were bolted onto bytes 6 and 7 to stay backward-compatible with existing 286 descriptors.
⚠️ Caveat: Entry 0 must be the null descriptor. Loading a segment register with selector0x00is legal but using it for a memory access raises a #GP fault. The CPU reserves index 0 deliberately, which is why the first usable selector is0x08.
💡 Tidbit: The GDTR limit and every segment limit are stored as size minus one. A 4 GiB segment cannot store "4 GiB" in a field whose maximum is
0xFFFFFFFF— but "4 GiB − 1" fits exactly, and the hardware adds the 1 back.
- Global Descriptor Table — the concept and the table walkthrough
- Protected mode — where the GDT is used
-
Stage 2: C in protected mode —
boot.asmin context - Memory map — the flat 4 GiB space these descriptors span
- Glossary — selector, far jump, CR0/PE, A20
- Home
Stages
- 1 · Assembly boot
- 2 · C protected mode
- 3 · Interactive shell
- 4 · Clock / processes / calc
- 5 · Stabilized release
Concepts — boot
Concepts — protected mode
Concepts — hardware
Concepts — OS services
Reference
- Memory map
- I/O ports
- GDT descriptor format
- Scancode tables
- Command reference
- Toolchain & build
- Glossary
Guides