HALICERY

free-time coding, hardware dev, articles

Top
Home 8042 Blogs About
Home IntelEssential The 386: IA-32 Protected Mode Descriptor structures

Last modified: Mon Jun 29 08:12:32 UTC+0200 2026 © A. Tarpai


Descriptor structures

These structures sit in main memory, set up by system software, and the cpu will read them, when p-mode operation is turned on (PE-bit=1).

Descriptor memory layout

The funny layout comes from 286, which used 3-words size descriptors (the last word is zero). The 286 is a 16-bit cpu with 24-bit address lines: all EA calculation and therefore every LIMIT is 16-bit (max 64K) and every BASE is 24-bit (3 bytes, address-space extension to 16MB). Eg. for segment descriptors:

286 16-bit Descriptor                                   386 32-bit Descriptor

15                                              0       15                                              0
+-----------------------------------------------+       +-----------------------------------------------+
|                  LIMIT 15..0                  | 0     |                  LIMIT 15..0                  | 0
+-----------------------------------------------+       +-----------------------------------------------+
|                   BASE 15..0                  | 2     |                   BASE 15..0                  | 2
+-----------------------+-----------------------+       +-----------------------+-----------------------+
|  ACCESS RIGHT BYTE    |      BASE 23..16      | 4     |  ACCESS RIGHT BYTE    |      BASE 23..16      | 4
+-----------------------+-----------------------+       +-----------------------+-----------+-----------+
|                  RESERVED = 0                 | 6     |         BASE HI       | ATTR BITS | LIMIT HI  | 6
+-----------------------------------------------+       +-----------------------+-----------+-----------+

The 286 structure, when layed out in memory in little-endian order, can be directly loaded into 286 CPU 48-bit cache registers. The last word was simply ignored.

                   286 DESCRIPTOR CACHE REGISTER                           6 MEMORY BYTES

                  47                            0
                  +----+----+----+----+----+----+    Load..  +----+----+----+----+----+----+----+----+
                  | AR |     BASE     |  LIMIT  |  <-------  |  LIMIT  |     BASE     | AR |         |
                  +----+----+----+----+----+----+            +----+----+----+----+----+----+----+----+
                                                                0    1    2    3    4    5    6    7

The 386 is a 32-bit cpu with 32-bit address lines. Every EA OFFSET and every BASE values are 32-bit (4 bytes). 4-bits of Extended attributes and high address/limit information is placed in the last word. By having this word zero, descriptors are compatible with the 286 and system code can be still executed on the 386.

           386 DESCRIPTOR CACHE REGISTER                                     8 MEMORY BYTES


+-------+----+----+----+----+----+----+----+----+    Load..  +----+----+----+----+----+----+----+----+
|ATTR/AR|    BASE 31..0     |     LIMIT 31..0   |  <-------  |  LIMIT  |     BASE     | AR | ATTR/HI |
+-------+----+----+----+----+----+----+----+----+            +----+----+----+----+----+----+----+----+
                                                                0    1    2    3    4    5    6    7

Loading this structure is a little more complicated – see LIMIT GRANULARITY below.

S=1 Segment Descriptors

Only S=1 descriptors can be loaded into SR DESCRIPTOR CACHE REGISTERS.

80286 Code/Data descriptor

286 Code/Data descriptor

15                                                              0
+---------------------------------------------------------------+
|                           LIMIT 15..0                         | 0
+---------------------------------------------------------------+
|                           BASE 15..0                          | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=1| X |C/E|R/W| A |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+-------------------------------+
|                          RESERVED = 0                         | 6
+---------------------------------------------------------------+


Segment Descriptor ACCESS RIGHTS BYTE:

P   - SEGMENT PRESENT i.e. addressable in real memory
DPL - DESCRIPTOR PRIVILEGE LEVEL
S=1 - SEGMENT bit: Data/Code segment. Must be 1 for segments.
X   - EXECUTABLE
A   - ACCESSED. CPU sets this bit in memory when it loads a segment selector

When X=1 the segment is Executable (the only type allowed for CS):

EXECUTABLE SEGMENT DESCRIPTOR (X=1)

|                                                               |
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=1|X=1| C | R | A |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+-------------------------------+
|                          RESERVED = 0                         | 6
+---------------------------------------------------------------+

C - CONFORMING

    The code segment may be accessed from any lower privilege level
    code without privilege level change - and stack-switch

R - READABLE (dont't care for CS, has meaning through data segregs)

It is not possible to write to a segment described as a code segment

When X=0 the segment is a data- or stack segment:

DATA SEGMENT DESCRIPTOR (X=0)

|                                                               |
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=1|X=0| E | W | A |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+-------------------------------+
|                          RESERVED = 0                         | 6
+---------------------------------------------------------------+

W - WRITABLE

    For SS, only writable segment can be loaded.

E - EXPAND-DOWN

          __
    FFFF    |
    ....    |
    ....    |  E=1 EXPAND-DOWN
    ....    |  VALID OFFSET > LIMIT
    ....  __|
    LIMIT   |
    ....    |
    ....    |
    ....    |  E=0
    ....    |  VALID OFFSET <= LIMIT
    ....    |
    ....    |
    0000  __|

    An expand-down segment has maximum size when the limit is zero.
    BUT! Accessing Byte 0 will cause exception: must be greater, than limit.
    Accessing Byte 1 will not - tested on real hardware.

Present-bit is the virtual memory management support bit and for disk swapping: If this bit is zero, the descriptor is not valid for use in address transformation; the processor will signal an exception when a selector for the descriptor is loaded into a segment register.

80386 Code/Data descriptor

Same ACCESS RIGHTS BYTE meaning. New attribute-bits:

386 Code/Data descriptor (Segment-bit = 1)

15                                                              0
+---------------------------------------------------------------+
|                            LIMIT 15..0                        | 0
+---------------------------------------------------------------+
|                            BASE 15..0                         | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=1| X |C/E|R/W| A |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+---+---+---+---+---------------+
|         BASE 31..24           | G |D/B| 0 | V | LIMIT 19..16  | 6
+-------------------------------+---+---+---+---+---------------+


G - LIMIT GRANULARITY FLAG = how to load the 20-bit value

    31                              0
    +---+---+---+---+---+---+---+---+
    | 0   0   0 |    LIMIT 19..0    |  <-- G=0: high-order 12 bits filled with zeroes (max 1MB)
    +---+---+---+---+---+---+---+---+
    |    LIMIT 19..0    | F   F   F |  <-- G=1: shift value by 12 and fill low-
    +---+---+---+---+---+---+---+---+            order bits with ones*

    * This is true for EXPAND-DOWN as well: LIMIT=0 and G=1 will load 00000FFF

V - AVAILABLE. Software can write any value here.
    The processor does not set or clear this field.

386 EXECUTABLE SEGMENT DESCRIPTOR (X=1)

15                                                              0
|                                                               |
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=1|X=1| C | R | A |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+---+---+---+---+---------------+
|         BASE 31..24           | G | D | 0 | V | LIMIT 19..16  | 6
+-------------------------------+---+---+---+---+---------------+


D - DEFAULT OPERAND- AND ADDRESS-SIZE

    D = 0
    W-bit in opcode or implicit data/register size means 16-bit WORD
    MODR/M byte interpreted as legacy 8086 and results in 16-bit OFFSET (with OFFSET-HI zeroed)

    D = 1
    W-bit in opcode or implicit data/register size means 32-bit DWORD
    MODR/M byte interpreted as 386-type and results in 32-bit OFFSET

386 DATA SEGMENT DESCRIPTOR (X=0)

15                                                              0
|                                                               |
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=1|X=0| E | W | A |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+---+---+---+---+---------------+
|         BASE 31..24           | G | B | 0 | V | LIMIT 19..16  | 6
+-------------------------------+---+---+---+---+---------------+

B - BIG STACK POINTER

    B = 1
    Use 32-bit ESP for implicit stack instructions and operation

    B = 0
    Use 16-bit SP for implicit instructions
    16-BIT LIMIT CHECK for expand down data segments including SS

The B-bit meaning for limit check

Offsets must be between 0000_0000 to 0000_FFFF when referencing through B=0 E=1 Expand Down Segments.

The B-bit meaning for Stack Segment Register

For dec-inc SP or ESP and data movement to [SS-BASE + SP] or [SS-BASE + ESP] for instructions that implicitly use the stack:

                 B = 0                          B = 1

         31                  0          31                  0
         +---------+---------+          +-------------------+
         | . . . . |  SP +/- | ESP      |      ESP +/-      | ESP
         +---------+---------+          +-------------------+
                   |                              |
                   v                              v
         +-------------------+          +-------------------+
         |  0 0 0 0 x x x x  |          |  x x x x x x x x  |
         +-------------------+          +-------------------+
              LIMIT check                   LIMIT check

         +-------------------+          +-------------------+
         |      SS:BASE      |          |      SS:BASE      |
 +       +-------------------+          +-------------------+
_______________________________________________________________
         +-------------------+          +-------------------+
         | EFFECTIVE ADDRESS |          | EFFECTIVE ADDRESS |
         +-------------------+          +-------------------+

        (.) ESP HI unchanged and ignored

These instructions are push, pop, ret, retf, iret and are NOT affected by address-size (D-bit) or the 67h prefix. B-bit in the current SS determines the operation. They implicitly use SS and there is segment override is not possible (tried with push).

LIMIT granularity and the G-bit

Guides how to load the 32-bit LIMIT into DCR from the 20-bit LIMIT-field value of the descriptor in memory.

G=1 usage makes sense to set limit over 1MB size (eg. flat memory model) – or essentially all expand-down segment in 32-bit (we have to specify a quite high limit):

LIMIT G=0                          LIMIT G=1

Byte-granularity                   4K-granularity

From 0 to 1MB                      In 4KB increments      EXPAND UP    EXPAND DOWN
in byte increment                                         From 4KB     From 0
                                                          to 4GB       to 4GB-4KB
____________________________       ________________________________________________

                       Byte                               Byte         Byte
LIMIT32  <-- LIMIT20   size        LIMIT32  <-- LIMIT20   size         size


FFFFFFFF                           FFFFFFFF <-- FFFFF     4GB          0
-- -- --                           -- -- --
-- -- --                           FFFFEFFF <-- FFFFE     4GB-4KB      4KB
-- -- --                           -- -- --
-- -- --                           FFFFDFFF <-- FFFFD     4GB-8KB      8KB
-- -- --                           -- -- --
-- -- --                           ........     .....
-- -- --                           ........     .....
-- -- --                           ........     .....
-- -- --                           ........     .....
-- -- --                           ........     .....
-- -- --                           -- -- --
000FFFFF <-- FFFFF     1MB         000FFFFF <-- 000FF     1MB
000FFFFE <-- FFFFE                 -- -- --
........     .....                 ........     .....
........     .....                 ........     .....
........     .....                 --------
........     .....                 00001FFF <-- 00001     8KB          4GB-8KB
........     .....                 ........     .....
........     .....                 00000FFF <-- 00000     4KB          4GB-4KB
00000001 <-- 00001     1 byte      ........
00000000 <-- 00000     0 byte      ........     N/A

31.....0     19..0                 31.....0     19..0
000xxxxx <-- xxxxx                 xxxxxFFF <-- xxxxx

LIMIT20 calculation for G=1 based on N = 4K size:

LSL – Load Segment Limit instruction operates the same: "If the descriptor has a page granular segment limit (the granularity flag is set to 1), the LSL instruction will translate the page granular limit (page limit) into a byte limit before loading it into the destination operand. The translation is performed by shifting the 20-bit "raw" limit left 12 bits and filling the low-order 12 bits with 1s."

Possible bit-combinations for loading Segment Descriptors and allowed r/w-operations with override prefix

When cannot be loaded/overridden – cannot be used for r/w. Invalid means exception (general protection fault with an error code of 0).

S=1 Segment Descriptor
X=1 X=0
R=03 R=1 W=0 W=1
LOAD READ WRITE LOAD READ WRITE LOAD READ WRITE LOAD READ WRITE
CS valid2 N/A valid2 N/A invalid invalid
DS invalid valid valid invalid valid valid invalid valid valid valid
ES
SS invalid1 invalid1 invalid1 valid valid valid
(1) For SS, only writable segment can be loaded. Executable segment is not writable (when PE=1).
(2) For CS, the R-bit is don't care
(3) Execute-only segment: cannot be accessed via DS or ES, nor read via CS with a CS override prefix

Note that on the 8086 and in Real Mode Emulation (or any 16/32-code but PE=0) the Executable segment is writable and this is legal:

                               [BITS 16]

2E 67 FE 05 [00000000]         INC BYTE [dword CS:0]
|  |
|  32-bit addressing mode prefix
CS-prefix

S=0 Control Descriptors

There are Gates for execution transfer:

And Segments, a memory area with BASE and LIMIT:

These has selector registers to load and from GDT only. BASE/LIMIT is then cached on-chip in hidden descriptor part.

NOTE!! DESTINATION CODE SELECTOR RPL is don't care in GATES. DPL of Gate matters.

Interrupt gate and Trap gate

In IDT only:

Interrupt gate (IF=0 i.e. CLI) and Trap gate (IF=1)


16-bit 286
Type = 6/7h

15                                                              0
+---------------------------------------------------------------+
|                    INTERRUPT CODE OFFSET                      | 0
+---------------------------------------------------+---+---+---+
|                INTERRUPT CODE SEGMENT SELECTOR    | T | x   x | 2
+---+---+---+---+---+---+---+---+---+---+---+-------+---+---+---+
| P |  DPL  |S=0| 0   1   1   IF| 0   0   0 |      unused       | 4
+---+---+---+---+---+---+---+---+---+---+---+-------------------+
|                         RESERVED = 0                          | 6
+---------------------------------------------------------------+


32-bit 386
Type = E/Fh

15                                                              0
+---------------------------------------------------------------+
|                    INTERRUPT CODE OFFSET                      | 0
+---------------------------------------------------+---+---+---+
|                INTERRUPT CODE SEGMENT SELECTOR    | T | x   x | 2
+---+---+---+---+---+---+---+---+---+---+---+-------+---+---+---+
| P |  DPL  |S=0| 1   1   1   IF| 0   0   0 |      unused       | 4
+---+---+---+---+---+---+---+---+---+---+---+-------------------+
|                INTERRUPT CODE OFFSET 31..16                   | 6
+---------------------------------------------------------------+

x: RPL don't care. Protection on pointed descr DPL.

The difference between a trap and an interrupt gate is whether the interrupt enable flag IF is to be cleared or not.

Call gate

A controlled, indirect function pointer:

Call gate


16-bit 286
Type = 4h

15                                                              0
+---------------------------------------------------------------+
|                    DESTINATION CODE OFFSET                    | 0
+---------------------------------------------------+---+---+---+
|                  DESTINATION CODE SELECTOR        | T | x   x | 2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| P |  DPL  |S=0| 0   1   0   0 | 0   0   0 |    WORD COUNT     | 4
+---+---+---+---+---+---+---+---+---+---+---+-------------------+
|                          RESERVED = 0                         | 6
+---------------------------------------------------------------+


32-bit 386:
Type = Ch

15                                                              0
+---------------------------------------------------------------+
|                    DESTINATION CODE OFFSET                    | 0
+---------------------------------------------------+---+---+---+
|                  DESTINATION CODE SELECTOR        | T | x   x | 2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| P |  DPL  |S=0| 1   1   0   0 | 0   0   0 |   DWORD COUNT     | 4
+---+---+---+---+---+---+---+---+---+---+---+-------------------+
|                       OFFSET 31..16                           | 6
+---------------------------------------------------------------+

x: RPL don't care. Protection on the pointed (executable) descriptor's DPL.

WORD/DWORD COUNT. Used only when CALL more-privilege and stack-switch. Copy n (0..31) WORD/DWORD to new stack. RETF n pulls both.
Based on current TSS values. Get new SS selector from TSS.. set new and copy.

Task Gate

Used for another level of indirection to make task-switch.

Task Gate Descriptor (286/386)

15                                                              0
+---------------------------------------------------------------+
|                            unused                             | 0
+---------------------------------------------------------------+
|                         TSS SELECTOR                          | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=0| 0   1   0   1 |          unused               | 4
+---+---+---+---+---+---+---+---+-------------------------------+
|                          RESERVED = 0                         | 6
+---------------------------------------------------------------+

Task gates can be located in any Descriptor Table (GDT, LDT, IDT).
Invoked by JMP or CALL (or INT).
Task gates may only refer to a task state segment. And the TSS is only in GDT.

Task switch
                                               GDT
                                             +------+
      GDT/LDT/IDT                            |  ..  |
       +------+                              +------+
       |  ..  |               +----------->  |TSS De|  --->  TSS structure
       +------+               |              +------+
       |  ..  |               |              |LDT De|  --->  LD TABLE
       +------+               |              +------+
       |TASK G|   ------------+              |  ..  |
       +------+
       |  ..  |                           Direct jump/call
                                        refers to a TSS Descriptor
  Indirect: jump/call/int                     in GDT
  refers to a TASK GATE

  (Task gate stores a selector
  referring to the GDT)

Task State Descriptor

Only in GDT. Holds a linear memory address of a Task State Structure, TSS (with limit and access-rights).

Cached into on-chip register TR on explicit load (LTR r/m16) or on task-switch..

 _________________________________________
|  CPU                                    |         MEMORY
|                                         |
|   +--------------+                      |      15            0
|   |      TR      | --------------->---- |----> +-------------+
|   +--------------+   linear address     |      |     LDT     | --> My LD TABLE Selector
|                        of TSS           |      +-------------+
|                                         |      |   SEG REGS  |
                                                 |     ...     |
                                                 +-------------+
                                                 |   GP REGS   |
                                                 |     ...     |
                                                 +-------------+
                                                 |    FLAGS    |
                                                 +-------------+
                                                 |      IP     |  --> entry point
                                                 +-------------+
                                                 | SS:SP 2/1/0 |  for automatic stack-switch
                                                 |     ...     |
                                                 +-------------+
                                                 |   BACKLINK  | --> Previous TSS Selector
 NB: This is 16-bit TSS                          +-------------+

The 386 can work with both TSS structures to run 16-bit 286 system compatibility code. And therefore different 16/32-bit types. The 32-bit TSS is very different.. (just not really interested in this).

Task State Descriptor

16-bit 286
Type = 1/3h

15                                                              0
+---------------------------------------------------------------+
|                            LIMIT 15..0                        | 0
+---------------------------------------------------------------+
|                            BASE 15..0                         | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=0| 0   0   B   1 |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+-------------------------------+
|                         RESERVED = 0                          | 6
+---------------------------------------------------------------+

  B-bit: BUSY

  CPU sets this bit, when TR reg is loaded by
  - explicitly by LTR instruction
  - any direct/indirect call/jmp/int referring to a Task Gate or this TSS Descriptor (dispatch)

  NB: LTR is not dispatch! But CPU will use eg. to read higher privilege SS:SP
  on privilege level change CALL/INT.


32-bit 386
Type = 9/Bh

15                                                              0
+---------------------------------------------------------------+
|                            LIMIT 15..0                        | 0
+---------------------------------------------------------------+
|                            BASE 15..0                         | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=0| 1   0   B   1 |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+---+---+---+---+---------------+
|         BASE 31..24           | G | 0 | 0 | V | LIMIT 19..16  | 6
+-------------------------------+---+---+---+---+---------------+

LDT Segment Descriptor

Only in GDT. Holds a linear memory address of a LOCAL TABLE structure (with limit and access-rights).

Cached into on-chip register LDTR on explicit load (LLDT r/m16) or on task-switch from the TSS.

 _________________________________________
|  CPU                                    |       MEMORY
|                                         |
|   +--------------+                      |        LDT
|   |     LDTR     | --------------->---- |----> +------+
|   +--------------+   linear address     |      | SEG  |
|                    of descriptor table  |      +------+
|                                         |      |CALL G|
                                                 +------+
                                                 |TASK G|
                                                 +------+
                                                 |  ..  |

Same 286/386 type, RESERVED = 0 makes it 16-bit 286-compatible.

LDT Segment Descriptor (in GDT)
Type = 2h


16-bit 286

15                                                              0
+---------------------------------------------------------------+
|                            LIMIT 15..0                        | 0
+---------------------------------------------------------------+
|                            BASE 15..0                         | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=0| 0   0   1   0 |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+-------------------------------+
|                         RESERVED = 0                          | 6
+---------------------------------------------------------------+

32-bit 386

15                                                              0
+---------------------------------------------------------------+
|                            LIMIT 15..0                        | 0
+---------------------------------------------------------------+
|                            BASE 15..0                         | 2
+---+---+---+---+---+---+---+---+-------------------------------+
| P |  DPL  |S=0| 0   0   1   0 |        BASE 23..16            | 4
+---+---+---+---+---+---+---+---+---+---+---+---+---------------+
|         BASE 31..24           | G | 0 | 0 | V | LIMIT 19..16  | 6
+-------------------------------+---+---+---+---+---------------+

S=0 Control Descriptors summary

S=0 Control Descriptors
Type 286 S=0 Types 386 S=0 Types
Name Name
0 0000 - -
1 0001 Available TSS 80286 Available TSS
2 0010 LDT Segment LDT Segment
3 0011 Busy TSS 80286 Busy TSS
4 0100 Call gate 80286 Call gate
5 0101 Task gate Task gate
6 0110 Interrupt gate 80286 Interrupt gate
7 0111 Trap gate 80286 Trap gate
8 1000 - -
9 1001 - 80386 Available TSS
A 1010 - -
B 1011 - 80386 Busy TSS
C 1100 - 80386 Call gate
D 1101 - -
E 1110 - 80386 Interrupt gate
F 1111 - 80386 Trap gate

The 3 Descriptor Tables and allowed Descriptors

Segment means a memory area with BASE, LIMIT and Access rights.

GDT LDT IDT
Task State Segment
LDT Segment
Code/Data/Stack Segment Code/Data/Stack Segment
Call gate Call gate
Task gate Task gate Task gate
Interrupt gate
Trap gate