YeXo - een kernel programmeren

20 maart 2006

Een gdt (global descriptor table) maken

Wat is een gdt?


Een gdt is een speciale tabel. In die tabel staan gegevens zoals die minimale en maximale waardes van de segment registers. Naast de gdt bestaat er ook nog een ldt, een local descriptor table, maar die is sinds de 386 bestaat verouderd. Ik vertel hierna eerst wat de opbouw is van de gdt en daarna geef ik voorbeeldcode die een gdt maakt en laad.


De opbouw van een gdt


Een gdt is een array van gdt elementen. Een gdt element ziet er als volgt uit:
struct gdt_entry
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
De verschillende avariabelen base_* vormen samen de basis (de minimale waarde) van dit gdt element. De limit_* variabelen vormen de limiet (de maximale waarde). De access en granularity velden zijn als volgt opgebouwd:
Het acces veld:


765430
PDPLDTType
P - Is dit element aanwezig? (1 = ja)
DPL - Voor welke ring is dit gdt element? (0 tot 3) 0 is voor de kernel en 3 voor gebruikers-processen.
DT - Geen idee waar dit voor is.
Type - Welk type is dit element?

Het granularity veld:
765430
GD0ASeg Len. 19:16
G - Granularity (0 = 1byte, 1 = 4kbyte)
D - Grootte van dit element, meestal 32 bit(0 = 16bit, 1 = 32-bit)
0 - Deze bit is altijd 0
A - Deze bit is beschikbaar voor de processor, altijd op 0 instellen.


Voorbeeldcode


In dit voorbeeld gebruik ik drie bestanden, namelijk gdt.h, gdt.c en gdtasm.asm. gdt.h kan worden geinclude in het bestand waarin je main functie staat.
gdt.h

#ifndef __GDT_H_
#define __GDT_H_

/* Van het type gdt_entry maken we in gdt.c een array die de gdt voorstelt. Merk op hoe onlogisch
dit struct in elkaar zit. Helaas kan dit niet anders. */
struct gdt_entry
{
unsigned short limit_low;
unsigned short base_low;
unsigned char base_middle;
unsigned char access;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));

/* Dit is een speciale 48 bits pointer naar de gdt. Deze hebben we nodig in gdtasm.asm om de processor
de gdt te laten vinden. */
struct gdt_ptr
{
unsigned short limit;
unsigned int base;
} __attribute__((packed));

/* Het aantal elementen in de gdt. */
#define NUM_GET_ENTRIES 3

/*gdtInstall wordt aangeroepen vanuit kmain. Beide functies staan in gdt.c */
extern void gdtInstall();
extern void gdtSetGate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char granularity);

/* Deze functie staat in gdtasm.asm*/
extern void flushgdt();

#endif //__GDT_H_

gdt.c

#include
#include

/* De gdt en een speciale 48 bits pointer ernaartoe. */
struct gdt_entry gdt[NUM_GET_ENTRIES];
struct gdt_ptr gdtPointer;

/*
gdtSetGate stelt het numde element van de gdt array in. basis staat voor de basis van het segment, meestal 0.
De hoogste waarde die een adres in het segment kan aannemen is basis + (limiet*granularity).
*/
void gdtSetGate(int num, unsigned long basis, unsigned long limiet, unsigned char access, unsigned char gran)
{
/* Stel het basis adres van dit element in. We moeten zo moeiljk doen omdat een gdt_entry onlogisch
opgebouwd is. Zie hiervoor gdt.h*/
gdt[num].base_low = (basis & 0xFFFF);
gdt[num].base_middle = (basis >> 16) & 0xFF;
gdt[num].base_high = (basis >> 24) & 0xFF;

/* Stel de limiet in van dit element. */
gdt[num].limit_low = (limiet & 0xFFFF);
gdt[num].granularity = ((limiet >> 16) & 0x0F);

/* Stel ook de access en granularity velden goed in.*/
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}

/* gdtInstall is eigenlijk de belangrijkste functie uit dit bestand. gdtInstall wordt door kmain aangeroepen
om ervoor te zorgen dat de gdt goed wordt geinstalleerd. */
void gdtInstall()
{
/* Eerst stellen we de gdtPointer goed in. gdtPointer.limit moet de lengte krijgen van de gdt - 1
en gdtPointer.base moet het adres krijgen van het eerste element. */
gdtPointer.limit = (sizeof(struct gdt_entry) * NUM_GET_ENTRIES) - 1;
gdtPointer.base = &gdt;

/* Het eerste element van een gdt moet altijd volledig uit nullen bestaan. Vraag niet waarom, intel
heeft verzonnen dat het zo moet. */
gdtSetGate(0, 0, 0, 0, 0);

/* Het tweede element van de gdt wordt het code segment. Het basis adres is 0 en de
limiet is 4gb/4kb=0xFFFFF (zie gdtSetGate) */
gdtSetGate(1, 0, 0xFFFFF, 0x9A, 0xC0);

/* Het derde element is het date segment. Deze is precies hetzelfde als het code segment behalve
het type (onderdeel van het access veld). */
gdtSetGate(2, 0, 0xFFFFF, 0x92, 0xC0);

/* De functie flushgdt staat in gdtasm.asm en roepen we aan om de processor te laten weten
dat we een nieuwe gdt hebben. */
flushgdt();
}


gdtasm.asm

[BITS 32]
[SECTION .text]

; Deze functie stelt de segment registers in op de nieuwe waardes
; De data segment registers kunnen we vanuit ax laden maar voor cs
; is een andere aanpak nodig. Om cs opnieuw in te stellen moeten we
; een ljmp doen naar 0x08:adres
; Deze functie is gedefinieerd in C als 'extern void flushgdt();'
global _flushgdt ; Dit zorgt ervoor dat C code deze functie kan aanroepen
extern _gdtPointer ; _gdtPointer staat in een ander bestand, namelijk gdt.c
_flushgdt:
lgdt [_gdtPointer] ; Laad de gdt met onze speciale gdtPointer.
mov ax, 0x10 ; 0x10 is het data segment in de gdt
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:gdtflush ; 0x08 is het code segment in de gdt. Let op dat we een far jump doen
gdtflush:
ret ; Weer terug naar de aanroepende C code.