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)
 
(31 intermediate revisions by 15 users not shown)
Line 1:
{{Rating|1}}
InOn the [[:Category:x86IA32_Architecture_Family|IntelIA-32]] Architectureand [[X86-64|x86-64]] architectures, and more precisely in '''[[protectedProtected modeMode]],''' most of theor '''[[memoryLong managementMode]] and''', [[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, whatever) the CPU might need at some time. If you try, for instance, to load a new value into a '''[[SegmentSegmentation|segmentSegment registerRegister]]''', 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 of the CPU.
 
IntelOn definedthese 3architectures, typesthere are three of tablesthis type of table: theThe '''[[InterruptGlobal Descriptor Table]] (which supplants''', the '''[[IVTLocal Descriptor Table]]),''' and the Global'''[[Interrupt Descriptor Table ([[GDT]])''' and(which supplants the Local'''[[Interrupt DescriptorVector Table]]'''). Each table is defined asusing atheir (size, and '''[[linear address]])''' to the CPU through the <tt>LIDT</tt>, <tt>'''LGDT</tt>''', <tt>'''LLDT</tt>''', and '''LIDT''' instructions respectively. In mostalmost all use cases, thethese OStables simplyare tellsonly whereplaced thoseinto tables arememory once, at boot time, and then simplyedited goes writing/reading the tables throughlater awhen pointerneeded.
 
== Survival Glossary ==
 
; '''[[Segmentation|Segment]]'''
: aA logically contiguous chunk of memory with consistent properties (from the CPU's speakingperspective).
; '''Segment Register'''
: aA register of your CPU that refers to a segment for a specialparticular usepurpose (e.g.'''CS''', <tt>SS</tt>'''DS''', <tt>CS</tt>'''SS''', <tt>DS</tt>'''ES''') or for general use ('''FS''', ...'''GS''')
; '''[[Segment Selector]]'''
: aA reference to a descriptor, which you can load into a segment register; the selector is an offset ofinto a descriptor table entry.pointing to one of its entries. These entries are typically 8 bytes long, therefore selectorsbits can3 haveand valuesup 0x00,only 0x08declare the descriptor table entry offset, 0x10while bit 2 specifies if this selector is a GDT or LDT selector (LDT - bit set, 0x18GDT - bit cleared), and bits 0 - 1 declare the ring level that needs to correspond to the descriptor table entry's DPL field.. If it doesn't, a General Protection Fault occurs; if it does correspond then the CPL level of the selector used is changed accordingly.
; '''[[Global_Descriptor_Table#Segment_Descriptor|Segment Descriptor]]'''
; Descriptor
: An entry in a memorydescriptor structuretable. (partThese ofare a table)binary data structure that tells the CPU the attributes of a given segment.
 
== What shouldto iPut putIn in mya GDT? ==
 
=== Basics ===
 
For sanitythe purposesake of sanity, you should always have these items stored in your GDT:
 
* TheEntry null0 in a descriptor whichtable, or the '''Null Descriptor''', is never referenced by the processor, and should always contain no data. Certain emulators, like Bochs, will complain about limit exceptions if you do not have one present. Some use this descriptor to store a pointer to the GDT itself (to use with the LGDT instruction). The null descriptor is 8 bytes wide and the pointer is 6 bytes wide so it might just be the perfect place for this.
* A codeDPL segment0 '''Code Segment''' descriptor (for your kernel, it should have type=0x9A)
* A data'''Data segmentSegment''' descriptor (you can't writewriting to a code segment, so addsegments thisis withnot type=0x92allowed)
* A '''[[TSSTask State Segment]]''' segment descriptor (trust me,its keepvery auseful placeto forhave at least one)
* Room for more segments if you need them (e.g. user-level, [[LDT]]s, more TSS, whatever)
 
=== SysenterFlat /Sysexit Long Mode Setup ===
 
If you do not desire to use '''[[Segmentation]]''' to separate memory into protected areas, you can get away with using only a few segment descriptors. One reason may be that you desire to only use paging to protect memory. As well, this model is ''strictly enforced'' in '''[[Long Mode]]''', as the base and limit values are ignored.
If you are using the Intel <tt>SYSENTER</tt>/<tt>SYSEXIT</tt> routines, the GDT must be structured like this:
 
In this scenario, the only '''[[Global_Descriptor_Table#Segment_Descriptor|Segment Descriptors]]''' necessary are the '''Null Descriptor''', and one descriptor for each combination of privilege level, segment type, and execution mode desired, as well as system descriptors. Usually this will consist of the one code and one data segment for kernel and user mode, and a '''[[Task State Segment]]'''.
* Any descriptors preceding (null descriptor, special kernel stuff, etc.)
* A [[DPL]] 0 code segment descriptor (the one that <tt>SYSENTER</tt> will use)
* A DPL 0 data segment descriptor (for the <tt>SYSENTER</tt> stack)
* A DPL 3 code segment (for the code after <tt>SYSEXIT</tt>)
* A DPL 3 data segment (for the user-mode stack after <tt>SYSEXIT</tt>)
* Any other descriptors
 
{|class="wikitable" style="display: inline-table;"
The segment of the DPL 0 code segment is loaded into an [[MSR]]. The others are calculated based on that value. See the Intel Instruction Reference for <tt>SYSENTER</tt> and <tt>SYSEXIT</tt> for more information.
|+ 32-bit
! Offset !! Use !! Content
|-
| 0x0000 || Null Descriptor || <tt>Base = 0<br>Limit = 0x00000000<br>Access Byte = 0x00<br>Flags = 0x0</tt>
|-
| 0x0008 || Kernel Mode Code Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0x9A<br>Flags = 0xC</tt>
|-
| 0x0010 || Kernel Mode Data Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0x92<br>Flags = 0xC</tt>
|-
| 0x0018 || User Mode Code Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0xFA<br>Flags = 0xC</tt>
|-
| 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;"
The actual values you store there will depend on your system design.
|+ 64-bit
! Offset !! Use !! Content
|-
| 0x0000 || Null Descriptor || <tt>Base = 0<br>Limit = 0x00000000<br>Access Byte = 0x00<br>Flags = 0x0</tt>
|-
| 0x0008 || Kernel Mode Code Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0x9A<br>Flags = 0xA</tt>
|-
| 0x0010 || Kernel Mode Data Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0x92<br>Flags = 0xC</tt>
|-
| 0x0018 || User Mode Code Segment || <tt>Base = 0<br>Limit = 0xFFFFF<br>Access Byte = 0xFA<br>Flags = 0xA</tt>
|-
| 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#Long Mode System Segment Descriptor|64-bit System Segment]]''') || <tt>Base = &TSS<br>Limit = sizeof(TSS)-1<br>Access Byte = 0x89<br>Flags = 0x0</tt>
|}
 
=== FlatSmall Kernel Setup ===
 
If you desire to separate memory into protected areas of code and data, you will have to set the '''Base''' and '''Limit''' value of each entry in the table to the desired format.
You want to have full 4 GiB addresses untranslated: just use
 
For example, you may want to have two segments, a 4MiB Code Segment starting at 4MiB, and a 4MiB Data Segment starting at 8MiB, both accessible only to Ring 0. In that case, your GDT may look like this:
<source lang="c">
GDT[0] = {.base=0, .limit=0, .type=0}; // Selector 0x00 cannot be used
GDT[1] = {.base=0, .limit=0xffffffff, .type=0x9A}; // Selector 0x08 will be our code
GDT[2] = {.base=0, .limit=0xffffffff, .type=0x92}; // Selector 0x10 will be our data
GDT[3] = {.base=&myTss, .limit=sizeof(myTss), .type=0x89}; // You can use LTR(0x18)
</source>
 
{|class="wikitable" style="display: inline-table;"
Note that in this model, code is not actually protected against overwriting since code and data segment overlaps.
|+ Small Kernel
! Offset !! Use !! Content
|-
| 0x0000 || Null Descriptor || <tt>Base = 0<br>Limit = 0x00000000<br>Access Byte = 0x00<br>Flags = 0x0</tt>
|-
| 0x0008 || Kernel Mode Code Segment || <tt>Base = 0x00400000<br>Limit = 0x003FFFFF<br>Access Byte = 0x9A<br>Flags = 0xC</tt>
|-
| 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>
|}
 
That means whatever you load at physical address 4 MiB will appear as code at '''CS:0''' and what you load at physical address 8 MiB will appear as data at '''DS:0'''.
== Small Kernel Setup ==
 
This specifically is not a recommended design, but shows how to think about using the '''GDT''' to define separated segments.
If you want (for specific reason) to have code and data clearly separated (let's say 4 MiB for both, starting at 4 MiB as well), just use:
 
=== SYSENTER / SYSEXIT ===
<source lang="c">
GDT[0] = {.base=0, .limit=0, .type=0}; // Selector 0x00 cannot be used
GDT[1] = {.base=0x04000000, .limit=0x03ffffff, .type=0x9A}; // Selector 0x08 will be our code
GDT[2] = {.base=0x08000000, .limit=0x03ffffff, .type=0x92}; // Selector 0x10 will be our data
GDT[3] = {.base=&myTss, .limit=sizeof(myTss), .type=0x89}; // You can use LTR(0x18)
</source>
 
If you are using the Intel '''SYSENTER''' / '''SYSEXIT''' routines, the '''GDT''' must contain four special entries, the first one pointed to by the value in the '''IA32_SYSENTER_CS''' '''[[Model Specific Registers|Model Specific Register]]''' (MSR 0x0174).
That means whatever you load at physical address 4 MiB will appear as code at <tt>CS:0</tt> and what you load at physical address 8 MiB will appear as data at <tt>DS:0</tt>. However it might not be the best design.
 
For more information, see the sections on '''SYSENTER''' and '''SYSEXIT''' in '''Chapter 4.3: Instructions (M-U)''' of the Intel Software Developer Manual, Volume 2-B.
== How can we do that? ==
 
{|class="wikitable"
=== Disable interrupts ===
|+ GDT
! Offset !! Use
|-
| Preceding Entries || Null Descriptor<br>Kernel Segments<br>etc.
|-
| IA32_SYSENTER_CS + 0x0000 || DPL 0 Code Segment<br>'''SYSENTER''' Code
|-
| IA32_SYSENTER_CS + 0x0008 || DPL 0 Data Segment<br>'''SYSENTER''' Stack
|-
| IA32_SYSENTER_CS + 0x0010 || DPL 3 Code Segment<br>'''SYSEXIT''' Code
|-
| IA32_SYSENTER_CS + 0x0018 || DPL 3 Data Segment<br>'''SYSEXIT''' Stack
|-
| Successive Entries || Any Other Descriptors
|}
 
The actual values stored in these segments will depend on your system design.
If they're enabled, turn them off or you're in for trouble.
 
=== FillingHow theto tableSet Up The GDT ===
 
=== Disable Interrupts ===
You noticed that I didn't give a real structure for <tt>GDT[]</tt>, didn't you? That's on purpose. The actual structure of descriptors is a little messy for backwards compatibility with the 286's GDT. Base address are split on 3 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.
 
If they're enabled, ''be absolutely sure'' to turn them off or you could run into undesired behavior and exceptions. This can be achieved through the '''CLI''' assembly instruction.
<source lang="c">
 
/**
=== Filling the Table ===
* \param target A pointer to the 8-byte GDT entry
 
* \param source An arbitrary structure describing the GDT entry
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.
*/
 
<syntaxhighlight lang="c">
void encodeGdtEntry(uint8_t *target, struct GDT source)
{
// Check the limit to make sure that it can be encoded
if ((source.limit > 655360xFFFFF) && {kerror(source.limit"GDT &cannot 0xFFF)encode !=limits 0xFFFlarger than 0xFFFFF")) {;}
kerror("You can't do that!");
}
if (source.limit > 65536) {
// Adjust granularity if required
source.limit = source.limit >> 12;
target[6] = 0xC0;
} else {
target[6] = 0x40;
}
// Encode the limit
target[0] = source.limit & 0xFF;
target[1] = (source.limit >> 8) & 0xFF;
target[6] |= (source.limit >> 16) & 0xF0x0F;
// Encode the base
target[2] = source.base & 0xFF;
target[3] = (source.base >> 8) & 0xFF;
Line 108 ⟶ 142:
target[7] = (source.base >> 24) & 0xFF;
// And...Encode Typethe access byte
target[5] = source.typeaccess_byte;
// 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.
Okay, that's rather ugly, but it's the most 'for dummies' I can come with ... hope you know about masking and shifting. You can hard-code that rather than convert it at runtime, of course. This code assumes that you only want 32-bit segments.
 
You can hard-code values in the '''GDT''' rather than converting them at runtime, of course.
=== Telling the CPU where the table stands ===
 
=== 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>LGDT</tt> and <tt>LIDT</tt> 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.
 
Some assembly is required here. While you could use [[inline assembly]], the memory packing expected by the '''LGDT''' and '''LIDT''' instructions makes it much easier to write a small assembly routine instead. As said above, you'll use '''LGDT''' 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 ====
 
==== Real Mode ====
The linear address should here be computed as <tt>segment * 16 + offset</tt>. I'll assume <tt>GDT</tt> and <tt>GDT_end</tt> are symbols in the current data segment.
 
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.
<source lang="asm">
 
<syntaxhighlight lang="asm">
gdtr DW 0 ; For limit storage
DD 0 ; For base storage
Line 138 ⟶ 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. IIn the 'll''[[System assumeV youABI]]''', callarguments are passed on reverse order in the stack, so a function that can be called 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]
RET
</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>.
 
<syntaxhighlight 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>
 
==== FromLong non-flat protected modeMode ====
 
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">
=== Reload segment registers ===
gdtr DW 0 ; For limit storage
DQ 0 ; For base storage
 
setGdt:
Whatever you do with the GDT has no effect on the CPU until you load selectors into segment registers. You can do this using:
MOV [gdtr], DI
MOV [gdtr+2], RSI
LGDT [gdtr]
RET
</syntaxhighlight>
 
=== Reload Segment Registers ===
<source lang="asm">
 
Whatever you do with the '''GDT''' has no effect on the CPU until you load new '''Segment Selectors''' into '''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:
 
<syntaxhighlight lang="asm">
reloadSegments:
; Reload CS register containing code selector:
JMP 0x08:.reload_CS ; 0x08 is a stand-in for your code segment
.reload_CS:
; Reload data segment registers:
MOV AX, 0x10 ; 0x10 pointsis ata thestand-in newfor your data selectorsegment
MOV DS, AX
MOV ES, AX
Line 174 ⟶ 249:
MOV GS, AX
MOV SS, AX
; Reload CS register containing code selector:
JMP 0x08:reload_CS ; 0x08 points at the new code selector
.reload_CS:
RET
</syntaxhighlight>
</source>
 
An explanation of the above code can be found [http://stackoverflow.com/questions/23978486/far-jump-in-gdt-in-bootloader here].
 
==== Long Mode ====
== What's so special about the LDT? ==
 
In '''[[Long Mode]]''' the process of changing '''CS''' is not simple as far jumps cannot be used. Using a far return is recommended instead:
 
<syntaxhighlight lang = "asm">
reloadSegments:
; Reload CS register:
PUSH 0x08 ; Push code segment to stack, 0x08 is a stand-in for your code segment
LEA RAX, [rel .reload_CS] ; Load address of .reload_CS into RAX
PUSH RAX ; Push this value to the stack
RETFQ ; Perform a far return, RETFQ or LRETQ depending on syntax
.reload_CS:
; Reload data segment registers
MOV AX, 0x10 ; 0x10 is a stand-in for your data segment
MOV DS, AX
MOV ES, AX
MOV FS, AX
MOV GS, AX
MOV SS, AX
RET
</syntaxhighlight>
 
== The LDT ==
 
Much like the GDT (global descriptor table), the LDT (''local'' descriptor table) contains descriptors for memory segments description, call gates, etc. The good thing with the LDT is that each task can have its own LDT and that the processor will automatically switch to the right LDT when you use hardware task switching.
Line 199 ⟶ 293:
Note that with 386+ processors, the paging has made LDT almost obsolete, and there's no longer need for multiple LDT descriptors, so you can almost safely ignore the LDT for OS developing, unless you have by design many different segments to store.
 
== What is theThe IDT and iswhy it's needed? ==
 
As said above, the IDT (Interrupt Descriptor Table) loads much the same way as the GDT and its structure is roughly the same except that it only contains gates and not segments. Each gate gives a full reference to a piece of code (code segment, privilege level and offset to the code in that segment) that is now bound to a number between 0 and 255 (the slot in the IDT).
 
The IDT will be one of the first things to be enabled in your kernel sequence, so that you can catch hardware exceptions, listen to external events, etc. See [[Interrupts for dummies]] for more information about the interrupts of X86 family.
 
== Some stuff to make your life easy ==
 
Tool for easily creating GDT entries.
<sourcesyntaxhighlight lang="c">
// Used for creating GDT segment descriptors in 64-bit integer form.
Line 290 ⟶ 384:
}
</syntaxhighlight>
</source>
 
== See Also ==
Line 296 ⟶ 390:
=== Articles ===
* [[Global Descriptor Table]]
* http://web.archive.org/web/20190424213806/http://www.osdever.net/tutorials/view/the-world-of-protected-mode - how to set up GDT in assembler
 
=== Threads ===
Line 303 ⟶ 398:
[[Category:Tutorials]]
[[Category:X86 CPU]]
[[Category:Memory Segmentation]]