wrmsr instruction causing triple fault in bootloader while trying to enable paging?

203 views Asked by At

I am working on an OS and am trying to transition into long mode in order to execute 64-bit Rust code in an x86_64 environment.

boot.asm:

;;;;;;;;;;;;;;;;;;;;;;;;;
; STAGE ONE / BOOT SECTOR
;;;;;;;;;;;;;;;;;;;;;;;;;

[ORG 0x7c00] ; this doesn't work when targeting elf in nasm
[BITS 16]

global _start

_start:
; set segment registers
cli
xor ax, ax     ; code segment
mov ds, ax     ; data segment
mov es, ax     ; extra segment
mov ss, ax     ; stack segment
mov bp, 0x7c00 ; base pointer
mov sp, bp     ; stack pointer
sti

; print message (removed for now, though)
;mov si, msg1
;call print
;%include 'Bootloader/print.asm'
;msg1: db "Bootsector - loading stage two", 0

; Print out RM (for real mode)
mov ah, 0eh
mov al, 'R'
int 10h
mov al, 'M'
int 10h

; read the next sector. i wrote some notes on how this interrupt works for later
mov al, 01h    ; sectors to read (1)
mov bx, 0x7e00 ; buffer address (512 bytes away from current address 0x7c00)
mov cx, 0002h  ; cylinder and sector numbers (cylinder 0, sector 2)
mov dl, 0      ; drive 0 (boot drive)
mov dh, 0      ; head 0

mov ah, 02h
int 13h

; switch us to protected mode
cli
lgdt [GDT.desc]
mov eax, cr0
or  eax, 0x1
mov cr0, eax
; we are now in protected mode!
jmp 0x8:ProtMode

[BITS 32]
ProtMode:
    ; reset segment registers
    mov ax, 0x10
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    jmp 0x7e00 ; next sector

GDT: ; finally figured out the GDT. comments are copied from https://github.com/micouy/gniazdo-os/blob/master/asm/gdt.asm to help me remember what they mean
    .null:
        dq 0x0

    .code:
        dw 0xffff       ; Limit
        dw 0x0          ; Base
        db 0x0          ; Base

        ; 7 - present flag
        ; 5-6 - required privelige
        ; 4 - is either code or data?
        ; 3 - code or data?
        ; 2 - is lower privelige allowed to read/exec?
        ; 1 - read or write?
        ; 0 - access flag
        db 0b10011010   ; ACCESS BYTES

        ; 7 - granularity (multiplies segment limit by 4kB)
        ; 6 - 16 bit or 32 bit?
        ; 5 - required by intel to be set to 0
        ; 4 - free to use
        ; 0-3 - last bits of segment limit
        db 0b11001111   ; FLAGS

        db 0x0          ; Base

    .data:
        dw 0xffff
        dw 0x0
        db 0x0
        ; the access bytes and flags are the same as in the .code
        db 0b10010010 ; ACCESS BYTES
        db 0b11001111 ; FLAGS
        db 0x0

    .desc:
        dw $ - GDT - 1
        dd GDT

times 510-($ - $$) db 0 ; fill up rest of sector with zeros
dw 0xAA55 ; tells BIOS this is bootable

;;;;;;;;;;;
; STAGE TWO
;;;;;;;;;;;
[BITS 32] ; making sure we're in PM

; if this code executes, the "RM" written on the screen will change to "PM" for protected mode,
; letting us know that we're running in protected mode now ;)
mov word [0xb8000], 0x0750

; set paging tables, credits to the intermezzOS docs for helping me learn this
; point first entry of p4_table to first entry in p3_table
mov eax, p3_table ; copy p3_table to EAX
or eax, 0b11 ; sets first two bits, present bit and writable bit, to one (page is in memory, page can be written to)
mov dword [p4_table + 0], eax ; copy EAX to the memory address of the zeroth entry in the p4_table

; point first entry of p3_table to first entry in p2_table
mov eax, p2_table ; copy p2_table to EAX
or eax, 0b11 ; sets first two bits, present bit and writable bit, to one (page is in memory, page can be written to)
mov dword [p3_table + 0], eax ; copy EAX to the memory address of the zeroth entry in the p3_table

; point all p2_table entries to a page
mov ecx, 0 ; counter
.loop_p2_table:
    mov eax, 0x200000 ; 2 MiB
    mul ecx ; multiply EAX by ECX
    or eax, 0b10000011 ; extra 1 at the front tells that this is a "huge page", the rest is the same
    mov [p2_table + ecx * 8], eax ; copy EAX to the memory address of the (ECX * 8)th entry in the p2_table

    inc ecx ; increment ECX by 1 (ECX = ECX+1)
    cmp ecx, 512 ; is ecx equal to 512?
    jne .loop_p2_table ; if not, loop back over

mov word [0xb8002], 0x0754 ; set screen to "PT" for paging tables

; enable paging
; move page table into CR3
mov eax, p4_table
mov cr3, eax

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

; set Long Mode bit
mov ecx, 0xC0000080
rdmsr
or eax, (1 << 8)
wrmsr ; write MSR TODO: fix this bc it causes a triple fault?

; enable paging :O
mov eax, cr0
or eax, (1 << 31 | 1 << 16)
mov cr0, eax

; set the Long Mode GDT
lgdt [gdt64.desc]

; TODO: finish bootloader

; paging tables
section .bss
align 4096
p4_table:
    resb 4096
p3_table:
    resb 4096
p2_table:
    resb 4096

; long GDT
section .rodata
gdt64:
    .null:
        dq 0
    .code: equ $ - gdt64
        dq (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53)
        ; 44th bit: Descriptor type, set to 1 for code/data segments
        ; 47th bit: Present, set to 1 if entry is valid
        ; 41st bit: Read/Write, set to 1 if it's readable
        ; 43rd bit: Executable, set to 1 for code segments
        ; 53rd bit: "64-bit," set to 1 if this is a 64-bit GDT
    .data: equ $ - gdt64
        dq (1<<44) | (1<<47) | (1<<41)
        ; 41st bit: set to 1 if it's writable
        ; 44th and 47th bit are the same
    .desc:
        dw .desc - gdt64 - 1
        dq gdt64

Makefile in case it's needed (i am using the make boot option)

https://github.com/TetrisLitHub/Bootable-Rust/blob/master/Makefile

virtualbox error message in debug console:

dbgf event: DBGFSTOP (hyper)
File:     VINF_EM_TRIPLE_FAULT
Line:     0
Function: <NULL>
eax=00000100 ebx=00007e00 ecx=c0000080 edx=00000000 esi=00000000 edi=0000fff0
eip=00007e6a esp=00007c00 ebp=00007c00 iopl=0 nv up di pl nz na po nc
cs=0008 ds=0010 es=0010 fs=0010 gs=0010 ss=0010               eflags=00200006
0008:00007e6a 0f 30                   wrmsr
VBoxDbg>

I'm really confused why wrmsr is doing this, I've checked my code against the code from the IntermezzOS book for mistakes and couldn't find any. I've googled multiple times and I can't find what I've done wrong. Does the MSR register somehow work differently in VirtualBox than it does in other virtual machines or on bare metal?

find the GitHub repository at my account here: https://github.com/TetrisLitHub/Bootable-Rust

0

There are 0 answers