Problem accessing higher half in 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
User avatar
Heron
Posts: 8
Joined: Tue Apr 22, 2014 6:36 am

Problem accessing higher half in Long Mode

Post by Heron »

The processor doesn't set the access bit but I have no idea where the problem is. Long Mode works but the jump to higher half crashes the kernel.

My code:

Code: Select all

bits 32
section .init
global check_cpuid
global check_x64_compatibility
global setup_x64

PAGE_PRESENT equ (1 << 0)
PAGE_WRITE   equ (1 << 1)
PAGE_GLOBAL  equ (1 << 8)

check_cpuid:
	pushfd                 ; Get
	pop eax
	mov ecx, eax           ; Save
	xor eax, 0x200000      ; Flip
	push eax               ; Set
	popfd
	pushfd                 ; And test
	pop eax
	xor eax, ecx           ; Mask changed bits
	shr eax, 21            ; Move bit 21 to bit 0
	and eax, 1             ; And mask others
	push ecx
	popfd                  ; Restore original flags
	ret

check_x64_compatibility:
	mov eax, 0x80000000
	cpuid 
	cmp eax, 0x80000001
	jb .no_x64
	
	mov eax, 0x80000001    ; Set the A-register to 0x80000001.
	cpuid                  ; CPU identification.
	test edx, 1 << 29      ; Test if the LM-bit, which is bit 29, is set in the D-register.
	jz .no_x64             ; They aren't, there is no long mode.
	mov eax, 1
	ret

.no_x64:
	mov eax, 0
	ret
	
setup_x64:
	mov edx, 0x1000				; Save the address of the first Page-Map Level-4 Table (PML4)
	mov edi, edx
	xor eax, eax
	mov ecx, 0x1000				; Size of memory to flush
	rep stosd						; Flush
	mov edi, edx

	; Lower half mapping
	lea eax, [edi + 0x1000]			; Put the address of the first Page Directory Pointer Table (PDPT) to eax
	or eax, PAGE_PRESENT | PAGE_WRITE	; Make present and writeable
	mov [edi], eax					; Set the value of eax as the first PML4 entry

	lea eax, [edi + 0x2000]			; Put the address of the first Page Directory Table (PDT) to eax
	or eax, PAGE_PRESENT | PAGE_WRITE
	mov [edi + 0x1000], eax			; Set the value of eax as the first PDPT entry

	lea eax, [edi + 0x3000]			; Put the address of the first Page Table (PT) to eax
	or eax, PAGE_PRESENT | PAGE_WRITE
	mov [edi + 0x2000], eax			; Set the value of eax as the first PDT entry

	; Higher half mapping
	lea eax, [edi + 0x1FF8]			; Put the address of the last Page Directory Pointer Table (PDPT) to eax
	or eax, PAGE_PRESENT | PAGE_WRITE	; Make present and writeable
	mov [edi + 0x0FF8], eax			; Set the value of eax as the last PML4 entry

	lea eax, [edi + 0x2C00]			; Put the address of the last Page Directory Table (PDT) to eax
	or eax, PAGE_PRESENT | PAGE_WRITE
	mov [edi + 0x1FF8], eax			; Set the value of eax as the 384th PDPT entry

	lea eax, [edi + 0x3000]			; Put the address of the first Page Table (PT) to eax
	or eax, PAGE_PRESENT | PAGE_WRITE
	mov [edi + 0x2C00], eax			; Set the value of eax as the first PDT entry

	mov edi, 0x4000                    ; Point to the address of the first PT
	mov ecx, 512					; Number of entries to set
	lea eax, [0x0]					; Put the first memory address to eax

.set_entry:						; Set the page entries
	or eax, PAGE_PRESENT | PAGE_WRITE | PAGE_GLOBAL
	mov [edi], eax					; Set the page in the PT
	add eax, 0x1000				; Add 4096 for next page
	add edi, 8					; Add 8 for next PT entry
	loop .set_entry

.enable_pae:						; Enable PAE by setting the PAE bit (bit 5)
	mov eax, cr4
	or eax, 1 << 5
	mov cr4, eax

.set_paging_entry:					; Point CR3 at the PML4
	mov cr3, edx

.enable_long_mode:					; Enable Long Mode by setting the LM bit (bit 8)
	mov ecx, 0xC0000080
	rdmsr
	or eax, 1 << 8
	wrmsr

.enable_paging:					; Enable Paging by setting the PG bit (bit 31)
	mov eax, cr0
	or eax, 1 << 31
	mov cr0, eax

	ret

Code: Select all

bits 32
section .init
global kinit
global load_gdt
extern KERNEL_VIRTUAL_BASE, tmp_stack
extern check_cpuid, check_x64_compatibility, setup_x64
extern load_gdt

kinit:
	call check_cpuid
	cmp eax, 1
	jne .error

	call check_x64_compatibility
	cmp eax, 1
	jne .error

	call setup_x64

	mov eax, gdt64.pointer
	call load_gdt

	jmp gdt64.code:yeah_here_we_go

.error:
	mov byte [0xB8000], 'E'
	mov byte [0xB8001], 0xE | 0 << 4 ; background black, foreground yellow
	mov byte [0xB8002], 'R'
	mov byte [0xB8003], 0xE | 0 << 4
	mov byte [0xB8004], 'R'
	mov byte [0xB8005], 0xE | 0 << 4
	mov byte [0xB8006], 'O'
	mov byte [0xB8007], 0xE | 0 << 4
	mov byte [0xB8008], 'R'
	mov byte [0xB8009], 0xE | 0 << 4
	mov byte [0xB800A], '!'
	mov byte [0xB800B], 0xE | 0 << 4

.game_over:
	cli
	hlt
	jmp .game_over

bits 64
section .init
yeah_here_we_go:
	cli                           ; Clear the interrupt flag.
	mov byte [0xB8000], 'L'
	mov byte [0xB8001], 0xE | 0 << 4
	mov byte [0xB8002], 'o'
	mov byte [0xB8003], 0xE | 0 << 4
	mov byte [0xB8004], 'a'
	mov byte [0xB8005], 0xE | 0 << 4
	mov byte [0xB8006], 'd'
	mov byte [0xB8007], 0xE | 0 << 4
	mov byte [0xB8008], 'i'
	mov byte [0xB8009], 0xE | 0 << 4
	mov byte [0xB800A], 'n'
	mov byte [0xB800B], 0xE | 0 << 4
	mov byte [0xB800C], 'g'
	mov byte [0xB800D], 0xE | 0 << 4
	mov byte [0xB800E], '.'
	mov byte [0xB800F], 0xE | 0 << 4
	mov byte [0xB8010], '.'
	mov byte [0xB8011], 0xE | 0 << 4
	mov byte [0xB8012], '.'
	mov byte [0xB8013], 0xE | 0 << 4

	lea rcx, [kentry]
	jmp rcx

extern kmain
extern __cxa_finalize

kentry:
	call kmain

	sub esp, 4
	mov [esp], dword 0x0
	call __cxa_finalize
	add esp, 4

.end:
	cli
	hlt
	jmp .end


gdt64:
	.null: equ $ - gdt64         ; The null descriptor.
	dq 0

	.code: equ $ - gdt64         ; The code descriptor.
	dw 0                         ; Limit (low).
	dw 0                         ; Base (low).
	db 0                         ; Base (middle)
	db 10011000b                 ; Access.
	db 00100000b                 ; Granularity.
	db 0                         ; Base (high).

	.data: equ $ - gdt64         ; The data descriptor.
	dw 0                         ; Limit (low).
	dw 0                         ; Base (low).
	db 0                         ; Base (middle)
	db 10010000b                 ; Access.
	db 00000000b                 ; Granularity.
	db 0                         ; Base (high).

align 4
	dw 0

	.pointer:                    ; The GDT-pointer.
	dw $ - gdt64 - 1             ; Limit.
	dd gdt64

Code: Select all

ENTRY(kinit)
OUTPUT(elf64_x86_64)

KERNEL_VIRTUAL_BASE = 0xFFFFFFFFF0000000;

SECTIONS
{
	. = 0x100000;                                   /* 0x0000000000100000 */

	kernel_init_start = .;

	.init :
	{
		*(.multiboot)
		*(.init)
	}

	kernel_init_end = .;

	. += KERNEL_VIRTUAL_BASE;

	kernel_main_start = .;

	.text ALIGN(0x1000) : AT(ADDR(.text) - KERNEL_VIRTUAL_BASE)
	{
		*(.text)
		*(.gnu.linkonce.t*)
	}
	
	.data ALIGN(0x1000) : AT(ADDR(.data) - KERNEL_VIRTUAL_BASE)
	{
		__init_array = .;
		KEEP(*( .init_array ));
		KEEP(SORT_BY_NAME(*)( .init_array));
		__init_array_end = .;
		
		__fini_array = .;
		KEEP(*( .fini_array ));
		KEEP(SORT_BY_NAME(*)( .fini_array ));
		__fini_array_end = .;

		*(.data)
		*(.gnu.linkonce.d*)
		*(.rodata*)
		*(.gnu.linkonce.r*)
	}
		
	.bss ALIGN(0x1000) : AT(ADDR(.bss) - KERNEL_VIRTUAL_BASE)
	{
		*(COMMON)
		*(.bss)
		*(.gnu.linkonce.b*)
	}
	
	/DISCARD/ :
	{
		*(.comment)
		*(.eh_frame)
	}

	kernel_main_end = .;
}
-> Some comments are not up-to-date <-

[EDIT]
Ok, got it finally. I misunderstood how the paging works..
Thanks for all your help :D
Last edited by Heron on Tue Dec 16, 2014 12:55 pm, edited 1 time in total.
English isn't my mother tongue so please correct me if something is wrong.
User avatar
eryjus
Member
Member
Posts: 286
Joined: Fri Oct 21, 2011 9:47 pm
Libera.chat IRC: eryjus
Location: Tustin, CA USA

Re: Problem accessing higher half in Long Mode

Post by eryjus »

I think Sortie pulled that tutorial because of the errors it had and/or the amount of trouble it was causing.
Heron wrote:

Code: Select all

KERNEL_VIRTUAL_BASE = 0xFFFFFFFFF0000000;
In your case, to looks like you have changed the higher-half location without properly setting up your paging tables. The original tutorial had the higher half location at 0xffffffff80000000. Your crash is most likely an un-handled page fault.

I recommend starting with Bare Bones.
Adam

The name is fitting: Century Hobby OS -- At this rate, it's gonna take me that long!
Read about my mistakes and missteps with this iteration: Journal

"Sometimes things just don't make sense until you figure them out." -- Phil Stahlheber
User avatar
Heron
Posts: 8
Joined: Tue Apr 22, 2014 6:36 am

Re: Problem accessing higher half in Long Mode

Post by Heron »

Thanks but I also tried other tutorials and looked at other implementations and it never worked as expected.

No, I have changed the paging structures, too. ;)
but I have maybe calculated it wrong..
English isn't my mother tongue so please correct me if something is wrong.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Problem accessing higher half in Long Mode

Post by Combuster »

The debugger is an interesting piece of software. It can pause execution at any point in the code, and it can test what piece of memory contains what data. It can also do the calculations for you and print what the address space looks like in it's current state.

tl;dr: compile bochs with debugger enabled, set a breakpoint at the paging enable and check result of "info tab"
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Heron
Posts: 8
Joined: Tue Apr 22, 2014 6:36 am

Re: Problem accessing higher half in Long Mode

Post by Heron »

I'm using Virtualbox and this is what I get from its debugger
rax=0000000000100010 rbx=0000000000000000 rcx=0000000000100268 rdx=0000000000000000
rsi=0000000000000000 rdi=0000000000005000 r8 =0000000000000000 r9 =0000000000000000
r10=0000000000000000 r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000 iopl=0 rf nv up di ng nz na po nc
rip=fffffffff0101000 rsp=000000000007fec8 rbp=0000000000000000
cs=0008 ds=0010 es=0010 fs=0010 gs=0010 ss=0018 rflags=00210086
Failed to disassemble instruction, skipping one byte.
u: error: Too many disassembly failures. Giving up: VERR_PAGE_TABLE_NOT_PRESENT
English isn't my mother tongue so please correct me if something is wrong.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Problem accessing higher half in Long Mode

Post by Combuster »

The only thing you can check from that is whether or not the address jumped to is correct. Virtualbox is inferior to Bochs' capabilities, you might want to learn how to use it.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Heron
Posts: 8
Joined: Tue Apr 22, 2014 6:36 am

Re: Problem accessing higher half in Long Mode

Post by Heron »

Ok, checked it. kmain is located at 0xfffffffff0101000
English isn't my mother tongue so please correct me if something is wrong.
User avatar
xenos
Member
Member
Posts: 1121
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: Problem accessing higher half in Long Mode

Post by xenos »

Heron wrote:VERR_PAGE_TABLE_NOT_PRESENT
Looks like already VirtualBox has a good suggestion what to check. Bochs can tell you more details.
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
User avatar
Heron
Posts: 8
Joined: Tue Apr 22, 2014 6:36 am

Re: Problem accessing higher half in Long Mode

Post by Heron »

OK Bochs I'm coming :P

I'll post my findings or solution tomorrow..
English isn't my mother tongue so please correct me if something is wrong.
Post Reply