Page 1 of 1
GDT in C
Posted: Sun Mar 06, 2011 1:23 am
by mark3094
Hi,
I have written a GDT in C for a 32-bit x86 system kernel, but I'm not sure it is correct. I have a GDT in the bootloader which is fine (in assembly) but I want one in the Kernel too, incase the bootloader is changed.
My structures are written like this:
Code: Select all
struct gdtstruct {
unsigned short seglimit;
unsigned short lowbase;
unsigned byte midbase;
unsigned byte access;
unsigned byte granularity;
unsigned byte highbase;
};
struct gdtrstruct {
unsigned short limit;
unsigned int base;
};
static struct gdtstruct gdt[2];
static struct gdtrstruct gdtr;
I then have functions to setup the GDT:
Code: Select all
void setgdt(int i, uword low, ubyte mid, ubyte high, uword limit, ubyte access, ubyte gran) {
gdt[i].lowbase = low;
gdt[i].midbase = mid;
gdt[i].highbase = high;
gdt[i].seglimit = limit;
gdt[i].access = access;
gdt[i].granularity = gran;
}
void installgdt() {
/* Add Descriptors to GDT */
setgdt(0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0); /* NULL Descriptor */
setgdt(1, 0x0, 0x0, 0x0, 0xFFFF, 0x9A, 0xCF); /* Code Descriptor */
setgdt(2, 0x0, 0x0, 0x0, 0xFFFF, 0x92, 0xCF); /* Data Descriptor */
/* Setup GDTR */
gdtr.base = (udword)&gdt[0]; /* Start of GDT */
gdtr.limit = (sizeof (struct gdtstruct) * 2) - 1; /* End of GDT */
/* Install GDT into GDTR */
__asm lgdt [gdtr];
}
I then call
from main() which seems to work, but I don't know how to be sure.
Does this look OK?
Re: GDT in C
Posted: Sun Mar 06, 2011 3:22 am
by mark3094
Sorry, I wrote 'unsigned byte' instead of 'unsigned char'.
I typedef'd it in a header and was supposed to change it to char before posting.
Re: GDT in C
Posted: Sun Mar 06, 2011 4:27 am
by cyr1x
Your array fits 2 entries, but you write 3 into it. You should also pack your structures.
Re: GDT in C
Posted: Sun Mar 06, 2011 4:50 am
by Chandra
The only way to find if your GDT is correct, is to actually use it and see if everything goes as intended. Bochs comes handy in this case. Good luck.
[See the post above for what you need to change in your code before actually implementing it]
Re: GDT in C
Posted: Sun Mar 06, 2011 8:50 am
by Tosi
Yeah, you need to pack your structures. Since you used _asm I assume you are using Visual C++ so the directive is something like "#pragma pack". Don't count me on it since I only use MSVC for school and I never need to pack structures so I'm not sure of the syntax.
Also,
only declares 2 entries in the GDT, but you need 3 (NULL, code, data) which you set up in the function installgdt().
Re: GDT in C
Posted: Sun Mar 06, 2011 2:20 pm
by mark3094
Thankyou everyone. I noticed the 2 needed to be changed to 3 as well, not long before I found the replies. I didn't know that the structure would need to be packed though. I'll have to read up on this one a bit more.
On some tutorials, they recommend setting up the segment registers again (
http://www.jamesmolloy.co.uk/tutorial_h ... 20IDT.html). In my case, my GDT is the same as the one I setup in the bootloader, so should I need to do this? My guess is no.
I tested my GDT by printing to screen after installing it, and nothing went wrong. I then tested making the limit zero which I thought would make it crash, but it still prints to screen with no errors. I guess this is because it is not packed, and the GDT is not installing correctly, so I am still using my old GDT from the bootloader?
Re: GDT in C
Posted: Sun Mar 06, 2011 3:52 pm
by Tosi
You absolutely need to reload the segment registers when you change GDT. The CPU maintains an internal descriptor cache to speed up address translation, and if you don't explicitly reload the segment registers after loading a new GDT, they will just contain the values from the old GDT. This is why you can load a GDT with a limit of 0, because you didn't reload the segment registers and hence the descriptor cache.
Re: GDT in C
Posted: Sun Mar 06, 2011 10:42 pm
by mark3094
When I tried to reload the segment registers, it triple faulted, so there is definitely something wrong with my GDT, probably the packing as has already been mentioned.
I haven't had to use it before. From what I have read, it is a method of aligning the structures to a specific boundary. Is this correct?
Is this to stop the compiler from trying to be helpful, and swapping the base and limit in the GDTR?
My next question then, is what boundary should it be aligned to? I have been looking in the Intel System Programming Guide, but I haven't found anything yet. On one tutorial it said to align it to one byte, but didn't really explain why. I would rather understand why that's the case than blindly follow.
Thankyou everyone, for your help.
Re: GDT in C
Posted: Sun Mar 06, 2011 11:45 pm
by mark3094
OK, I think I understand now (I'm happy to be corrected if I am wrong).
According to this link (
http://msdn.microsoft.com/en-us/library ... 80%29.aspx and
http://msdn.microsoft.com/en-us/library ... 80%29.aspx) the default boundary for structures is 8 bytes. This would mean that each member of a structure would align to the 8 byte boundary.
The GDT is supposed to contain descriptors with fixed sizes, so if the members of the GDT and GDTR structure are aligned to an 8 byte boundary, the GDT and GDTR structures would be the wrong size. In the structure I defined in the code above, the smallest unit is 1 byte, so I should use
or
before the structure and
after the structure.
I'm still not sure where the push and pop come into it, but I'll figure it out eventually.
Does this sound right?
Re: GDT in C
Posted: Mon Mar 07, 2011 3:19 am
by mark3094
I thought I had it, but I'm still missing something. Here is what I have so far (uword, ubyte, etc are typedef'd in a header):
Code: Select all
static void setgdt(int, uword, ubyte, ubyte, uword, ubyte, ubyte);
extern void loadseg(unsigned int);
#pragma pack (push, 1)
struct gdtstruct {
unsigned short seglimit;
unsigned short lowbase;
unsigned char midbase;
unsigned char access;
unsigned char granularity;
unsigned char highbase;
};
#pragma pack (pop, 1)
typedef struct gdtstruct gdt_entry_t;
#pragma pack (push, 1)
struct gdtrstruct {
unsigned short limit;
unsigned int base;
};
#pragma pack (pop, 1)
typedef struct gdtrstruct gdt_ptr_t;
gdt_entry_t gdt[2];
gdt_ptr_t gdtr;
static void setgdt(int desc, uword low, ubyte mid, ubyte high, uword limit, ubyte access, ubyte gran) {
gdt[desc].seglimit = limit;
gdt[desc].lowbase = low;
gdt[desc].midbase = mid;
gdt[desc].access = access;
gdt[desc].granularity = gran;
gdt[desc].highbase = high;
}
void installgdt() {
/* Setup GDTR */
gdtr.limit = (sizeof (gdt_entry_t) * 3) - 1;
gdtr.base = (udword)&gdt;
/* Add Descriptors to GDT */
setgdt(0, 0, 0, 0, 0, 0, 0);
setgdt(1, 0, 0, 0, 0xFFFF, 0x9A, 0xCF);
setgdt(2, 0, 0, 0, 0xFFFF, 0x92, 0xCF);
/* Install GDT into GDTR */
loadseg((unsigned int)&gdtr);
}
loadseg() is in a separate ASM file:
Code: Select all
[BITS 32]
[GLOBAL _loadseg]
[SECTION .TEXT]
_loadseg:
mov eax, [esp+8]
lgdt [eax]
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:.flush
.flush:
ret
When it tries to set the segment registers it triple faults.
Any ideas?
Re: GDT in C
Posted: Mon Mar 07, 2011 4:44 am
by Tosi
In the function loadseg:
is wrong. Since you don't set up a stack frame, you don't need to add the 8. Make it
instead.
Re: GDT in C
Posted: Mon Mar 07, 2011 1:49 pm
by mark3094
That was it!
Thanks!
I thought I'd already tried that, but obviously not.
Thanks again for your help, very much appreciated
Re: GDT in C
Posted: Tue Mar 08, 2011 12:03 am
by mark3094
berkus wrote:It makes no sense to typedef new type aliases and then not use them, does it?
I agree. When I started having troubles I looked at a tutorial that did things this way. In desparation I did the same to see if that helped. Obviously it didn't, as my problem was related to structure member alignment and use of the stack.
Now that it is working, I will go back through the code and 'tidy' it up a bit. Thanks for noticing this though
. I appreciate your assistance.
Re: GDT in C
Posted: Tue Mar 08, 2011 12:51 am
by mark3094
I think I got too excited too soon.
I no longer get the triple fault after setting the segment registers, however I don't think the GDT is working correctly.
If I change the 0xFFFF in the code and data descriptors to 0x0, then try to print to screen, it still works. Ironically, I actually want it to fail.
My theory is that if my GDT descriptors have a 0 base and 0 limit, then trying to write to screen should crash shouldn't it? I think that it is not crashing because it's still using the old GDT I created with the bootloader.
Can anyone see what I am doing wrong?
Edit:
I have found that if I change the readable / writable bit in the access byte it triple faults, which suggests the GDT is working after all.
Why does it work when the limit is set to zero? Is it because it is trying to write to a region of memory that is not defined in the GDT (ie, no descriptor for this region of memory because the only descriptors have a base and limit of 0), and the default is 'allow access'?
Re: GDT in C
Posted: Tue Mar 08, 2011 1:38 am
by Graham
mark3094 wrote:I
Also, should the line
be [3] or [2]? If there are supposed to be three descriptors, shouldn't it be [2], which would mean the descriptors are in array elements 0, 1 and 2? If I change it to a [2], it triple faults regardless of whether my limit is set to 0x0 or 0xFFFF.
When you declare an array, that number is the size of array. So gdt_entry_t gdt[3] means you have three entries of type gdt_entry_t. As array indices start at zero, you'll be able to use elements, 0, 1 and 2.