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
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)
}
}
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
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:
Code: Select all
ENTRY(start)
SECTIONS {
. = 1M;
.boot :
{
/* ensure that the multiboot header is at the beginning */
*(.multiboot_header)
}
.text :
{
*(.text)
}
}
Any help is appreciated, as I really don't understand what's going wrong here