Local Descriptor Table

From OSDev Wiki
Jump to navigation Jump to search

A Local Descriptor Table (LDT) is like the Global Descriptor Table in that it holds Segment descriptors for access to memory. The difference is that every Task/thread can have its own LDT, and the OS can change the LDT Register (LDTR) on every Task switch.

That means that every program can have its own list of memory Segment descriptors, and keep them private from other programs:

  • The Code, Data and Heap segments can be private in the LDT - separate from other programs, but available to this program;
  • Each Task/thread within this program can have its own Stack in the LDT, and yet still be able to access the above Segments;
  • Sharing within the program is automatic: just 'know' the correct Descriptor reference;
  • If another program (with another LDT) was to attempt to access one of these Segments, it would access its own LDT's Segment rather than the target Segment.

What is an LDT?

The Local Descriptor Table is a list of descriptors for the current program. It is distinct from the GDT in a number of respects:

  • It cannot hold many types of descriptors;
  • The GDT holds a reference to the LDT (the Base and Limit of the table in memory);
  • It is only accessible by Tasks/threads with the same Local Descriptor Table Register (LDTR) value.

Only certain Descriptors

Since the LDT is local to the current Task, there are many things that the LDT should not (and in fact cannot) hold:

  • Code for Interrupt handlers;
  • Data for system-wide resources (e.g. keyboard buffer);
  • System segments (Task State Segments and Local Descriptor Tables).

If you think about it, this makes sense: if an interrupt or task-switch occurs, the current Task cannot be expected to hold every possible reference for every system structure - that's what the GDT is for!

GDT holds reference

If the LDT needs to be known to the system, then it needs to be held to the same security ideal as the rest of the system. You wouldn't want a rogue Task to define an LDT that could access sensitive memory! Therefore, the LDT is defined as a 'normal' memory Segment inside the GDT - simply with a Base memory address and Limit. But the fact that it is identified by a specific Descriptor Type (LDT=0x02) means that the Descriptor cannot be loaded into a standard Segment register (CS,DS,ES,FS,GS,SS): the only way that the Local Descriptor Table Register (LDTR) can be loaded with this value is via the LLDT instruction or via a Task State Segment (TSS) - that's right: if you use the '386's hardware tasking mechanism, the TSS holds the Task's LDT and is automatically loaded on a task switch!

Only accessible within current Task

The whole point of using an LDT is to localize this program's Segments to only certain Tasks. Each program would define its own Code, Data and Heap Segments, and a Stack Segment for each Task within the program. Any attempt to access the Segment from outside the program is futile: it would only access the referencing Task's LDT instead (see Introduction above).

How are LDTs defined and accessed?

An LDT is a block of (linear) memory up to 64K in size, just like the GDT. It can be Paged through the standard mechanism, so is treated just like normal memory in that respect. The difference from the GDT is in the Descriptors that it can store, and the method used to access it.

In Protected Mode, a Segment Selector (CS,DS,ES,FS,GS,SS) is used to index a Descriptor from a Descriptor Table. The 16-bit Segment Selector is made of three fields:

 Selector
+=============+=+==+
| Index       |T|PL|
+=============+=+==+
 1111110000000 0 00
 5432109876543 2 10

Index = Index within Descriptor Table
T     = Which Table: 0=GDT, 1=LDT
PL    = Requested Privilege Level (RPL) *
* The RPL is compared against the Privilege Level encoded inside the Descriptor (Descriptor Privilege Level - DPL).
Any mismatches and the request causes a Fault.

So:

  • 0x0000-0x0003 loaded into a Segment Register all work - but attempting to use that Segment Register causes a General Protection Fault (GPF) - the zero'th entry of the GDT is reserved to allow a NULL Selector;
  • 0x0004-0x0007 references the 0x000'th Descriptor in the (current) LDT (with an RPL of 0-3);
  • 0x0008 references the 0x001'st Descriptor in the GDT (with an RPL of 0);
  • 0x000C references the 0x001'st Descriptor in the (current) LDT (with an RPL of 0);
  • 0xF002 references the 0x1E0'th Descriptor in the GDT (with an RPL of 2);
  • 0xF006 references the 0x1E0'th Descriptor in the (current) LDT (with an RPL of 2).