Loading GDT causes triple-fault

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.
User avatar
KrnlHckr
Member
Member
Posts: 36
Joined: Tue Jul 17, 2007 9:16 am
Location: Washington, DC Metro Area
Contact:

Loading GDT causes triple-fault

Post by KrnlHckr »

Okay, I know that everyone has answered droves of questions related to newbie attempts at using Bran's tutorial. I've personally used the tutorial successfully on numerous occasions (cut and paste tinkering) for the sake of playing around. It's a nice piece of work. Now that I've sat down to actually work on a research kernel, I've managed to code a GDT that triple-faults.

I apologize for such a lenghty post but I sincerely appreciate anyone's help which might clear up this issue. I've worked on this for over a week and have made no progress. :x

I've attached the tarball of my code. It is heavily borrowed from Bran's tutorial to act as a skeleton while I rework his code and start on my additions. If you've fiddled with his tutorial any, the code will look familiar.

Code: Select all

/* gdt.c */
#include <gdt.h>

/* setup descriptor in gdt */
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char granularity)
{
        /* set up descriptor base addr */
        gdt[num].base_lo = (base & 0xFFFF);
        gdt[num].base_mid = (base >> 16) & 0xFF;
        gdt[num].base_hi = (base >> 24) & 0xFF;

        /* set up descriptor limits */
        gdt[num].limit_lo = (limit & 0xFFFF);
        gdt[num].granularity = ((limit >> 16) & 0x0F);

        /* set up granularity and access flags */
        gdt[num].granularity |= (granularity & 0xF0);
        gdt[num].access = access;
}

/* called by main to set up gdt, first 3 entries in gdt, and call gdt_flush */
void gdt_install()
{
        /* setup gdt pointer and limit */
        gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
        gp.base = (unsigned int)(&gdt);

        /* null descriptor */
        gdt_set_gate(0, 0, 0, 0, 0);

        /* cs - base addr is 0, limit is 4gb w/ 4kb gran, 32-bit opcodes */
        gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

        /* ds - same as code, but descriptor type is ds */
        gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);

        /* flush old gdt and install new */
        gdt_flush();
}

Code: Select all

/* include/gdt.h */
#ifndef __GDT_H
#define __GDT_H

/* define gdt entry - packed prevents compiler from dorking it up */
struct gdt_entry
{
        unsigned short limit_lo;
        unsigned short base_lo;
        unsigned char base_mid;
        unsigned char access;
        unsigned char granularity;
        unsigned char base_hi;
} __attribute__ ((__packed__));

/* special pointer - max bytes taken by gdt - 1 */
struct gdt_ptr
{
        unsigned short limit;
        unsigned int base;
} __attribute__ ((__packed__));

/* a simple 3-entry gdt and pointer */
struct gdt_entry gdt[3];
struct gdt_ptr gp;

/* the extern function in loader.asm */
extern void gdt_flush();

void gdt_set_gate(int, unsigned long, unsigned long, unsigned char, unsigned char);
void gdt_install();

#endif
When I run the code from Bochs (or VMware), I get a triple-fault at the far jump.

Code: Select all

; snippet from loader.asm
        ;; the gdt (global descriptor table)
        [global gdt_flush]      ; allow c code to link this
        [extern gp]             ; gp is in gdt.c
gdt_flush:
        lgdt    [gp]
        jmp     0x08:gdt_flush2 ; 0x08 is offset to code seg - far jmp
gdt_flush2:
        mov     ax, 0x10        ; 0x10 is offset in gdt to ds
        mov     ds, ax
        mov     es, ax
        mov     fs, ax
        mov     gs, ax
        mov     ss, ax
        ret                     ; return back to c code
I've tried using the original code to look like:

Code: Select all

        ;; the gdt (global descriptor table)
        [global gdt_flush]      ; allow c code to link this
        [extern gp]             ; gp is in gdt.c
gdt_flush:
        lgdt    [gp]
        mov     ax, 0x10        ; 0x10 is offset in gdt to ds
        mov     ds, ax
        mov     es, ax
        mov     fs, ax
        mov     gs, ax
        mov     ss, ax
gdt_flush2:
        ret                     ; return back to c code
but that triple-faults at the mov ds, ax operation.

My link.ld looks like:

Code: Select all

OUTPUT_FORMAT("elf32-i386")
ENTRY(start)
phys = 0x00100000;
SECTIONS
{
        .text phys : AT(phys)
        {
                code = .;
                *(.text)
                *(.rodata*)
                . = ALIGN(4096);
        }
        .data : AT(phys + (data - code))
        {
                data = .;
                *(.data)
                . = ALIGN(4096);
        }
        .bss : AT(phys + (bss - code))
        {
                bss = .;
                *(.bss)
                *(.COMMON*)
                . = ALIGN(4096);
        }
        end = .;
}
The output from Bochs using the far jump:

Code: Select all

00079523319e[CPU  ] fetch_raw_descriptor: GDT: index (f)1 > limit (0)
00079523319e[CPU  ] interrupt(): gate descriptor is not valid sys seg
00079523319e[CPU  ] interrupt(): gate descriptor is not valid sys seg
00079523319i[CPU  ] protected mode
00079523319i[CPU  ] CS.d_b = 32 bit
00079523319i[CPU  ] SS.d_b = 32 bit
00079523319i[CPU  ] | EAX=2badb002  EBX=0002cc80  ECX=00103000  EDX=00000001
00079523319i[CPU  ] | ESP=00102fcc  EBP=00067ebc  ESI=000538f4  EDI=000538f5
00079523319i[CPU  ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af pf cf
00079523319i[CPU  ] | SEG selector     base    limit G D
00079523319i[CPU  ] | SEG sltr(index|ti|rpl)     base    limit G D
00079523319i[CPU  ] |  CS:0008( 0001| 0|  0) 00000000 000fffff 1 1
00079523319i[CPU  ] |  DS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00079523319i[CPU  ] |  SS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00079523319i[CPU  ] |  ES:0010( 0002| 0|  0) 00000000 000fffff 1 1
00079523319i[CPU  ] |  FS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00079523319i[CPU  ] |  GS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00079523319i[CPU  ] | EIP=00100032 (00100032)
00079523319i[CPU  ] | CR0=0x00000011 CR1=0 CR2=0x00000000
00079523319i[CPU  ] | CR3=0x00000000 CR4=0x00000000
00079523319i[CPU  ] >> jmp far 0008:00100039 : EA390010000800
00079523319p[CPU  ] >>PANIC<< exception(): 3rd (13) exception with no resolution
The output from Bochs when I use the original gdt_flush looks similar, but hoses at the mov ds, ax.

Code: Select all

00074203321e[CPU  ] fetch_raw_descriptor: GDT: index (17)2 > limit (0)
00074203321e[CPU  ] interrupt(): gate descriptor is not valid sys seg
00074203321e[CPU  ] interrupt(): gate descriptor is not valid sys seg
00074203321i[CPU  ] protected mode
00074203321i[CPU  ] CS.d_b = 32 bit
00074203321i[CPU  ] SS.d_b = 32 bit
00074203321i[CPU  ] | EAX=2bad0010  EBX=0002cc80  ECX=00103000  EDX=00000001
00074203321i[CPU  ] | ESP=00102fcc  EBP=00067ebc  ESI=000538f4  EDI=000538f5
00074203321i[CPU  ] | IOPL=0 id vip vif ac vm RF nt of df if tf sf zf af pf cf
00074203321i[CPU  ] | SEG selector     base    limit G D
00074203321i[CPU  ] | SEG sltr(index|ti|rpl)     base    limit G D
00074203321i[CPU  ] |  CS:0008( 0001| 0|  0) 00000000 000fffff 1 1
00074203321i[CPU  ] |  DS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00074203321i[CPU  ] |  SS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00074203321i[CPU  ] |  ES:0010( 0002| 0|  0) 00000000 000fffff 1 1
00074203321i[CPU  ] |  FS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00074203321i[CPU  ] |  GS:0010( 0002| 0|  0) 00000000 000fffff 1 1
00074203321i[CPU  ] | EIP=00100036 (00100036)
00074203321i[CPU  ] | CR0=0x00000011 CR1=0 CR2=0x00000000
00074203321i[CPU  ] | CR3=0x00000000 CR4=0x00000000
00074203321i[CPU  ] >> mov ds, ax : 8ED8
00074203321p[CPU  ] >>PANIC<< exception(): 3rd (13) exception with no resolution
The first lines look suspicious, but I can also see that DS, SS, ES, DS, and GS all look to have 0x10 mov'd to them (which likely happened earlier in the run). :cry: FWIW: I'm using GRUB and I know that GRUB sends us to protected mode and sets up a GDT (of sorts) and that a triple-fault will occur if you step on that one. Could it be that this is what's occuring in my code?

1000 thanks!
Attachments
kernel-0.1.tar.gz
(5.7 KiB) Downloaded 356 times
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

I couldn't get your code to triplefault like you did. It triplefaulted on the divide-by-zero you put in in kernel.c. With that removed, it worked fine (see screenshot below).
Attachments
snapshot12.png
snapshot12.png (51.08 KiB) Viewed 4697 times
User avatar
KrnlHckr
Member
Member
Posts: 36
Joined: Tue Jul 17, 2007 9:16 am
Location: Washington, DC Metro Area
Contact:

Post by KrnlHckr »

JamesM wrote:I couldn't get your code to triplefault like you did. It triplefaulted on the divide-by-zero you put in in kernel.c. With that removed, it worked fine (see screenshot below).
Brilliant, I'm on my way to making a Microsoft styled OS (works on some, BSODs on others). Based on your results, I could continue with coding up my IDT, but what gives on my attempts? :(

I guess that this might be related to my dev environment? I'm using Fedora 7 (upgraded from FC6) and it's got gcc-4.1.2-13.fc6, libgcc-4.1.2-13.fc6, and binutils-2.17.50.0.12-4. NASM is 0.99.01 and GRUB 0.97. VMware server is 1.0.3. Bochs is compiled from source using:

Code: Select all

./configure --prefix=/usr/local --with-x11 --enable-debugger --enable-disasm --enable-magic-breakpoints --enable-vbe
Could it be GRUB?
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

I doubt it's the toolchain or GRUB. I noticed that you don't disable interrupts anywhere. That could be the problem.

If it's not, feel free to PM me and I can email you the floppy image I made (just copied your kernel.bin into my standard image with grub) and the kernel.bin file (and any obj files you may want to diff).

JamesM
User avatar
KrnlHckr
Member
Member
Posts: 36
Joined: Tue Jul 17, 2007 9:16 am
Location: Washington, DC Metro Area
Contact:

Post by KrnlHckr »

JamesM wrote:I doubt it's the toolchain or GRUB. I noticed that you don't disable interrupts anywhere. That could be the problem.
This is what I've changed the asm code for gdt to (added cli and sti to beginning and end):

Code: Select all

        ;; the gdt (global descriptor table)
        [global gdt_flush]      ; allow c code to link this
        [extern gp]             ; gp is in gdt.c
gdt_flush:
        cli
        lgdt    [gp]
        mov     ax, 0x10        ; 0x10 is offset in gdt to ds
        mov     ds, ax
        mov     es, ax
        mov     fs, ax
        mov     gs, ax
        mov     ss, ax
        ;ret      ; EDIT: that ret isn't supposed to be there - DOH!
        jmp     0x08:gdt_flush2 ; 0x08 is offset to code seg - far jmp
gdt_flush2:
        sti
        ret                     ; return back to c code
Same results. I remember reading somewhere (I think in Bran's headers) that GRUB leaves us with interrupts disabled. PM on the way! :)
Last edited by KrnlHckr on Thu Jul 26, 2007 6:47 pm, edited 1 time in total.
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

No, you must load the segment registers after the 'jmp 0x08:gdt_flush2'. This basically flushes out all the old segment data and loads the data from the GDT. If it doesn't work, something is wrong with your GDT.

In this case, it's obvious what the problem is (I hope). Somehow your GDT pointer (gp) is not being setup properly and your GDT is being created with a limit of 0 (which means you have no possible way of having data in it).
User avatar
JamesM
Member
Member
Posts: 2935
Joined: Tue Jul 10, 2007 5:27 am
Location: York, United Kingdom
Contact:

Post by JamesM »

pcmattman:

No, that is incorrect. You don't *have* to load the segment registers after the far jump. I do it that way and have no troubles. I also think (IIRC) that bran's tutorial does it that way.

Whatever the error is it's transient, because I cannot reproduce it on my machine. I got his PM and will mail him the code when i get home. I'll also run it on QEMU instead of bochs and see what I can see.

JamesM
User avatar
KrnlHckr
Member
Member
Posts: 36
Joined: Tue Jul 17, 2007 9:16 am
Location: Washington, DC Metro Area
Contact:

Bochs 'info cpu'

Post by KrnlHckr »

Code: Select all

<bochs:2> info cpu
eax:0x00100010, ebx:0x0002cc80, ecx:0x00000001, edx:0x00000001
ebp:0x00102ff8, esp:0x00102fcc, esi:0x000538f4, edi:0x000538f5
eip:0x00100037, eflags:0x00010002, inhibit_mask:0
cs:s=0x0008, dl=0x0000ffff, dh=0x00cf9a00, valid=1
ss:s=0x0010, dl=0x0000ffff, dh=0x00cf9300, valid=7
ds:s=0x0010, dl=0x0000ffff, dh=0x00cf9300, valid=7
es:s=0x0010, dl=0x0000ffff, dh=0x00cf9300, valid=1
fs:s=0x0010, dl=0x0000ffff, dh=0x00cf9300, valid=1
gs:s=0x0010, dl=0x0000ffff, dh=0x00cf9300, valid=1
ldtr:s=0x0000, dl=0x0000ffff, dh=0x00008200, valid=1
tr:s=0x0000, dl=0x0000ffff, dh=0x00008300, valid=1
gdtr:base=0x00000000, limit=0x0
idtr:base=0x00000000, limit=0xffff
dr0:0x00000000, dr1:0x00000000, dr2:0x00000000
dr3:0x00000000, dr6:0xffff0ff0, dr7:0x00000400
cr0:0x00000011, cr1:0x00000000, cr2:0x00000000
cr3:0x00000000, cr4:0x00000000
If I'm reading this correct, the GDT does have a limit of 0x0, even though I pass 0xFFFFFFFF in the function call to gdt_set_gate().

I wrote some lines of C to test the bitwise shifting and masking found in gdt_set_gate() It appears to do what I want it to do (unless I dorked this up too). Still poking around with this to test some ideas...

Code: Select all

$ ./ostest 
gp.limit = 0X23
gp.base  = 0x40
gdt[1].base_lo = 0
gdt[1].base_mid = 0
gdt[1].base_hi = 0
gdt[1].limit_lo = 0XFFFF
gdt[1].granularity = 0XF
gdt[1].granularity = 0XC0
gdt[1].access = 0X9A

Code: Select all

$ cat ostest.c 
#include <stdio.h>

#define uint32 long
#define uint16 unsigned int
#define uint8  unsigned char

struct gdt_entry
{
        uint16 limit_lo;
        uint16 base_lo;
        uint8  base_mid;
        uint8  access;
        uint8  granularity;
        uint8  base_hi;
} __attribute__ ((__packed__));

struct gdt_ptr
{
        uint16 limit;
        uint8  base;
} __attribute__ ((__packed__));

struct gdt_entry gdt[3];
struct gdt_ptr   gp;

int main(int argc, char *argv[])
{
        int    num    = 1;
        uint32 base   = 0;
        uint32 limit  = 0xFFFFFFFF;
        uint8  access = 0x9A;
        uint8  gran   = 0xCF;

        gp.limit = (sizeof(struct gdt_entry) * 3) - 1;
        gp.base  = (uint16)&gdt;

        printf("gp.limit = %#X\n", gp.limit);
        printf("gp.base  = %p\n", gp.base);

        gdt[num].base_lo  = (base & 0xFFFF);
        gdt[num].base_mid = (base >> 16) & 0xFF;
        gdt[num].base_hi  = (base >> 24) & 0xFF;

        gdt[num].limit_lo    = (limit & 0xFFFF);
        gdt[num].granularity = ( (limit >> 16) & 0x0F);

        printf("gdt[%d].base_lo = %#X\n", num, gdt[num].base_lo);
        printf("gdt[%d].base_mid = %#X\n", num, gdt[num].base_mid);
        printf("gdt[%d].base_hi = %#X\n", num, gdt[num].base_hi);

        printf("gdt[%d].limit_lo = %#X\n", num, gdt[num].limit_lo);
        printf("gdt[%d].granularity = %#X\n", num, gdt[num].granularity);

        gdt[num].granularity = (gran & 0xF0);
        gdt[num].access      = access;

        printf("gdt[%d].granularity = %#X\n", num, gdt[num].granularity);
        printf("gdt[%d].access = %#X\n", num, gdt[num].access);

        return 0;
}
Still hunting though, and I'll post any changes in the situation. Thanks JamesM and pcmattman for your insights. I'm all ears for other suggestions or ideas! :)
User avatar
KrnlHckr
Member
Member
Posts: 36
Joined: Tue Jul 17, 2007 9:16 am
Location: Washington, DC Metro Area
Contact:

Post by KrnlHckr »

pcmattman: I took a stroll through MatisseOS's code (BTW, good job), and I can see the rough outline of the tutorial. I noticed that despite the customization you've added to the skeleton of Bran's tutorial, my test kernel still follows the same path.

Since your OS image works on my Bochs install, I'm going to desk check your code against mine and see if anything obvious stands out. Still, the fact that JamesM can run my kernel image and my system won't befuddles me. My eyes are crossed from staring at the code so long.

Something good has come of this though --- my emacs-fu is getting better! :)
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

JamesM wrote:No, that is incorrect. You don't *have* to load the segment registers after the far jump. I do it that way and have no troubles. I also think (IIRC) that bran's tutorial does it that way.
Loading them afterwards is the best way of knowing for sure that the GDT is loaded and working properly. It also means you have the correct code segment setup already when you start loading the data segments.

@KrnlHckr:

Two things. First of all, the data I see here is wrong:

Code: Select all

gdtr:base=0x00000000, limit=0x0 
No base, no limit. Something is wrong with your 'gp' struct.

Second of all, the limits on of your GDT entries are wrong.

I suggest rewriting all the GDT code to match (exactly) Bran's code. At least it's been tested. If you still have trouble, then there's something else happening that we can help with.
Ninjarider
Member
Member
Posts: 62
Joined: Fri Jun 29, 2007 8:36 pm

Post by Ninjarider »

are you trying to enter 32-bit protected mode, or just switch the 32-bit


[edit]
from what i see you'll are entering protected mode. the jump has to be right after cr0 is set. according to the intel manuals doing otherwise can cause a broad range of issues. not sure what to look for exactly in c code but i dont even see were the cr0 is being set. but that might be why it works on some systems and not others.
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

Ninjarider wrote:from what i see you'll are entering protected mode. the jump has to be right after cr0 is set. according to the intel manuals doing otherwise can cause a broad range of issues. not sure what to look for exactly in c code but i dont even see were the cr0 is being set. but that might be why it works on some systems and not others.
He already is in protected mode (can't you see the CR0 register in the bochs log?).
Ninjarider
Member
Member
Posts: 62
Joined: Fri Jun 29, 2007 8:36 pm

Post by Ninjarider »

i saw that but didn't know if that was before or after the code was ran.
that being the case it is probably the values of the gdt.

can't tell by looking at the cr? values but is paging enabled?
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

If paging was enabled CR0 would be 0x80000011 (iirc).
User avatar
KrnlHckr
Member
Member
Posts: 36
Joined: Tue Jul 17, 2007 9:16 am
Location: Washington, DC Metro Area
Contact:

Post by KrnlHckr »

Lots of stuff to chew on! I'm going to poke around in gdt.c and desk check it against the original Bran code as pcmattman suggested. Weary eyes looking over the same code going on 1+ weeks so far ... I'm sure it's me doing something (anything) stupid.

Ya'll are great! I'll post any success (along with results from JamesM's findings diff'ed against mine).

Side note: I'm setting up CVS. Working on the only copies of these code files is getting dangerous really quickly.

:shock:
Post Reply