Skip to content

gdt descriptor format

Mohiuddin Khan Inamdar edited this page Jun 21, 2026 · 3 revisions

← Home

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).

The 8-byte descriptor

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).

MyOS-Simple's three entries

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 0x0

The access byte (byte 5)

Bits, 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)

Decode CODE access 0x9A = 1 00 1 1 0 1 0

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

Decode DATA access 0x92 = 1 00 1 0 0 1 0

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

The flags nibble (high nibble of byte 6)

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

Decode flags 0xC = 1100

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.

How the limit becomes 4 GiB

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.

The GDTR operand (gdt_descriptor)

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

Selectors

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 selector 0x00 is 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 is 0x08.

💡 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.

See also

Clone this wiki locally