assemblyx86nasmbootloader

Bootloader crashing when jumping to 0x100000


I am having a problem with a bootloader I made. Mostly used code snippets from wiki.osdev.org, and screeck (on Github and Youtube) . The issue is: the bootloader cannot jump farther than 0xFFFFF, and crashes (used QEMU to test, the system restarts infinitely or triple faults). This is a VBR. I don't think the MBR is needed (or affects anything) here. It jumps to a kernel, but i don't think the kernel is an issue either.
It stops with check_exception old: 0x8 new 0xd .

Can be assembled using nasm: nasm -fbin input.asm -o output.bin

For adding a kernel file, i used dd. To make a full binary:

dd if=bootloader.bin >> disk.img (or bin extension)
dd if=kernel.bin >> disk.img
dd if=/dev/zero bs=512 count=32 >> disk.img (add zeros because i am reading 32 sectors)

I used my own kernel, but just for testing i used a simple:

[org 0x100000]
[bits 64]
start:
    cli
    hlt
times 512 - ($ - $$) db 0

Assembled just like the bootloader.

Ran using qemu -hda disk.img -d int

Here is the bootloader code:

[BITS 16]
[ORG 0x7C00]
CODE_OFFSET equ 0x8
DATA_OFFSET equ 0x10
CR0_PAGING equ 1 << 31

PML4T_ADDR equ 0x1000
SIZEOF_PAGE_TABLE equ 4096

PDPT_ADDR equ 0x2000
PDT_ADDR equ 0x3000
PT_ADDR equ 0x4000
PT_ADDR_MASK equ 0xffffffffff000
PT_PRESENT equ 1
PT_READABLE equ 2

ENTRIES_PER_PT equ 512
SIZEOF_PT_ENTRY equ 8
PAGE_SIZE equ 0x1000

CR4_PAE_ENABLE equ 1 << 5

EFER_MSR equ 0xC0000080
EFER_LM_ENABLE equ 1 << 8

CR0_PM_ENABLE equ 1 << 0
CR0_PG_ENABLE equ 1 << 31

PRESENT        equ 1 << 7
NOT_SYS        equ 1 << 4
EXEC           equ 1 << 3
DC             equ 1 << 2
RW             equ 1 << 1
ACCESSED       equ 1 << 0
GRAN_4K       equ 1 << 7
SZ_32         equ 1 << 6
LONG_MODE     equ 1 << 5
VGA_TEXT_BUFFER_ADDR equ 0xb8000
COLS equ 80
ROWS equ 25
VGA_TEXT_BUFFER_SIZE equ COLS * ROWS
start:
    cli
    mov ax, 0x00
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    sti
load_kernel:
    mov ah, 0x42
    mov si, DAP
    mov dl, 0x80
    int 0x13
    jnc load_protected
    hlt
load_protected:
    cli
    lgdt [gdt_desc]
    mov eax, cr0
    or al, 1
    mov cr0, eax
    jmp CODE_OFFSET:protected_main
gdt_start:
    dd 0x0
    dd 0x0

    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 10011010b
    db 11001111b
    db 0x00

    dw 0xFFFF
    dw 0x0000
    db 0x00
    db 10010010b
    db 11001111b
    db 0x00
gdt_end:
gdt_desc:
    dw gdt_end - gdt_start - 1
    dd gdt_start
gdt_start_new:
    .Null: equ $ - gdt_start_new
        dq 0
    .Code: equ $ - gdt_start_new
        .Code.limit_lo: dw 0xffff
        .Code.base_lo: dw 0
        .Code.base_mid: db 0
        .Code.access: db PRESENT | NOT_SYS | EXEC | RW
        .Code.flags: db GRAN_4K | LONG_MODE | 0xF
        .Code.base_hi: db 0
    .Data: equ $ - gdt_start_new
        .Data.limit_lo: dw 0xffff
        .Data.base_lo: dw 0
        .Data.base_mid: db 0
        .Data.access: db PRESENT | NOT_SYS | RW
        .Data.Flags: db GRAN_4K | SZ_32 | 0xF
        .Data.base_hi: db 0
gdt_end_new:
gdt_desc_new:
    dw gdt_end_new - gdt_start_new - 1
    dd gdt_start_new
[BITS 32]
protected_main:
    mov ax, DATA_OFFSET
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov ss, ax
    mov gs, ax
    mov ebp, 0x9C00
    mov esp, ebp
    in al, 0x92
    or al, 2
    out 0x92, al
no_paging_32:
    mov eax, cr0
    and eax, ~CR0_PAGING
    mov cr0, eax
clear_tables:
    mov edi, PML4T_ADDR
    mov cr3, edi
    xor eax, eax
    mov ecx, SIZEOF_PAGE_TABLE
    rep stosd
    mov edi, cr3
link_entries:
    mov DWORD [edi], PDPT_ADDR & PT_ADDR_MASK | PT_PRESENT | PT_READABLE
    mov edi, PDPT_ADDR
    mov DWORD [edi], PDT_ADDR & PT_ADDR_MASK | PT_PRESENT | PT_READABLE
    mov edi, PDT_ADDR
    mov DWORD [edi], PT_ADDR & PT_ADDR_MASK | PT_PRESENT | PT_READABLE
fill_table:
    mov edi, PT_ADDR
    mov ebx, PT_PRESENT | PT_READABLE
    mov ecx, ENTRIES_PER_PT
set_entry:
    mov DWORD [edi], ebx
    add ebx, PAGE_SIZE
    add edi, SIZEOF_PT_ENTRY
    loop set_entry
set_pae:
    mov eax, cr4
    or eax, CR4_PAE_ENABLE
    mov cr4, eax
comp_main:
    mov ecx, EFER_MSR
    rdmsr
    or eax, EFER_LM_ENABLE
    wrmsr
enable_paging:
    mov eax, cr0
    or eax, CR0_PG_ENABLE | CR0_PM_ENABLE
    mov cr0, eax
load_gdt:
    lgdt [gdt_desc_new]
    jmp gdt_start_new.Code:long_main
[BITS 64]
long_main:
    cli
    mov ax, gdt_start_new.Data
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov rdi, VGA_TEXT_BUFFER_ADDR + 160
    mov rcx, 11
    mov rbx, 0
    .print:
        mov ah, 0x07
        mov al, [success + rbx]
        stosw
        add rbx, 1
        dec rcx
        cmp rcx, 0
        jnz .print
jump_kernel:
    jmp 0x100000
DAP:
  .size db 0x10
  .reserved db 0
  .numSectors dw 0x0020
  .bufferSeg dd 0x100000
  .LBAAddress dq 2
success db "VBR Loaded", 0
times 510 - ($ - $$) db 0
dw 0xAA55

Solution

  • According to OSDev, the 32-bit buffer address in the disk address packet (DAP) is actually a real mode seg:ofs far pointer. So your dd 0x100000 is actually 0x10:0000, which is physical address 0x100, not 0x100000. Thus your kernel is not loaded where you think it is, and you're presumably jumping to some garbage code. (In my test I get CR2 = RAX, which could be consistent with executing add [rax], al whose encoding is zeros.)

    You can't load disk data outside the low 1MB of memory with this int 0x13 function. OSDev mentions an extended form that accepts a 64-bit flat address, but says it's not widely supported; I don't know whether QEMU supports it.

    The usual solution here would be to load your kernel somewhere in the low 1MB, and either execute it there, or manually copy it to higher memory.