0

I am trying to build my own kernel and am now setting up the GDT. I use an assembly file for the loader and calling the kernel that is written in C and am trying to get a GDT working. The kernel boots from GRUB and flushes the GDT setup by GRUB. When I have my GDT entries set up as segments (with the appropriate limits and offsets), I am assuming there is a triple fault of sorts as the kernel reboots, both in the QEMU as well as when I boot off a pendrive.

My questions are:

Can I implement a segmented model for the x86 architecture? Does it need to be done in protected mode? How do I get out of protected mode once the job is done?

I would post the code here but most of it is from tutorials anyway and if I mix up the assembly and the C code, it will get messy. The main thing is that if I do anything other than the base of both the Code Segment and Data Segment entries in the C kernel as '0', the kernel just reboots. Moreover, the same thing happens when I set the granularity to disable 4KB paging. Please ask for more details, if needed. Thanks :) :)

Edit: Here is the linker.ld file that I use for linking the asm bootloader and the C kernel file. I have posted the segments from both the asm and the C file that are relevant to the memory segmentation:

Linker:

ENTRY (loader)

SECTIONS
{
    . = 0x00100000;

    .text ALIGN (0x1000) :
    {
        *(.text)
    }

    .rodata ALIGN (0x1000) :
    {
        *(.rodata*)
    }

    .data ALIGN (0x1000) :
    {
        *(.data)
    }

    .bss :
    {
        sbss = .;
        *(COMMON)
        *(.bss)
        ebss = .;
    }
}

C function to set the GDT and the instantiating functions:

void 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 gdt_install()
{
    /* Setup the GDT pointer and limit */
    gp.limit = (sizeof(struct gdt_entry) * 35) - 1;
    gp.base = &gdt;

    /* Our NULL descriptor */
    gdt_set_gate(0, 0, 0, 0, 0);

   gdt_set_gate(1, 0x00000000, 0xFFFFFFFF, 0x9A, 0xCF); //Setting Code Segment


  gdt_set_gate(2, 0x00000000, 0xFFFFFFFF, 0x92, 0xCF); //Setting Data Segment

  //In the above two, if the second parameter is anything other 
  //than 0 i.e. base is not 0, the kernel doesn't run. 
 //Moreover, setting the last to 0x4F, which is byte accessing rather 
 //than 4KB paging gives the same malfunction too. 

 //gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
  //gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);     
    //TASK STATE SEGMENT -1 
    //TASK STATE SEGMENT -2 

    //gdt_set_gate(3, 0, 0xFFFFFFFF, 0x89, 0xCF);
   // gdt_set_gate(4, 0, 0xFFFFFFFF, 0x89, 0xCF);   









    /* Flush out the old GDT and install the new changes! */
    gdt_flush();
}

Finally, the GDT flush function that is written in ASM:

gdt_flush:    
    lgdt [gp]        ; Load the GDT with our '_gp' which is a special pointer   
    ;ltr [0x18]
    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

I know that the C function and the ASM one are correct because they work with the flat memory model. Is there any specific change in this that I need to make and I would like some advise about the linker file to set up segmentation or segmented paging.

plaknas
  • 274
  • 4
  • 15
  • You shouldn't be using segmentation. You should be using paging. Segmentation is ancient and will not be supported on newer Intel processors. Just use the standard of paging. – Linuxios Feb 02 '13 at 14:37
  • @Linuxios I wouldn't be so sure. Even in 64-bit mode you still use `GS` to organize access to Thread-Local Storage (TLS). – Alexey Frunze Feb 04 '13 at 08:47
  • Let's look at an example. Suppose `gdt_install()` is at offset 0x123456 and is runnable when you start with CS.base = 0. In this case the CPU fetches the first instruction of `gdt_install()` from physical address 0x123456 (base + offset). You then change CS.base to, say, 0x10000. In this case the CPU would still need to fetch the first instruction from physical address 0x123456 (because that's where the code is in the memory, you didn't move it!), but it is instead going to fetch it from physical address 0x133456. Can you see the problem? – Alexey Frunze Feb 04 '13 at 08:53
  • Yeah, I see the problem. I need to get the loader to move the entire function or at least the part after the first call to gdt_install() to the Code Segment offset. My question is, is there any role the linker plays in all this? – plaknas Feb 04 '13 at 11:09
  • @alexey: true. But you should still be using paging for everything else. – Linuxios Feb 04 '13 at 14:24
  • @plaknas The linker controls offset generation and that's important since x86 code is not position-independent generally (many instructions will have offsets directly encoded inside of them). – Alexey Frunze Feb 04 '13 at 14:44
  • @Linuxios Maybe. But until segments are fully eradicated (at the expense of backward compatibility), you still have to deal with them. – Alexey Frunze Feb 04 '13 at 14:46
  • @AlexeyFrunze: True, but Intel has plans to do just that, very soon. – Linuxios Feb 04 '13 at 14:47
  • @Linuxios They have had plans for that for some 10 years if not more. They also didn't want to support real mode in VMs and at first didn't, but then put the support in. Anyway, we aren't there yet and this isn't very relevant to the original question. – Alexey Frunze Feb 04 '13 at 14:52

2 Answers2

1

Can I implement a segmented model for the x86 architecture?

Of course, you can.

Does it need to be done in protected mode?

You only have two choices: real mode with its awkwardness and 64K and other limits and protected mode.

How do I get out of protected mode once the job is done?

In a few words, you switch off page translation (if it's on), jump to a 16-bit code segment, load segment registers with selectors pointing to descriptors compatible with real mode, clear CR0.PE. There are many more details (e.g. task switching, interrupt handling). You can find them in the official documentation and some tutorials online.

if I do anything other than the base of both the Code Segment and Data Segment entries in the C kernel as '0', the kernel just reboots.

You must understand that x86 code is not position independent and will not function correctly if it's relocated to address X, but is loaded to address Y. That means that not only the segment descriptors' bases should be adjusted in order to move your code/data somewhere, but also the linker should relocate the executable image to another location. A single simple mistake is enough for this not to work.

Moreover, the same thing happens when I set the granularity to disable 4KB paging.

That statement doesn't make any sense. You do not disable paging by manipulating with the descriptor's granularity bit.

Please ask for more details, if needed.

It's your job to provide enough info for us to help you out. The question, as it is currently stated, lacks detail to be answered fully. You've got some buggy code, but you aren't showing it to us, how can we help?

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • I have added the appropriate code. I would be grateful if you could go through it once again and let me know. Thanks :) :) – plaknas Feb 04 '13 at 08:14
0

Use this in your asm code after loading the GDT.

mov eax, cr0
or eax, 1b
mov cr0, eax
mridul_verma
  • 265
  • 1
  • 10