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.