GDT in C

Programming, for all ages and all languages.
Post Reply
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

GDT in C

Post 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

Code: Select all

installgdt()
from main() which seems to work, but I don't know how to be sure.

Does this look OK?
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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.
cyr1x
Member
Member
Posts: 207
Joined: Tue Aug 21, 2007 1:41 am
Location: Germany

Re: GDT in C

Post by cyr1x »

Your array fits 2 entries, but you write 3 into it. You should also pack your structures.
User avatar
Chandra
Member
Member
Posts: 487
Joined: Sat Jul 17, 2010 12:45 am

Re: GDT in C

Post 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]
Programming is not about using a language to solve a problem, it's about using logic to find a solution !
Tosi
Member
Member
Posts: 255
Joined: Tue Jun 15, 2010 9:27 am
Location: Flyover State, United States
Contact:

Re: GDT in C

Post 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,

Code: Select all

static struct gdtstruct gdt[2];
only declares 2 entries in the GDT, but you need 3 (NULL, code, data) which you set up in the function installgdt().
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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?
Tosi
Member
Member
Posts: 255
Joined: Tue Jun 15, 2010 9:27 am
Location: Flyover State, United States
Contact:

Re: GDT in C

Post 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.
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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.
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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

Code: Select all

#pragma pack(1)
or

Code: Select all

#pragma pack (push, 1)
before the structure and

Code: Select all

#pragma pack (pop, 1)
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?
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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?
Tosi
Member
Member
Posts: 255
Joined: Tue Jun 15, 2010 9:27 am
Location: Flyover State, United States
Contact:

Re: GDT in C

Post by Tosi »

In the function loadseg:

Code: Select all

   mov eax, [esp+8]
is wrong. Since you don't set up a stack frame, you don't need to add the 8. Make it

Code: Select all

   mov eax, [esp+4]
instead.
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post by mark3094 »

That was it!
Thanks!

I thought I'd already tried that, but obviously not.

Thanks again for your help, very much appreciated
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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.
User avatar
mark3094
Member
Member
Posts: 164
Joined: Mon Feb 14, 2011 10:32 pm
Location: Australia
Contact:

Re: GDT in C

Post 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'?
Last edited by mark3094 on Tue Mar 08, 2011 2:09 am, edited 1 time in total.
Graham
Posts: 14
Joined: Thu Mar 04, 2010 7:29 am
Location: UK
Contact:

Re: GDT in C

Post by Graham »

mark3094 wrote:I
Also, should the line

Code: Select all

gdt_entry_t gdt[3]
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.
Post Reply