Page fault on ljmp to long mode

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
codeExplorer
Posts: 4
Joined: Wed Jan 24, 2024 11:33 am

Page fault on ljmp to long mode

Post by codeExplorer »

Hey everyone,

As the title implies, I've been trying to setup long mode on my operating system, and have run into a problem. I managed to get everything working in BOCHS, and I successfully enter long mode, but on QEMU, the ljmp to reload the GDT and enter long mode from compatibility mode gives a page fault, and it's been near impossible to debug in QEMU, since even with "-d int", it doesn't provide any more information (I think because it happens right when the CPU changes mode).

What's really bothering me, though, is that seemingly the same code, but written is NASM instead of GAS, seems to work without issue. I've even gone through both of the objdumps, and compared them running side by side in BOCHS, to see if there was any difference, but I couldn't find any. In the GAS code, I can get a minimal example to work if I don't try to disable any paging that the bootloader might have set up (this is the code with the comment #BUG), but that makes no difference in the NASM example, so I really want to understand what's the difference between them.

So, here is the code in GAS:
init.S:

Code: Select all

header_start:
    .long 0xe85250d6
    .long 0
    .long header_end - header_start
    .long 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))
    .short 0
    .short 0
    .long 8
header_end:


# BSS
# Stack, and paging tables
.section .bss
.align 4096
p4_table:
.skip 4096
p3_table:
.skip 4096
p2_table:
.skip 4096
stack_bottom:
#.skip 16384 # 16 KiB
.skip 64
stack_top:


.code32
.section .text
.global _start
.type _start, @function
_start:
    xchgw %bx, %bx
    mov $stack_top, %esp
    
    # BUG this breaks it. WHY????
    mov %cr0, %eax
    and 0b01111111111111111111111111111111, %eax
    mov %eax, %cr0 # BUG Specifically this one

    call set_up_page_tables
    call enable_paging

    lgdt (gdt_desc)

    xchgw %bx, %bx

    ljmp $CODE_SEGMENT, $long_mode_start

    
set_up_page_tables:
    mov $p3_table, %eax
    or $0b11, %eax
    mov %eax, p4_table

    mov $p2_table, %eax
    or $0b11, %eax
    mov %eax, p3_table

    mov $0, %ecx

.map_p2_table:
    mov $0x200000, %eax
    mul %ecx
    or $0b10000011, %eax
    
    movl %eax, p2_table(, %ecx, 8)

    inc %ecx
    cmp $512, %ecx
    jne .map_p2_table

    ret

enable_paging:
    mov $p4_table, %eax
    mov %eax, %cr3

    mov %cr4, %eax
    or $1 << 5, %eax
    mov %eax, %cr4

    mov $0xC0000080, %ecx
    rdmsr
    or $1 << 8, %eax
    wrmsr

    mov %cr0, %eax
    or $1 << 31, %eax
    mov %eax, %cr0

    ret

long_mode_start:
xchgw %bx, %bx
    cli
    hlt


.section .rodata
gdt_start:
gdt_null:
   .word 0x0000, 0x0000
   .byte 0x00, 0b00000000, 0b00000000, 0x00
   # Code Descriptor
gdt_code:
    .quad (1<<43) | (1<<44) | (1<<47) | (1<<53)
gdt_end:

.equ GDT_START, gdt_start 
    .word gdt_end - gdt_start - 1
    .long GDT_START

.equ CODE_SEGMENT, gdt_code - gdt_start
init.ld:

Code: Select all

ENTRY(_start)
OUTPUT_FORMAT("elf32-i386")
OUTPUT_ARCH("i386")
phys = 1M;

/* Tell linker where to put **** */
SECTIONS
{
    /* Where to begin putting sections */
    . = phys;

    /* Put the header first, and then the .text section */
    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.multiboot_header)
        *(.text)
    }

    /* Read-only data */
    .rodata BLOCK(4K) : ALIGN(4K)
    {
        *(.rodata)
    }

    /* Read-write data (initialized) */
    .data BLOCK(4K) : ALIGN(4K)
    {
        *(.data)
    }

    /* Read-write data (uninitialized) and stack */
    .bss BLOCK(4K) : ALIGN(4K)
    {
        *(COMMON)
        *(.bss)
    }
}
And the code in NASM:
boot.asm:

Code: Select all

global start

section .text
bits 32
start:
    xchg bx,bx
    mov esp, stack_top

    ;call check_multiboot
    ;call check_cpuid
    ;call check_long_mode

    mov eax, cr0
    and eax, 0b01111111111111111111111111111111
    mov cr0, eax

    call set_up_page_tables
    call enable_paging

    lgdt [gdt64.pointer]

    xchg bx, bx

    jmp gdt64.code:long_mode_start


set_up_page_tables:
    ; map first P4 entry to P3 table
    mov eax, p3_table
    or eax, 0b11 ; present + writable
    mov [p4_table], eax

    ; map first P3 entry to P2 table
    mov eax, p2_table
    or eax, 0b11 ; present + writable
    mov [p3_table], eax

    ; map each P2 entry to a huge 2MiB page
    mov ecx, 0         ; counter variable

.map_p2_table:
    ; map ecx-th P2 entry to a huge page that starts at address 2MiB*ecx
    mov eax, 0x200000  ; 2MiB
    mul ecx            ; start address of ecx-th page
    or eax, 0b10000011 ; present + writable + huge
    mov [p2_table + ecx * 8], eax ; map ecx-th entry

    inc ecx            ; increase counter
    cmp ecx, 512       ; if counter == 512, the whole P2 table is mapped
    jne .map_p2_table  ; else map the next entry

    ret

enable_paging:
    ; load P4 to cr3 register (cpu uses this to access the P4 table)
    mov eax, p4_table
    mov cr3, eax

    ; enable PAE-flag in cr4 (Physical Address Extension)
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    ; set the long mode bit in the EFER MSR (model specific register)
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    ; enable paging in the cr0 register
    mov eax, cr0
    or eax, 1 << 31
    mov cr0, eax

    ret

long_mode_start:
    xchg bx,bx
    cli
    hlt
    mov ax, 10
    xchg bx,bx

bits 32
section .bss
align 4096
p4_table:
    resb 4096
p3_table:
    resb 4096
p2_table:
    resb 4096
stack_bottom:
    resb 64
stack_top:

section .rodata
gdt64:
    dq 0
.code: equ $ - gdt64
    dq (1<<43) | (1<<44) | (1<<47) | (1<<53)
.pointer:
    dw $ - gdt64 - 1
    dq gdt64
multiboot.asm:

Code: Select all

section .multiboot_header
header_start:
    dd 0xe85250d6                ; magic number (multiboot 2)
    dd 0                         ; architecture 0 (protected mode i386)
    dd header_end - header_start ; header length
    ; checksum
    dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))

    ; insert optional multiboot tags here

    ; required end tag
    dw 0    ; type
    dw 0    ; flags
    dd 8    ; size
header_end:
linker.ld:

Code: Select all

ENTRY(start)

SECTIONS {
    . = 1M;

    .boot :
    {
        /* ensure that the multiboot header is at the beginning */
        *(.multiboot_header)
    }

    .text :
    {
        *(.text)
    }
}
Both the examples are linked with the same linker, and I'm using grub2, with the multiboot 2 standard to boot them both.

Any help is appreciated, as I really don't understand what's going wrong here
Octocontrabass
Member
Member
Posts: 5560
Joined: Mon Mar 25, 2013 7:01 pm

Re: Page fault on ljmp to long mode

Post by Octocontrabass »

codeExplorer wrote:it's been near impossible to debug in QEMU, since even with "-d int", it doesn't provide any more information
If QEMU isn't logging the exception, try adding "-accel tcg" to the command line to disable hardware acceleration.
codeExplorer wrote:In the GAS code, I can get a minimal example to work if I don't try to disable any paging that the bootloader might have set up
You're using Multiboot2, which means paging is already disabled.
codeExplorer wrote:

Code: Select all

    and 0b01111111111111111111111111111111, %eax
You forgot the $ prefix, so GAS is assembling it as a memory operand instead of an immediate operand. (But paging is already disabled so you don't need this code anyway.)
codeExplorer wrote:

Code: Select all

    lgdt (gdt_desc)
I don't see a gdt_desc label anywhere in your code.
codeExplorer wrote:Both the examples are linked with the same linker,
Why aren't they both linked with the same ld script? (Aside from the different entry point labels.)
codeExplorer
Posts: 4
Joined: Wed Jan 24, 2024 11:33 am

Re: Page fault on ljmp to long mode

Post by codeExplorer »

You forgot the $ prefix, so GAS is assembling it as a memory operand instead of an immediate operand. (But paging is already disabled so you don't need this code anyway.)
Jesus, this was the issue, I feel so dumb right now!

While I know the code is unnecessary, at this point I just wanted to understand what was wrong, after this many hours of debugging. Thanks for the help!
Post Reply