Page 1 of 2
My weird little PE kernel
Posted: Fri Jun 29, 2007 6:49 pm
by LaurensR1983
Hi everybody!
I have a weird little kernel experiment going on... I'm compiling my .c source files using the standard VC compiler (cl.exe), I generate my multiboot header and external assembly functions using NASM and I link the entire thing together using the GNU linker (ld.exe - MinGW version).
The reason I decided to go with the microsoft compiler, is because I like the MS way of doing inline assembly as well as other certain aspects of C. It also allows me to use my favorite IDE (Visual Studio 2005 pro). I decided to use the GNU linker because I like the way how it can be scripted. Besides all of that, I find it interesting to explore the possibility of using parts of different toolsets in one project.
But anyway... to get to my question: I borrowd some GDT code from a tutorial and at a certain point in my kernel I'm trying to install a new GDT table.
Code: Select all
global _gdt_flush ; Allows the C code to link to this
extern _gp ; Says that '_gp' is in another file
_gdt_flush:
lgdt [_gp] ; Load the GDT with our '_gp' which is a special pointer
mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
jmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump!
flush2:
ret ; Returns back to the C code!
When I get at the part of changing my SS register an error occures and an exception is raised according to BOCHS:
Code: Select all
Exception(): 3rd (13) exception with no resolution
I think it is a memory protection exception of some sort, but I don't know for sure. I know for sure the "mov ss" line is the problem, because when I comment it out, the kernel works just fine. Could the 0x10 offset be the problem?
I'm a pretty experienced developer, but very new to OS development so be gentle with me
Posted: Fri Jun 29, 2007 6:53 pm
by Kevin McGuire
Most likely will need to see the binary representation of your descriptors, or how and what code you use to generate the descriptors to ensure their bits have been set correctly.
Posted: Sat Jun 30, 2007 12:39 am
by B.E
Correct me if I'm wrong, but don't you have to flush the cache before you set the segment descriptors (see below). Also it would be a good idea to show us the bochsout.
Code: Select all
global _gdt_flush ; Allows the C code to link to this
extern _gp ; Says that '_gp' is in another file
_gdt_flush:
lgdt [_gp] ; Load the GDT with our '_gp' which is a special pointer
jmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump!
flush2:
mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
ret ; Returns back to the C code!
Posted: Sat Jun 30, 2007 3:01 am
by LaurensR1983
Ok here is the debugging output from Bochs (I left out the init stuff from bochs itself):
Code: Select all
00154253328e[CPU0 ] load_seg_reg(SS): not writable data segment
00154253328e[CPU0 ] interrupt(): gate descriptor is not valid sys seg
00154253328e[CPU0 ] interrupt(): gate descriptor is not valid sys seg
00154253328i[CPU0 ] protected mode
00154253328i[CPU0 ] CS.d_b = 32 bit
00154253328i[CPU0 ] SS.d_b = 32 bit
00154253328i[CPU0 ] | EAX=00000010 EBX=00026260 ECX=00000002 EDX=00000002
00154253328i[CPU0 ] | ESP=0010502c EBP=00105030 ESI=000263bb EDI=000263c4
00154253328i[CPU0 ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf AF PF cf
00154253328i[CPU0 ] | SEG selector base limit G D
00154253328i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
00154253328i[CPU0 ] | CS:0008( 0001| 0| 0) 00000000 000fffff 1 1
00154253328i[CPU0 ] | DS:0010( 0002| 0| 0) ffffffff 000fffff 1 1
00154253328i[CPU0 ] | SS:0010( 0002| 0| 0) 00000000 000fffff 1 1
00154253328i[CPU0 ] | ES:0010( 0002| 0| 0) ffffffff 000fffff 1 1
00154253328i[CPU0 ] | FS:0010( 0002| 0| 0) ffffffff 000fffff 1 1
00154253328i[CPU0 ] | GS:0010( 0002| 0| 0) ffffffff 000fffff 1 1
00154253328i[CPU0 ] | EIP=001018ba (001018ba)
00154253328i[CPU0 ] | CR0=0x00000011 CR1=0 CR2=0x00000000
00154253328i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
00154253328i[CPU0 ] >> mov ss, ax : 8ED0
00154253328e[CPU0 ] exception(): 3rd (13) exception with no resolution, shutdown
status is 00h, resetting
btw, I decided to flush the thing this way because the tutorial told me so. I have used the same technique succesfully before in another kernel using DJGPP. But I will give it a try
As for the C code for setting all stuff up (I left all the tutorial comments in, because they might help you analysing the code):
Code: Select all
void k_gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
/* Setup the descriptor base address */
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
/* Setup the descriptor limits */
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = ((limit >> 16) & 0x0F);
/* Finally, set up the granularity and access flags */
gdt[num].granularity |= (gran & 0xF0);
gdt[num].access = access;
}
void k_gdt_install()
{
gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
gp.base = &gdt;
/* Our NULL descriptor */
k_gdt_set_gate(0, 0, 0, 0, 0);
/* The second entry is our Code Segment. The base address
* is 0, the limit is 4GBytes, it uses 4KByte granularity,
* uses 32-bit opcodes, and is a Code Segment descriptor.
* Please check the table above in the tutorial in order
* to see exactly what each value means */
k_gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
/* The third entry is our Data Segment. It's EXACTLY the
* same as our code segment, but the descriptor type in
* this entry's access byte says it's a Data Segment */
k_gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
/* Flush out the old GDT and install the new changes! */
gdt_flush(); //this is the assembly function that I gave you before
}
Thanks for all the advice so far
edit: I tried to change the assembly function as suggested, but it didn't help though... i'll just keep debugging.
Posted: Sat Jun 30, 2007 5:13 am
by Kevin McGuire
The answer is at the very top of the BOCHS debug output. It tells you what is wrong with trying to move the descriptor into SS.
The data segment is r--, instead of rw-.
Posted: Sat Jun 30, 2007 5:41 am
by LaurensR1983
Ah I see... it makes more sense to me now
I'm going to try to change it from r-- to -rw
probably just something I overlooked somehow... now to figure out how to do it
Somehow I'm having fun figuring this all out
edit: I mean rw- ofcourse
Posted: Sat Jun 30, 2007 11:57 am
by LaurensR1983
Ok, I'm a bit lost now... :S
I searched lot's of sites but cant find anything concerning access modifiers for segments, registers or whatever.
Could somebody point me in the right direction?
I have a feeling that solution might be quite simple, but that simply don't know where to look. :S
Posted: Sat Jun 30, 2007 12:29 pm
by frank
Something else is wrong with the code, but the problem is not the flags. This part is right and the flags are right 0x92 and 0xcf.
Code: Select all
/* The third entry is our Data Segment. It's EXACTLY the
* same as our code segment, but the descriptor type in
* this entry's access byte says it's a Data Segment */
k_gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
I think that something is either wrong with the k_gdt_set_gate function or the structure that the gdt is using. Look at the base addresses for ds es and so on. The data must be getting placed in the wrong location.
Can you show us your gdt struct?
Posted: Sat Jun 30, 2007 12:59 pm
by LaurensR1983
sure, here it is
Code: Select all
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;
};
struct gdt_entry gdt[3];
Posted: Sat Jun 30, 2007 1:09 pm
by Kevin McGuire
I think frank is correct. I have just noticed that it loads crazy base value for each segment until it raises the exception when loading ss, like frank stated earlier.
If I remember correctly it was the command info gdt that you could type in the BOCHS debugger which would print information about the GDT such as where the processor thinks it is at and how long it think it is.
Posted: Sat Jun 30, 2007 2:28 pm
by JJeronimo
LaurensR1983 wrote:Ok here is the debugging output from Bochs (I left out the init stuff from bochs itself):
Code: Select all
00154253328e[CPU0 ] load_seg_reg(SS): not writable data segment
00154253328e[CPU0 ] interrupt(): gate descriptor is not valid sys seg
00154253328e[CPU0 ] interrupt(): gate descriptor is not valid sys seg
(...)
Do step by step execution from the start of the gdt_flush function to determine the faulty instruction (see the address in the linking script or write a spinlock and then manipulate it with bochs debugger's "set" command). For example:
Code: Select all
xor al, al
.bpspinlock:test al, al
jz .bpspinlock
Then, when execution stops, go to the console where bochs is running and do Ctrl-C. Then enter "set al = 1" and you can execute the code one instruction at one time, using the "p" command.
Take care when you introduce this thing inside normal code flow, so that you don't corrupt any registers the compiler is using.
Code: Select all
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;
};
I've read you code and found no errors. To catch the problem, your best solution is doing step-by-step through the function that loads the descriptor and see what is happening wrong...
Also use the "info gdt" bochs command...
And... this may sound stupid, but ensure that both the gdt_entry and the gp structures are appropriately packed... Automatic alignment caused me a bug once...
JJ
Posted: Sat Jun 30, 2007 3:12 pm
by frank
Try changing it to
Code: Select all
#pragma pack( push )
#pragma pack( 1 )
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;
};
/* put the structure for gp in here to */
#pragma pack( pop )
Posted: Sat Jun 30, 2007 3:15 pm
by JJeronimo
frank wrote:Try changing it to
Code: Select all
#pragma pack( push )
#pragma pack( 1 )
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;
};
#pragma pack( pop )
The align problem is more likely to be in the gp structure, cause the limit, which comes first, is shorter than the address...
JJ
Posted: Sat Jun 30, 2007 3:23 pm
by LaurensR1983
Well thank you all for all your suggestions and ideas... I'll start trying them out first thing tomorrow morning.
I'll let you all know when I made some progress..
.. or not
edit: for those who are wondering, this is my gp structure:
Code: Select all
struct gdt_ptr
{
unsigned short limit;
unsigned int base;
};
struct gdt_ptr gp;
Posted: Sat Jun 30, 2007 3:38 pm
by LaurensR1983
Hurray, it works!
The #pragma pack(push) etc did the trick!
So the problem was probably an alignment issue of some sort.
Thanks to everybody for their input!