My weird little PE kernel

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.
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

My weird little PE kernel

Post 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 :wink:
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post 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.
User avatar
B.E
Member
Member
Posts: 275
Joined: Sat Oct 21, 2006 5:29 pm
Location: Brisbane Australia
Contact:

Post 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! 
Image
Microsoft: "let everyone run after us. We'll just INNOV~1"
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

Post 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.
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post 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-.
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

Post 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 :D

edit: I mean rw- ofcourse
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

Post 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
frank
Member
Member
Posts: 729
Joined: Sat Dec 30, 2006 2:31 pm
Location: East Coast, USA

Post 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?
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

Post 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];

User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post 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.
JJeronimo
Member
Member
Posts: 202
Joined: Wed Oct 18, 2006 3:29 pm

Post 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
frank
Member
Member
Posts: 729
Joined: Sat Dec 30, 2006 2:31 pm
Location: East Coast, USA

Post 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 )
JJeronimo
Member
Member
Posts: 202
Joined: Wed Oct 18, 2006 3:29 pm

Post 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
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

Post 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 :oops:

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;
LaurensR1983
Posts: 24
Joined: Wed Jun 27, 2007 10:34 am

Post by LaurensR1983 »

Hurray, it works!

The #pragma pack(push) etc did the trick! :D So the problem was probably an alignment issue of some sort.

Thanks to everybody for their input!
Post Reply