[SOLVED] Setting Segment Registers - Crash?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
mgrosvenor
Posts: 6
Joined: Sat Dec 11, 2010 3:52 pm
Location: Sydney, Australia

[SOLVED] Setting Segment Registers - Crash?

Post by mgrosvenor »

Hi All,
This is my first foray into playing with the GDT. I have built a simple GDT in memory, and using LGDT and SGDT confirmed that it is set. However, when I try to set any segment register my kernel seems to crash. For instance, setting GS should, as far as I know, have no effect however, if I run the following, I never see the second 'X' (0x58) coming out of my serial port. If comment out the movw %%cx, %gs, I get two 'X''s as expected. Any hints?

Code: Select all

__asm__ __volatile__ ("mov $0x58,  %%eax \n"
                      "mov $0x3F8, %%edx \n"
                      "mov $0x10,  %%ecx \n"
                      "outb %%al,  %%dx  \n"
                      "movw %%cx,  %%gs  \n"
                      "outb %%al,  %%dx  \n"   );
Last edited by mgrosvenor on Sun Dec 12, 2010 2:14 pm, edited 1 time in total.
User avatar
xenos
Member
Member
Posts: 1121
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: Setting Segment Registers - Crash?

Post by xenos »

It seems that your GDT is broken, i.e. even though it can be loaded using the LGDT instruction, your selector 0x10 does not contain a valid (data) segment that can be loaded into GS. It would be very helpful if you could post the contents of your GDT.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
mgrosvenor
Posts: 6
Joined: Sat Dec 11, 2010 3:52 pm
Location: Sydney, Australia

Re: Setting Segment Registers - Crash?

Post by mgrosvenor »

Sure thing. I'm not sure what format is good, but I'll show what I have and can modify to show anything you like. I "verified" my GDT by printing out the GRUB supplied GDT first to show that my structs are "correct". I then set my own GDT and print it out. So, this is my output:

Code: Select all

Startiing M6 OS...
==================

This is the GDT supplied by GRUB:
GDT Starts at 0x00009108 and has 5 entries (40b):
000 pres=0 priv=00 exe=0 rw=0 4K=0 32Bit=0 base 0x00000000 limit 65520    top 0x0000fff0
001 pres=1 priv=00 exe=1 rw=1 4K=1 32Bit=1 base 0x00000000 limit 1048575  top 0xfffff000
002 pres=1 priv=00 exe=0 rw=1 4K=1 32Bit=1 base 0x00000000 limit 1048575  top 0xfffff000
003 pres=1 priv=00 exe=1 rw=1 4K=0 32Bit=0 base 0x00000000 limit 65535    top 0x0000ffff
004 pres=1 priv=00 exe=0 rw=1 4K=0 32Bit=0 base 0x00000000 limit 65535    top 0x0000ffff
====================================
These are the segment registers supplied by GRUB
CS: index=001, isldt=0, priv=0
DS: index=002, isldt=0, priv=0
SS: index=002, isldt=0, priv=0
====================================
Setting gdt
This is my own GDT
GDT Starts at 0x00102000 and has 3 entries (24b):
000 pres=1 priv=00 exe=0 rw=0 4K=0 32Bit=0 base 0x00000000 limit 0        top 0x00000000
001 pres=1 priv=00 exe=1 rw=1 4K=1 32Bit=1 base 0x00000000 limit 1048575  top 0xfffff000
002 pres=1 priv=00 exe=0 rw=1 4K=1 32Bit=1 base 0x00000000 limit 1048575  top 0xfffff000
====================================
Generating segments
CS: index=001, isldt=0, priv=0
DS: index=002, isldt=0, priv=0
SS: index=002, isldt=0, priv=0
Integer representation CS=0x8, DS=0x10, SS=0x10



-->X

The source is pretty verbose because I am trying to stick in C as much as possible. Just using little inline blocks to do things like LGDT. Here are my GDT struct defs:

Code: Select all

//GDT Entry Access bits (8bits)
typedef struct{
	uint8_t accessed    : 1; //CPU sets this to 1 if the segment is accessed
	uint8_t read_write  : 1; //On code segments, is segment readable (can never write), on data segments, is segment writeable (can awlays read)
	uint8_t direction   : 1; //On code segments, if 1, code can be executed at lower privilege. On data segments, if 1 segment grows down (ie offset has to be greater than base).
	uint8_t executable  : 1; //If 1 code segment, if 0, data segment.
	uint8_t app         : 1; //0 if system, 1 if application
	uint8_t privilege   : 2; //Privilege level, 0-3, 0 highest.
	uint8_t present     : 1; //Must be 1 for valid selector.

} __attribute__((__packed__)) gdt_entry_access_t;


//GDT entry (8 bytes)
typedef struct{
	uint16_t           limit0_15;       //Segment limit, bits 0-15
	uint16_t           base0_15;        //Base address, bits 0-15
	uint8_t            base16_23;       //Base address, bits 16-23
	gdt_entry_access_t access;          //Access rights
	uint8_t            limit16_19  : 4; //Segment limit, bits 16-19
	uint8_t            reserved    : 2; //Nothing
	uint8_t            size        : 1; //If 1 32bit protected mode, if 0, 16bit protected
	uint8_t            granularity : 1; //If 0, limit is measured in bytes, otherwise in 4K pages.
	uint8_t            base24_31;       //Base address, bits 24-31
}__attribute__((__packed__)) gdt_entry_t;

//Size and location of Global Descriptor Table
typedef struct{
	uint16_t size;
	gdt_entry_t* offset;
} __attribute__((__packed__)) gdt_descriptor_t;


//Segment register format
typedef struct{
	unsigned priv  : 2;
	unsigned isldt : 1;
	unsigned index : 13;
} __attribute__((__packed__)) segment_t;
And the code to set it

Code: Select all

void set_gdt_entry(gdt_entry_t* entry, unsigned base, unsigned limit, uint8_t  executable, uint8_t privelege, uint8_t read_write, uint8_t size, uint8_t granularity)
{
	memset(entry, 0, sizeof(gdt_entry_t));
	entry->limit0_15   = (uint16_t)(limit & 0xFFFF);
	entry->limit16_19  = (uint8_t)( (limit >> 16) & 0xFF);
	entry->base0_15    = (uint16_t)(base & 0xFFFF);
	entry->base16_23   = (uint8_t)( (base >> 16) & 0xFF);
	entry->base24_31   = (uint8_t)( (base >> 24) & 0xFF);
	entry->granularity = granularity;
	entry->size        = size;
	entry->reserved    = 0;

	entry->access.accessed   = 0;
	entry->access.direction  = 0;
	entry->access.executable = executable;
	entry->access.read_write = read_write;
	entry->access.privilege  = privelege;
	entry->access.app        = 0;

	entry->access.present = 1;

}
gdt_descriptor_t gdt_descriptor;
gdt_entry_t gdt[3];

void install_basic_gdt()
{
	set_gdt_entry(&gdt[0], 0, 0, 0, 0, 0, 0, 0);          //First gdt entry is NULL
	set_gdt_entry(&gdt[1], 0, 0xFFFFFFFF, 1, 0, 1, 1, 1); //Entire range is executable
	set_gdt_entry(&gdt[2], 0, 0xFFFFFFFF, 0, 0, 1, 1, 1); //Entire range is data

	gdt_descriptor.offset = gdt;
	gdt_descriptor.size   = (sizeof(gdt_entry_t) * 3) - 1;

	printf("Setting gdt\n");
	set_gdt_descriptor(&gdt_descriptor);

}

//Set the GDT Descriptor to the address given
void set_gdt_descriptor(gdt_descriptor_t* descriptor)
{
	//Note, the "m" attribute has the effect of passing the address of
	//descriptor, which already is a pointer.
	__asm__ __volatile__ ("lgdt %0\n" : : "m"(*descriptor));
}

void set_segs(segment_t* cs_seg,  segment_t*  ds_seg,  segment_t*  ss_seg)
{

	printf("\n\n\n-->");
	//Note, the "m" attribute has the effect of passing the address of
	//seg, which already is a pointer.
	__asm__ __volatile__ ("mov $0x58,  %%eax \n"
			              "mov $0x3F8, %%edx \n"
			              "mov $0x10,  %%ecx \n"
			              "outb %%al,  %%dx  \n"
						  "movw %%cx,  %%gs  \n"
						  "outb %%al,  %%dx  \n"
					      );

	printf("\n\n\n");
}

Hope this makes sense :-P
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: Setting Segment Registers - Crash?

Post by gerryg400 »

Are you sure 8bit bitfields are allowed with your compiler ? Perhaps do a

Code: Select all

sizeof(gdt_entry_access_t)
to check that all's well or do a memory dump of the GDT and check it by hand.
If a trainstation is where trains stop, what is a workstation ?
mgrosvenor
Posts: 6
Joined: Sat Dec 11, 2010 3:52 pm
Location: Sydney, Australia

Re: Setting Segment Registers - Crash?

Post by mgrosvenor »

Good question. I'm using:
gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.1-4ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu9)
I've never had a problem with GCC bitfields before. But previous OS dev experience suggests this is all very version specific.

So far as sizes are concerned, I wrote this early on before I had implemented assert().

Code: Select all

//Visual check that the GDT sizes are as expected
void gdt_check_sizes()
{
	printf("GDT Descriptor is %d, should be 6\n", sizeof(gdt_descriptor_t));
	printf("GDT Entry Access is %d, should be 1\n", sizeof(gdt_entry_access_t));
	printf("GDT Entry is %d, should be 8\n", sizeof(gdt_entry_t));
}
which produces:
GDT Descriptor is 6, should be 6
GDT Entry Access is 1, should be 1
GDT Entry is 8, should be 8
Might try inlining the gdt_entry_access_t and see. Was just trying to be consistent with the intel manual.
mgrosvenor
Posts: 6
Joined: Sat Dec 11, 2010 3:52 pm
Location: Sydney, Australia

Re: Setting Segment Registers - Crash?

Post by mgrosvenor »

AH HA! I figured it out. Very very subtle. Thanks for the suggestion to memory dump. Turned out to be the key. I dumped my GDT, which gave me this:

Code: Select all

Dumping 24 bytes at 0x00102000:
=============================================================
            00 01 02 03 04 05 06 07   08 09 0A 0B 0C 0D 0E 0F
            -------------------------------------------------
0x00102000  00 00 00 00 00 90 00 00   ff ff 00 00 00 8a cf 00
0x00102010  ff ff 00 00 00 82 cf 00
=============================================================

Looking field by field from 0x00102008
limit00:15 - FF FF
base00:15 - 00 00
base16:23 - 00
P:DPL:S:TYPE - 8a (1000 1010)
etc...


The confusion was to do with the "S" field, the intel docs call it "Descriptor type, 0 = system, 1 = code/data" (Vol 3A, 3-13), one of the other docs I read called it "system/application" field. Since I'm doing "system" programming, I assumed a value of 0. (The OS Dev Wiki has this defaulting to 1 with no explanation). I only noticed the difference when I dumped the GRUB installed GDT and compared. Fixing this field to 1 and everything works.

Code: Select all

Dumping 24 bytes at 0x00102000:
=============================================================
            00 01 02 03 04 05 06 07   08 09 0A 0B 0C 0D 0E 0F
            -------------------------------------------------
0x00102000  00 00 00 00 00 90 00 00   ff ff 00 00 00 9a cf 00
0x00102010  ff ff 00 00 00 92 cf 00
=============================================================


:-)
User avatar
xenos
Member
Member
Posts: 1121
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: Setting Segment Registers - Crash?

Post by xenos »

So it was "not a valid data segment" - just as I expected :)

Indeed, this bit is very subtle. A "system descriptor" is not a segment descriptor used by the kernel or the operating system, but one of the system defined descriptors, such as TSS, interrupt gate, call gate and so on.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
mgrosvenor
Posts: 6
Joined: Sat Dec 11, 2010 3:52 pm
Location: Sydney, Australia

Re: Setting Segment Registers - Crash?

Post by mgrosvenor »

Thanks for the clarification. I'll keep that in mind. I guess it sort of makes sense. There's lots of overloaded terminology in the docs!. I've not played with TSS yet...soon enough. My current task is writing an IDT so that I can catch the faulting address next time :-)

Is there a way to mark this thread as "solved"?
User avatar
xenos
Member
Member
Posts: 1121
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: Setting Segment Registers - Crash?

Post by xenos »

Some simple exception / fault handler that print the faulting address and a register dump are very helpful. If you are using bochs or qemu for testing your kernel, you can also get some information from the log file that is created during the simulation.

To mark a thread as "solved", you can edit your initial post and change its title ;)
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
Post Reply