OSDev.org

The Place to Start for Operating System Developers
It is currently Tue Apr 23, 2024 7:21 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: GDT generates Triple Fault
PostPosted: Sat Nov 17, 2018 3:11 pm 
Offline

Joined: Sat Nov 17, 2018 2:47 pm
Posts: 11
Hello, this is my first topic at this forum so forgive me if I make a mistake.

I started developing a kernel to learn more about how OSes work, when I realized, I was implementing the GDT to leave real mode and enter protected mode. For my surprise, when I had my kernel built and running on QEMU I was able to see how the kernel generates a triple fault and how it is rebooting endlessly.
Before you ask, all my structures are properly packed.

My loader.asm:

Code:
bits 32
section   .text
   align 4
   dd 0x1BADB002
   dd 0x00
   dd - (0x1BADB002+0X00)

global start
extern kmain
start:
   cli
   call kmain
   hlt


My kernel.c:

Code:
#include "include/headers/screen.h"
#include "include/headers/string.h"
#include "include/headers/keyboard.h"
#include "include/headers/system.h"
#include "include/headers/idt.h"
#include "include/headers/interrupts.h"
#include "include/headers/gdt.h"
#include "include/headers/shell.h"
#include "include/headers/memory.h"
#include "tests.h"


void run_tests(){
   //MEMORY TESTS
   //test_memcccpy();
   test_memchr();
   //test_memcmove();
   //test_memcmp();
   test_memcpy();
   //test_memset();
   //STRING TESTS
}


kmain() {
    clearScreen();
    print("Running lib tests....");
    run_tests();
    gdt_init();
    print("Global Descriptor Table initialized\n");
    shell_main();

    outportb(0xf4, 0x00);
}


My gdt.h:

Code:
#ifndef OSFROMSCRATCH_GDT_H
#define OSFROMSCRATCH_GDT_H

#include "types.h"

#define KERNEL_CS 0X08
#define KERNEL_DS 0x10
#define GDT_ENTRIES 4


typedef struct {
    //pointer is 32 bits long
    //limit is 20 bits long
    uint16 limit_low;//16 bits
    uint16 pointer_low;//16 bits low pointer
    uint8 pointer_middle;//1byte high of low pointer
    uint8 access;//1 byte of access or type
    uint8 granularity;//high 4 bits for flags, low 4 bits for limit
    uint8 pointer_high;//1 byte left for the pointer
}__attribute__((packed)) gdt_entry_t;

/**
* Pointer with limit and base
*/
typedef struct{
    uint16 limit;
    uint32 base;
}__attribute__((packed)) gdt_limit_ptr_t;

//0-> null entry
//1-> reserved entry
//2-> CS entry
//3-> DS entry
//4-> TSS entry
//5-> USER entry
//6-> LDT entry
//7-> second TSS entry(only if we need it)
//8,9-> reserved for future entries
gdt_entry_t gdt_entry[GDT_ENTRIES];
gdt_limit_ptr_t gdt_limit_ptr;


/**
* Function to flush or init
*/
void gdt_set();

/**
* It prepares the structs for being used
*/
void gdt_init();

void gdt_set_gate(int n, uint32 base,uint32 limit, uint8 access, uint8 granularity);

//not implemented yet
//TODO Mean to return the actual offset of DS and CS
uint16 code_segment_offset();
uint16 data_segment_offset();

#endif //OSFROMSCRATCH_GDT_H


Code:
void gdt_init(){
    //see gdt.h for index
    gdt_limit_ptr.limit =(sizeof(gdt_entry_t)*GDT_ENTRIES)-1;
    gdt_limit_ptr.base=(uint32)&gdt_entry;

    //null entry
    gdt_set_gate(0,0,0,0,0);

    //reserved entry
    gdt_set_gate(1,0,0,0,0);

    //code segment entry
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x9A, 0xCF);

    //data segment entry
    gdt_set_gate(3, 0, 0xFFFFFFFF, 0x92, 0xCF);

    //tss entry is called from another file

    //user entry


      
    gdt_set();
   
}

void gdt_set_gate(int n, uint32 base,uint32 limit, uint8 access, uint8 granularity){


    gdt_entry[n].pointer_low = (base & 0xFFFF);
    gdt_entry[n].pointer_middle = (base >> 16) & 0xFF;
    gdt_entry[n].pointer_high = (base >>24) & 0xFF;

    gdt_entry[n].limit_low = (limit & 0xFFFF);
    gdt_entry[n].granularity=((limit >> 16)&0x0F);

    gdt_entry[n].granularity |= (granularity & 0xF0);
    gdt_entry[n].access = access;//type

}

void gdt_set(){
   
__asm__ __volatile__("lgdtl (gdt_limit_ptr)");
   __asm__ __volatile__(
      "cli\n"
      "movw $0x10, %ax \n"
      "movw %ax, %ds \n"
      "movw %ax, %es \n"
      "movw %ax, %fs \n"
      "movw %ax, %gs \n"
      "movw %ax, %ss \n"
      "ljmp $0x08, $next \n"
      "next:          \n"
   );
}


I'm not a native english speaker, sorry if there are mistakes. Thank all of you in advance.


Top
 Profile  
 
 Post subject: Re: GDT generates Triple Fault
PostPosted: Sat Nov 17, 2018 3:19 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1604
You aren't loading the GDT. You need an LGDT instruction before you can do the segment initialization.
EDIT: Oops, sorry, misread that. But your initialization and the assembly snippet at the end don't fit. The snippet at the end assumes that the third descriptor is data and the second one is code, but you initialized these into slots 4 and 3 respectively.

_________________
Carpe diem!


Top
 Profile  
 
 Post subject: Re: GDT generates Triple Fault
PostPosted: Sat Nov 17, 2018 3:40 pm 
Offline
Member
Member
User avatar

Joined: Mon Jan 15, 2018 2:27 pm
Posts: 201
Yep, LGDT is needed. And it seems that you are trying to load code segment descriptor into data segment registers (selector 0x10) and, what you call reserved entry, into CS register (selector 0x08) when doing ljmp.
BTW. Multiboot compatible loaders (like GRUB or QEMU internal loader) put your machine in 32 bit protected mode (but it is advised to load known GDT, reload segments registers and initialize your own stack as soon as possible). Details here: https://www.gnu.org/software/grub/manua ... state.html


Top
 Profile  
 
 Post subject: Re: GDT generates Triple Fault
PostPosted: Sat Nov 17, 2018 5:04 pm 
Offline

Joined: Sat Nov 17, 2018 2:47 pm
Posts: 11
nullplan wrote:
You aren't loading the GDT. You need an LGDT instruction before you can do the segment initialization.
EDIT: Oops, sorry, misread that. But your initialization and the assembly snippet at the end don't fit. The snippet at the end assumes that the third descriptor is data and the second one is code, but you initialized these into slots 4 and 3 respectively.


Thank very much, I forgot about to rewrite the asm lines at the end, now it's fixed :)


Top
 Profile  
 
 Post subject: Re: GDT generates Triple Fault
PostPosted: Sat Nov 17, 2018 11:56 pm 
Offline
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 693
This code has potential bugs especially if you start building with optimizations on:

Code:
void gdt_set(){
   
__asm__ __volatile__("lgdtl (gdt_limit_ptr)");
   __asm__ __volatile__(
      "cli\n"
      "movw $0x10, %ax \n"
      "movw %ax, %ds \n"
      "movw %ax, %es \n"
      "movw %ax, %fs \n"
      "movw %ax, %gs \n"
      "movw %ax, %ss \n"
      "ljmp $0x08, $next \n"
      "next:          \n"
   );
}
Because you have added an extra empty selector Your Code Segment should be 0x10 (instead of 0x08) and data segment should be 0x18 (instead of 0x10). The defines should be changed to:
Code:
#define KERNEL_CS 0x10
#define KERNEL_DS 0x18
Multiple basic ASM statements (even if volatile) that appear one after another are not guaranteed to be output in the same order. It is frowned upon and bad practice to access a global variable directly with inline. It should be passed as a parameter using extended inline assembly. You modify the register EAX without telling the compiler it has been modified. This may result in the compiler assuming the value in EAX is the same after the inline assembly as it was before. You could rewrite this by combining the two and pass gdt_limit_ptr as a constraint. You could do something like:
Code:
void gdt_set(){

   __asm__ __volatile__(
      "cli\n\t"
      "lgdtl %[gdtr]\n\t"
      "movw $0x18, %%ax\n\t"
      "movw %%ax, %%ds\n\t"
      "movw %%ax, %%es\n\t"
      "movw %%ax, %%fs\n\t"
      "movw %%ax, %%gs\n\t"
      "movw %%ax, %%ss\n\t"
      "ljmp $0x10, $next%=\n"
      "next%=:"
      : /* No output constraints */
      : [gdtr]"m"(gdt_limit_ptr)
      : "eax" /* clobber list */
   );
}
You could clean this up and use constraints to have the compiler choose a register and pass the values of the segments in doing something like:
Code:
void gdt_set(){

   __asm__ __volatile__(
      "cli\n\t"
      "lgdtl %[gdtr]\n\t"
      "mov %[dataseg], %%ds\n\t"
      "mov %[dataseg], %%es\n\t"
      "mov %[dataseg], %%fs\n\t"
      "mov %[dataseg], %%gs\n\t"
      "mov %[dataseg], %%ss\n\t"
      "ljmp %[codeseg], $next%=\n"
      "next%=:"
      : /* no output constraints */
      : [dataseg]"r"(KERNEL_DS), /* Pass DS via a general purpose register */
        [codeseg]"i"(KERNEL_CS), /* Pass CS via an immediate value so it can be used with ljmp */
        [gdtr]"m"(gdt_limit_ptr) /* Pass a memory reference to the gdt record */
   );
}
If you are new to inline assembly I recommend not using it and writing assembly code in an external assembly module. It is very easy to introduce subtle bugs with inline assembly if you don't do it correctly. Those bugs can be harder to nail down later.

PS: The %= I put on the end of next%= adds a unique number to the end of the label specific to the instance of the inline assembly generated. This avoids name collision with other labels you may wish to call next in other inline assembly.

A more generic function that allows for the parameters to be passed to gdt_set:
Code:
void gdt_set(gdt_limit_ptr_t *gdtr, uint16 codeseg, uint16 dataseg){

   __asm__ __volatile__(
      "cli\n\t"
      "lgdtl %[gdtr]\n\t"
      "mov %[dataseg], %%ds\n\t"
      "mov %[dataseg], %%es\n\t"
      "mov %[dataseg], %%fs\n\t"
      "mov %[dataseg], %%gs\n\t"
      "mov %[dataseg], %%ss\n\t"
      "push %k[codeseg]\n\t"
      "push $next%=\n\t"
      "ljmp *(%%esp)\n"
      "next%=:\n\t"
      "add $8, %%esp"
      : /* no output constraints */
      : [dataseg]"r"(dataseg),
        [codeseg]"ri"(codeseg),
        [gdtr]"m"(*gdtr)
   );
}


Last edited by MichaelPetch on Mon Nov 19, 2018 2:30 pm, edited 11 times in total.

Top
 Profile  
 
 Post subject: Re: GDT generates Triple Fault
PostPosted: Sun Nov 18, 2018 9:22 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 21, 2011 9:47 pm
Posts: 286
Location: Tustin, CA USA
Blackburn wrote:
Code:
    //null entry
    gdt_set_gate(0,0,0,0,0);

    //reserved entry
    gdt_set_gate(1,0,0,0,0);

    //code segment entry
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x9A, 0xCF);


// ... snip ...

void gdt_set(){
   
__asm__ __volatile__("lgdtl (gdt_limit_ptr)");
   __asm__ __volatile__(
      "cli\n"
      "movw $0x10, %ax \n"
      "movw %ax, %ds \n"
      "movw %ax, %es \n"
      "movw %ax, %fs \n"
      "movw %ax, %gs \n"
      "movw %ax, %ss \n"
      "ljmp $0x08, $next \n"           // <-- this is entry #1
      "next:          \n"
   );
}


You did notice that you are trying to jump to an effectively additional NULL segment selector?

_________________
Adam

The name is fitting: Century Hobby OS -- At this rate, it's gonna take me that long!
Read about my mistakes and missteps with this iteration: Journal

"Sometimes things just don't make sense until you figure them out." -- Phil Stahlheber


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: akasei, Bing [Bot], DotBot [Bot], Google [Bot] and 109 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group