Page 1 of 1

general protection fault when listing pages in 64 long mode

Posted: Sat Jun 08, 2024 11:07 am
by maxwildner
hi. I'm getting a general protection fault when trying to list a memory layout retrieved from the multiboot structure after enabling gdt and paging and jumping to 64 bit long protected mode. i don't know, if multiboot structure is properly, it should work. I created a custom kernel which jumps to 32 bit mode after enabling paging and gdt64, then i am enabling interrupts and after i tries according to this article ([wiki]Detecting_Memory_(x86)[/wiki]) list a memory map to proceed to writing a memory manager. The problem is, i am constantly getting general protection faults from interrupt handler when trying to simply list the pages.
Here is my code:
main.asm (entry point is here)

Code: Select all

; Start of bootloader code
bits 32
global start
extern long_mode_start

section .text
align 4

start:
    ; Set up stack
    mov esp, stack_top

    ; Perform checks
    call check_multiboot
    call check_cpuid
    call check_long_mode

    ; Set up paging and enable 64-bit mode
    call setup_page_tables
    call enable_paging

    ; Load GDT and jump to 64-bit code
    lgdt [gdt64_pointer]
    jmp 0x08:long_mode_start

    hlt

check_multiboot:
    cmp eax, 0x2BADB002  ; Multiboot 1 magic number
    jne .no_multiboot
    ret
.no_multiboot:
    mov al, "M"
    jmp error

check_cpuid:
    pushfd
    pop eax
    mov ecx, eax
    xor eax, 1 << 21
    push eax
    popfd
    pushfd
    pop eax
    push ecx
    popfd
    cmp eax, ecx
    je .no_cpuid
    ret
.no_cpuid:
    mov al, "C"
    jmp error

check_long_mode:
    mov eax, 0x80000000
    cpuid
    cmp eax, 0x80000001
    jb .no_long_mode

    mov eax, 0x80000001
    cpuid
    test edx, 1 << 29
    jz .no_long_mode
    
    ret
.no_long_mode:
    mov al, "L"
    jmp error

setup_page_tables:
    mov eax, page_table_l3
    or eax, 0b11 ; present, writable
    mov [page_table_l4], eax
    
    mov eax, page_table_l2
    or eax, 0b11 ; present, writable
    mov [page_table_l3], eax

    mov ecx, 0 ; counter
.loop:
    mov eax, 0x200000 ; 2MiB
    mul ecx
    or eax, 0b10000011 ; present, writable, huge page
    mov [page_table_l2 + ecx * 8], eax

    inc ecx ; increment counter
    cmp ecx, 512 ; checks if the whole table is mapped
    jne .loop ; if not, continue

    ret

enable_paging:
    ; Pass page table location to CPU
    mov eax, page_table_l4
    mov cr3, eax

    ; Enable PAE
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    ; Enable long mode
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    ; Enable paging
    mov eax, cr0
    or eax, 1 << 31
    mov cr0, eax

    ret

error:
    ; Print "ERR: X" where X is the error code
    mov dword [0xb8000], 0x4f524f45
    mov dword [0xb8004], 0x4f3a4f52
    mov dword [0xb8008], 0x4f204f20
    mov byte  [0xb800a], al
    hlt

section .bss
align 4096
page_table_l4:
    resb 4096
page_table_l3:
    resb 4096
page_table_l2:
    resb 4096
stack_bottom:
    resb 4096 * 4
stack_top:

section .rodata
align 8
gdt64:
    dq 0                   ; Null descriptor
    dq 0x00af9a000000ffff  ; Code segment descriptor
    dq 0x00af92000000ffff  ; Data segment descriptor

gdt64_pointer:
    dw gdt64_end - gdt64 - 1  ; Limit (size of GDT)
    dq gdt64                 ; Base address of GDT

gdt64_end:

section .multiboot_header
align 4

header_start:
    dd 0x1BADB002     ; Multiboot header magic number
    dd 0x00000000     ; Multiboot flags
    dd -(0x1BADB002)  ; Checksum

    ; Alignment tags (optional, helps alignment)
    align 8
header_end:

here i have main64.asm which is basically a loader for the 64 bit kernel and just prepares the data segment registers

Code: Select all

global long_mode_start
extern kernel_main

section .text
bits 64

long_mode_start:
    ; Load null into all data segment registers
    mov ax, 0
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; Set up stack (optional, stack setup can be done here if needed)
    ; mov rsp, <desired stack address>

    ; Call kernel_main with multiboot magic and mbi pointer
    mov rdi, 0x2BADB002    ; Multiboot 1 magic number
    mov rsi, [rbx + 8]      ; Multiboot information structure pointer
    call kernel_main

    hlt

i also have a header.asm which is just a multiboot header structure:

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;; MULTIBOOT
MBOOT_PAGE_ALIGN EQU 1 << 0
MBOOT_MEM_INFO EQU 1 << 1
MBOOT_USE_GFX EQU 0

MBOOT_MAGIC EQU 0x1BADB002
MBOOT_FLAGS EQU MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO | MBOOT_USE_GFX
MBOOT_CHECKSUM EQU -(MBOOT_MAGIC + MBOOT_FLAGS)
;;;;;;;;;;;;;;;;;;;;;;;; KERNEL STUFF

section .multiboot_header
ALIGN 4
    DD MBOOT_MAGIC
    DD MBOOT_FLAGS
    DD MBOOT_CHECKSUM
    DD 0, 0, 0, 0, 0

    DD 0
    DD 800
    DD 600
    DD 32
and here is the kernel itself (main.cpp)

Code: Select all

extern "C" void kernel_main(uint32_t magic, multiboot_info_t* mbd) {

    if (is_64bit_mode_enabled()) {
        printf("64-bit mode is enabled\n");
    } else {
        printf("64-bit mode is not enabled\n");
    }

    if (is_gdt_enabled()) {
        printf("GDT is enabled\n");
    } else {
        printf("GDT is not enabled\n");
    }

    if (is_paging_enabled()) {
        printf("Paging is enabled\n");
    } else {
        printf("Paging is not enabled\n");
    }

    if (is_interrupts_enabled()) {
        printf("Interrupts are enabled\n");
    } else {
        printf("Interrupts are not enabled\n");
    }

    init_idt();

    if (is_interrupts_enabled()) {
        printf("Interrupts are enabled\n");
    } else {
        printf("Interrupts are not enabled\n");
    }

    /* Check bit 6 to see if we have a valid memory map */
    if(!(mbd->flags >> 6 & 0x1)) {
        printf("invalid memory map given by GRUB bootloader");
    }


     /* Make sure the magic number matches for memory mapping*/
    if(magic != MULTIBOOT_BOOTLOADER_MAGIC) {
        printf("invalid magic number!");
    }
 
    /* Check bit 6 to see if we have a valid memory map */
    if(!(mbd->flags >> 6 & 0x1)) {
        printf("invalid memory map given by GRUB bootloader");
    }
 
    /* Loop through the memory map and display the values */
    int i;
    for(i = 0; i < mbd->mmap_length; 
        i += sizeof(multiboot_memory_map_t)) 
    {
        multiboot_memory_map_t* mmmt = 
            (multiboot_memory_map_t*) (mbd->mmap_addr + i);
 
 
        if(mmmt->type == MULTIBOOT_MEMORY_AVAILABLE) {
            /* 
             * Do something with this memory block!
             * BE WARNED that some of memory shown as availiable is actually 
             * actively being used by the kernel! You'll need to take that
             * into account before writing to memory!
             */
        }
    }

    while (1) {
        asm("hlt");
    }
}
as you can see the memory code was 1:1 taken from the topic mentioned above.
Also, here is a linker file, if this might help:

Code: Select all

SECTIONS
{
    . = 1M;

    .boot :
    {
        KEEP(*(.multiboot_header))
    }

    .text :
    {
        *(.text)
    }

    .rodata :
    {
        *(.rodata)
    }

    .data :
    {
        *(.data)
    }

    .bss :
    {
        *(COMMON)
        *(.bss)
    }

    /DISCARD/ : { *(.eh_frame) *(.eh_frame_hdr) }
}
I am studying computer science now and i wanted to learn something in practice, so thats why i writing this microkernel.
Thx in advice.

Re: general protection fault when listing pages in 64 long m

Posted: Tue Jun 11, 2024 11:31 am
by Octocontrabass
maxwildner wrote:

Code: Select all

    mov rsi, [rbx + 8]      ; Multiboot information structure pointer
There are two problems here.

The first problem is that this instruction loads garbage into RSI. When the bootloader jumps to your kernel's entry point, EBX contains the pointer to the Multiboot information structure. This instruction loads a value from memory using the pointer in RBX, but you want the pointer. You should use something like "mov esi, ebx" instead.

The second problem is that EBX doesn't contain the pointer to the Multiboot information structure here. In check_long_mode, you use the CPUID instruction, which overwrites EBX with a different value. You need to store the pointer somewhere else - perhaps in a different register, like ESI - so it won't get overwritten between your entry point and kernel_main.

Re: general protection fault when listing pages in 64 long m

Posted: Wed Jun 12, 2024 3:06 pm
by maxwildner
So i did a small update on code t make sure to store a propper multiboot pointers on start:

Code: Select all

 main.asm
section .data
align 4
; Multiboot header magic number
global mboot_magic
mboot_magic: dd 0x00000000
global mboot_info
mboot_info: dd 0x00000000

section .text
align 4

start:
    ; Set up stack
    mov esp, stack_top

    ; Load multiboot magic number from eax to mboot_magic
    mov [mboot_magic], eax
    ; load multiboot info to mboot_info
    mov [mboot_info], ebx
...

and main64.asm

Code: Select all

long_mode_start:
    ; Load null into all data segment registers
    mov ax, 0
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; Set up stack (optional, stack setup can be done here if needed)
    ; mov rsp, <desired stack address>

    ; Move the values of mboot_magic and mboot_info into registers
    mov edi, dword [mboot_magic]
    mov esi, dword [mboot_info]

    call kmain

    ; Halt the CPU
    hlt
but the issue seems not to be resolved.

this is the kmain:

Code: Select all

extern "C" void kmain(uint32_t magic, multiboot_info* bootInfo) {

    printf("Hello, World!");

    //comare the edi register with the magic number
    if (magic != MULTIBOOT_BOOTLOADER_MAGIC) {
        printf("Invalid magic number");
    }

and it does not report any error, so multiboot should be there

Re: general protection fault when listing pages in 64 long m

Posted: Wed Jun 12, 2024 8:01 pm
by Octocontrabass
I don't see any other obvious problems. Either I've overlooked something or the bug is elsewhere.

Do you have any more information that would help track down the bug? For example, the logs from running QEMU with "-d int" are very helpful.