custom OS chrashes while loading GDT

132 views Asked by At

I am building a simple OS in c++ to learn about the topic. I am using the limine bootloader (Limine 6.20231210.0, as stated by the bootloader version request). One of the first things i am doing in the main kernel function, which is the following:

    extern "C" void _start(void) {
        // Ensure the bootloader actually understands our base revision (see spec) and fetch first fb
        if(LIMINE_BASE_REVISION_SUPPORTED == false) {
            hcf();
        }
        struct limine_framebuffer* framebuffer = framebuffer_request.response->framebuffers[0];
        if(framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) {
            hcf();
        }

        KERNEL::VGA::VGA_Driver           vgaDriver(framebuffer);
        KERNEL::TERMINAL::kernel_terminal terminal(&terfont7x14, &vgaDriver);
        terminal.printf("%s %s", bootloader_info_request.response->name,    bootloader_info_request.response->version);
        gdt_init();
        hcf();
    }

Is load the GDT. As you see, the gdt_init() function (code below) is called. The vgaDriver and terminal Objects are simple objects that allow me to write to the screen (they use the limine framebuffer). Also the declaration of gdt_init() inside the .h is marked ar extern "C", so it cant be about function names, the compiler would have given me an error

bits 64

align 0x10
gdt:
null_descriptor:
    dw 0x0000
    dw 0x0000
    db 0x00
    db 00000000b
    db 00000000b
    db 0x00

kernel_code_64:
    dw 0x0000
    dw 0x0000
    db 0x00
    db 10011010b
    db 00100000b
    db 0x00

kernel_data_64:
    dw 0x0000
    dw 0x0000
    db 0x00
    db 10010010b
    db 00100000b
    db 0x00

user_code_64:
    dw 0x0000
    dw 0x0000
    db 0x00
    db 11111010b
    db 00100000b
    db 0x00

user_data_64:
    dw 0x0000
    dw 0x0000
    db 0x00
    db 11110010b
    db 00100000b
    db 0x00

gdt_end:

gdt_ptr:
dw gdt_end - gdt - 1
dq gdt

CODE_SEG equ kernel_code_64 - gdt
DATA_SEG equ kernel_data_64 - gdt

global gdt_init
gdt_init:
    lgdt [gdt_ptr]
    mov rax, rsp
    push DATA_SEG
    push rax
    pushfq
    push CODE_SEG
    push flush
    iretq
    flush:
        mov ax, DATA_SEG
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov ss, ax
        ret

I even tried different version of the gdt_load(), like the following:

lgdt [rdi]  ; i was passing the parameters by function here
push 0x8   ; the code segment offset
lea rax, [test]  ; i declare the test label later in the code, after crash point
push rax
iretq
; fails after iretq

But the code always fails at the iretq instruction. I tried debugging with gdb but the registers seem all fine as of my knowledge. The gdt entries are always like the ones shown in the first code example, even in the c++ implementation, so it cant be that (unless the entries themselves are the problem). The platform i am trying to build the OS for is x86_64, and here is my linker.ld file:

/* Tell the linker that we want an x86_64 ELF64 output file */
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
 
/* We want the symbol _start to be our entry point */
ENTRY(_start)
 
/* Define the program headers we want so the bootloader gives us the right */
/* MMU permissions */
PHDRS
{
    text    PT_LOAD    FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */
    rodata  PT_LOAD    FLAGS((1 << 2)) ;            /* Read only */
    data    PT_LOAD    FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */
    dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic PHDR for relocations */
}
 
SECTIONS
{
    /* We wanna be placed in the topmost 2GiB of the address space, for optimisations */
    /* and because that is what the Limine spec mandates. */
    /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
    /* that is the beginning of the region. */
    . = 0xffffffff80000000;
 
    .text : {
        *(.text .text.*)
    } :text
 
    /* Move to the next memory page for .rodata */
    . += CONSTANT(MAXPAGESIZE);
 
    .rodata : {
        *(.rodata .rodata.*)
    } :rodata
 
    /* Move to the next memory page for .data */
    . += CONSTANT(MAXPAGESIZE);
 
    .data : {
        *(.data .data.*)
    } :data
 
    /* Dynamic section for relocations, both in its own PHDR and inside data PHDR */
    .dynamic : {
        *(.dynamic)
    } :data :dynamic
 
    /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
    /* unnecessary zeros will be written to the binary. */
    /* If you need, for example, .init_array and .fini_array, those should be placed */
    /* above this. */
    .bss : {
        *(.bss .bss.*)
        *(COMMON)
    } :data
 
    /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */
    /DISCARD/ : {
        *(.eh_frame)
        *(.note .note.*)
        *(.comment)
    }
}

As you can see i based my code mostly on the limine bare-bones on osdev

Here are also my build command (replace .c and .o name)

g++ -o build/memory.o -c -std=c++17 -Wall -Wno-missing-field-initializers -Wextra -Wno-switch-bool -fno-rtti -fno-stack-protector -fno-stack-check -fno-lto -m64 -march=x86-64 -g src/memory.cpp
nasm -f elf64 -o build/x86.o src/x86.asm
ld -o build/kernel -m elf_x86_64 -static --no-dynamic-linker -T src/linker.ld build/kernel.o build/memory.o build/ports.o build/terminal.o build/vga.o build/gdt.o build/x86.o

The code seems to be able to load the address of GDTD in the register, because I once tried to remove the segments update and only load the gdt and it worked, and i could get the address by using sgdt. The returned address could be cast to a valid GDT structure (same implementation as in the assembly example but in c++)

This is the disassembly of the gdt_init() function (up until iretq)

│    0xffffffff80001608 <kernel_code_64>     add    BYTE PTR [rax],al                              │
│    0xffffffff8000160a <kernel_code_64+2>   add    BYTE PTR [rax],al                              │
│    0xffffffff8000160c <kernel_code_64+4>   add    BYTE PTR [rdx+0x20],bl                         │
│    0xffffffff80001612 <kernel_data_64+2>   add    BYTE PTR [rax],al                              │
│    0xffffffff80001614 <kernel_data_64+4>   add    BYTE PTR [rdx+0x20],dl                         │
│    0xffffffff8000161a <user_code_64+2>     add    BYTE PTR [rax],al                              │
│    0xffffffff8000161c <user_code_64+4>     add    dl,bh                                          │
│    0xffffffff8000161e <user_code_64+6>     and    BYTE PTR [rax],al                              │
│    0xffffffff80001620 <user_data_64>       add    BYTE PTR [rax],al                              │
│    0xffffffff80001622 <user_data_64+2>     add    BYTE PTR [rax],al                              │
│    0xffffffff80001624 <user_data_64+4>     add    dl,dh                                          │
│    0xffffffff80001626 <user_data_64+6>     and    BYTE PTR [rax],al                              │
│    0xffffffff80001628 <gdt_ptr>            (bad)                                                 │
│    0xffffffff80001629 <gdt_ptr+1>          add    BYTE PTR [rax],al                              │
│    0xffffffff8000162b <gdt_ptr+3>          (bad)                                                 │
│    0xffffffff8000162c <gdt_ptr+4>          add    BYTE PTR [rax-0x1],al                          │
│  > 0xffffffff80001632 <gdt_init>           lgdt   ds:0xffffffff80001628                          │
│    0xffffffff8000163a <gdt_init+8>         mov    rax,rsp                                        │
│    0xffffffff8000163d <gdt_init+11>        push   0x10                                           │
│    0xffffffff8000163f <gdt_init+13>        push   rax                                            │
│    0xffffffff80001640 <gdt_init+14>        pushf                                                 │
│    0xffffffff80001641 <gdt_init+15>        push   0x8                                            │
│    0xffffffff80001643 <gdt_init+17>        push   0xffffffff8000164a                             │
│    0xffffffff80001648 <gdt_init+22>        iretq

These are the registers state right before the iretq instruction:

rax            0xffff800007e9ff48  -140737355579576
rbx            0x0                 0
rcx            0xffff8000fd000000  -140733243719680
rdx            0x0                 0
rsi            0x0                 0
rdi            0xffff800007e9ffb0  -140737355579472
rbp            0xffff800007e9fff0  0xffff800007e9fff0
rsp            0xffff800007e9ff28  0xffff800007e9ff28
r8             0x0                 0
r9             0x0                 0
r10            0x84                132
r11            0xd                 13
r12            0x0                 0
r13            0x0                 0
r15            0x0                 0
rip            0xffffffff80001643  0xffffffff80001643 <gdt_init+17>
eflags         0x46                [ IOPL=0 ZF PF ]
cs             0x28                40
ss             0x30                48
ds             0x30                48
es             0x30                48
fs             0x30                48
gs             0x30                48
fs_base        0x0                 0
gs_base        0x0                 0
k_gs_base      0x0                 0
cr0            0x80010011          [ PG WP ET PE ]
cr3            0x7e8f000           [ PDBR=32399 PCID=0 ]
cr4            0x20                [ PAE ]
cr8            0x0                 0
efer           0xd00               [ NXE LMA LME ]
mxcsr          0x1f80              [ IM DM ZM OM UM PM ]

Note that as you can see the segment registers are already set to some reasonable value, thats just because Limine loads a default gdt with some entries. I want to load my own though.

0

There are 0 answers