OSDev.org

The Place to Start for Operating System Developers
It is currently Sat Apr 27, 2024 5:28 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: Page fault on ljmp to long mode
PostPosted: Tue Feb 06, 2024 5:19 am 
Offline

Joined: Wed Jan 24, 2024 11:33 am
Posts: 4
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:
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:
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:
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:
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:
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


Top
 Profile  
 
 Post subject: Re: Page fault on ljmp to long mode
PostPosted: Tue Feb 06, 2024 10:56 am 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
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:
    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:
    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.)


Top
 Profile  
 
 Post subject: Re: Page fault on ljmp to long mode
PostPosted: Wed Feb 07, 2024 7:30 am 
Offline

Joined: Wed Jan 24, 2024 11:33 am
Posts: 4
Quote:
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!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], DotBot [Bot], Majestic-12 [Bot] and 16 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group