GDT Tutorial: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(11 intermediate revisions by 5 users not shown)
Line 1:
{{Rating|1}}
On the [[IA32_Architecture_Family|IA-32]] and [[X86-64|x86-64]] architectures, and more precisely in '''[[Protected Mode]]''' or '''[[Long Mode]]''', [[Interrupt Service Routines]] and a good deal of [[memory management]] are controlled through tables of descriptors. Each descriptor stores information about a single object (e.g. a service routine, a task, a chunk of code or data) the CPU might need at some time. If you try, for instance, to load a new value into a '''[[Segmentation|Segment Register]]''', the CPU needs to perform safety and access control checks to see whether you're actually entitled to access that specific memory area. Once the checks are performed, useful values (such as the lowest and highest addresses) are cached in invisible CPU registers.
 
On these architectures, there are three of this type of table: The '''[[Global Descriptor Table]]''', the '''[[Local Descriptor Table]]''' and the '''[[Interrupt Descriptor Table]]''' (which supplants the '''[[Interrupt Vector Table]]'''). Each table is defined using their size and '''[[linear address]]''' to the CPU through the '''LGDT''', '''LLDT''', and '''LIDT''' instructions respectively. In almost all use cases, these tables are only placed into memory once, at boot time, and then edited later when needed.
 
== Survival Glossary ==
Line 15:
: An entry in a descriptor table. These are a binary data structure that tells the CPU the attributes of a given segment.
 
== What Toto Put In a GDT ==
 
=== Basics ===
Line 35:
{|class="wikitable" style="display: inline-table;"
|+ 32-bit
! SelectorOffset !! Use !! Content
|-
| 0x0000 || Null Descriptor || <tt>Base = 0<br>Limit = 0x00000000<br>Access Byte = 0x00<br>Flags = 0x0</tt>
Line 47:
| 0x0020 || User Mode Data Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0xF2<br>Flags = 0xC</tt>
|-
| 0x0028 || Task State Segment || <tt>Base = &TSS<br>Limit = sizeof(TSS)-1<br>Access Byte = 0x89<br>Flags = 0x0</tt>
|}
 
{|class="wikitable" style="display: inline-table;"
|+ 64-bit
! SelectorOffset !! Use !! Content
|-
| 0x0000 || Null Descriptor || <tt>Base = 0<br>Limit = 0x00000000<br>Access Byte = 0x00<br>Flags = 0x0</tt>
Line 64:
| 0x0020 || User Mode Data Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0xF2<br>Flags = 0xC</tt>
|-
| 0x0028 || Task State Segment<br>('''[[Global Descriptor Table#64-bitLong Mode System Segment Descriptor|64-bit System Segment]]''') || <tt>Base = &TSS<br>Limit = sizeof(TSS)-1<br>Access Byte = 0x89<br>Flags = 0x0</tt>
|}
 
Line 75:
{|class="wikitable" style="display: inline-table;"
|+ Small Kernel
! SelectorOffset !! Use !! Content
|-
| 0x0000 || Null Descriptor || <tt>Base = 0<br>Limit = 0x00000000<br>Access Byte = 0x00<br>Flags = 0x0</tt>
Line 83:
| 0x0010 || Kernel Mode Data Segment || <tt>Base = 0x00800000<br>Limit = 0x003FFFFF<br>Access Byte = 0x92<br>Flags = 0xC</tt>
|-
| 0x0018 || Task State Segment || <tt>Base = &TSS<br>Limit = sizeof(TSS)-1<br>Access Byte = 0x89<br>Flags = 0x0</tt>
|}
 
Line 98:
{|class="wikitable"
|+ GDT
! SelectorOffset !! Use
|-
| Preceding Entries || Null Descriptor<br>Kernel Segments<br>etc.
Line 123:
=== Filling the Table ===
 
The above structure of the '''GDT''' doesn't show you how to write entries in the correct format. The actual structure of descriptors is a little messy for backwards compatibility with the 286's '''GDT'''. Base address are split across three different fields and you cannot encode any limit you want. Plus, here and there, you have flags that you need to set up properly if you want things to work.
 
<sourcesyntaxhighlight lang="c">
/**
* \param target A pointer to the 8-byte GDT entry
* \param source An arbitrary structure describing the GDT entry
*/
void encodeGdtEntry(uint8_t *target, struct GDT source)
{
Line 150 ⟶ 146:
// Encode the flags
target[6] |= (source.flags << 4);
}
</syntaxhighlight>
</source>
 
In order to fill your table, you will want to use this function once for each entry, with <tt>*target</tt> pointing to the logical address of the '''Segment Descriptor''' and <tt>source</tt> being a struct of your design containing the necessary information.
Line 160 ⟶ 156:
=== Telling the CPU Where the Table Is ===
 
Some assembly example is required here. While you could use [[inline assembly]], the memory packing expected by <tt>the '''LGDT</tt>''' and <tt>'''LIDT</tt>''' instructions makes it much easier to write a small assembly routine instead. As said above, you'll use <tt>'''LGDT</tt>''' instruction to load the base address and the limit of the GDT. Since the base address should be a linear address, you'll need a bit of tweaking depending of your current [[MMU]] setup.
 
==== From Real Mode ====
 
The linear address should here be computed as <tt>segment * 16 + offset</tt>. <tt>GDT</tt> and <tt>GDT_end</tt> are assumed to be symbols in the current data segment.
 
<sourcesyntaxhighlight lang="asm">
gdtr DW 0 ; For limit storage
DD 0 ; For base storage
Line 181 ⟶ 177:
LGDT [gdtr]
RET
</syntaxhighlight>
</source>
 
==== FromProtected FlatMode, ProtectedFlat ModeModel ====
 
"Flat" meaning the base of your dataData segmentSegment is 0 (regardless of whether paging'''[[Paging]]''' is on or offenabled). This is the case if you'reyour code has just been booted by [[GRUB]], for instance. YouIn shouldthe '''[[System V ABI]]''', arguments are passed on reverse order in the stack, so a function that can callbe thiscalled as <tt>setGdt(GDTlimit, sizeof(GDT)base)</tt> might look like the following example code.
 
<sourcesyntaxhighlight lang="asm">
gdtr DW 0 ; For limit storage
DD 0 ; For base storage
 
setGdt:
MOV EAXAX, [esp + 4]
MOV [gdtr], AX
MOV EAX, [ESP + 8]
MOV [gdtr + 2], EAX
MOVLGDT AX, [ESP + 8gdtr]
DEC AXRET
</syntaxhighlight>
 
==== Protected Mode, Non-Flat Model ====
 
If your data segment has a non-zero base, you'll have to adjust the instructions of the sequence above to include the ability to add the base offset of your data segment, which should be a known value to you. You can pass it in as an argument and call this function as <tt>setGdt(limit, base, offset)</tt>.
 
<sourcesyntaxhighlight lang="asm">
gdtr DW 0 ; For limit storage
DD 0 ; For base storage
 
setGdt:
MOV AX, [esp + 4]
MOV [gdtr], AX
MOV EAX, [ESP + 8]
ADD EAX, [ESP + 12]
MOV [gdtr + 2], EAX
LGDT [gdtr]
RET
</syntaxhighlight>
</source>
 
==== From Non-Flat ProtectedLong Mode ====
 
In '''[[Long Mode]]''', the length of the '''Base''' field is 8 bytes, rather than 4. As well, the '''[[System V ABI]]''' passes the first two arguments via the '''RDI''' and '''RSI''' registers. Thus, this example code can be called as <tt>setGdt(limit, base)</tt>. As well, only a flat model is possible in long mode, so no considerations have to be made otherwise.
If your data segment has a non-zero base (e.g. you're using a [[Higher Half Kernel]] during the segmentation trick), you'll have to "<tt>ADD EAX, base_of_your_data_segment_which_you_should_know</tt>" between the "<tt>MOV EAX, ...</tt>" and the "<tt>MOV ..., EAX</tt>" instructions of the sequence above.
 
<syntaxhighlight lang="asm">
gdtr DW 0 ; For limit storage
DQ 0 ; For base storage
 
setGdt:
MOV [gdtr], DI
MOV [gdtr+2], RSI
LGDT [gdtr]
RET
</syntaxhighlight>
 
=== Reload Segment Registers ===
 
Whatever you do with the '''GDT''' has no effect on the CPU until you load selectorsnew '''Segment Selectors''' into segment'''Segment Registers'''. For most of these registers, the process is as simple as using '''MOV''' instructions, but changing the '''CS''' register requires code resembling a jump or call to elsewhere, as this is the only way its value is meant to be changed.
 
==== Protected Mode ====
 
In this case, reloading '''CS''' is as simple as performing a far jump to the required segment, directly after the jump instruction:
<source lang="asm">
 
<syntaxhighlight lang="asm">
reloadSegments:
; Reload CS register containing code selector:
Line 224 ⟶ 250:
MOV SS, AX
RET
</syntaxhighlight>
</source>
 
An explanation of the above code can be found [http://stackoverflow.com/questions/23978486/far-jump-in-gdt-in-bootloader here].
Line 232 ⟶ 258:
In '''[[Long Mode]]''' the process of changing '''CS''' is not simple as far jumps cannot be used. Using a far return is recommended instead:
 
<sourcesyntaxhighlight lang = "asm">
reloadSegments:
; Reload CS register:
Line 248 ⟶ 274:
MOV SS, AX
RET
</syntaxhighlight>
</source>
 
== The LDT ==
Line 276 ⟶ 302:
 
Tool for easily creating GDT entries.
<sourcesyntaxhighlight lang="c">
// Used for creating GDT segment descriptors in 64-bit integer form.
Line 358 ⟶ 384:
}
</syntaxhighlight>
</source>
 
== See Also ==
Line 372 ⟶ 398:
[[Category:Tutorials]]
[[Category:X86 CPU]]
[[Category:Memory Segmentation]]