Last active
April 13, 2020 14:49
-
-
Save Techno-coder/6c79a8849d88754c8d2cf9e7da5e97f9 to your computer and use it in GitHub Desktop.
64-bit higher-half boot script (with notes on changing KERNEL_BASE)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
global start | |
KERNEL_BASE equ 0xFFFFC00000000000 | |
section .inittext | |
bits 32 | |
start: | |
mov esp, stack_top - KERNEL_BASE ; set up stack | |
mov edi, ebx | |
call check_multiboot | |
call check_cpuid | |
call check_long_mode | |
call setup_page_tables | |
call enable_paging | |
lgdt [gdt64.pointer_low - KERNEL_BASE] | |
; update selectors | |
mov ax, gdt64.data | |
mov ss, ax | |
mov ds, ax | |
mov es, ax | |
jmp gdt64.code:prestart64 | |
error: | |
; prints 'ERR: ' and an error code (one ASCII character in al) to the screen | |
mov dword [0xb8000], 0x4f524f45 | |
mov dword [0xb8004], 0x4f3a4f52 | |
mov dword [0xb8008], 0x4f204f20 | |
mov byte [0xb800a], al | |
hlt | |
check_multiboot: | |
; returns error code 0 if the bootloader is not Multiboot-compliant | |
cmp eax, 0x36d76289 | |
jne .no_multiboot | |
ret | |
.no_multiboot: | |
mov al, "0" | |
jmp error | |
check_cpuid: | |
; returns error code 1 if the CPUID instruction is unsupported | |
pushfd | |
pop eax | |
mov ecx, eax | |
xor eax, 1 << 21 | |
push eax | |
popfd | |
pushfd | |
pop eax | |
push ecx | |
popfd | |
cmp eax, ecx | |
je .no_cpuid | |
ret | |
.no_cpuid: | |
mov al, "1" | |
jmp error | |
check_long_mode: | |
; test if extended processor info in available | |
mov eax, 0x80000000 ; implicit argument for CPUID | |
cpuid ; get highest supported argument | |
cmp eax, 0x80000001 ; it needs to be at least 0x80000001 | |
jb .no_long_mode ; if it's less, the CPU is too old for long mode | |
; use extended info to test if long mode is available | |
mov eax, 0x80000001 ; argument for extended processor info | |
cpuid ; returns various feature bits in ecx and edx | |
test edx, 1 << 29 ; test if the LM-bit is set in the D-register | |
jz .no_long_mode ; If it's not set, there is no long mode | |
ret | |
.no_long_mode: | |
mov al, "2" | |
jmp error | |
setup_page_tables: | |
; map first and 384th PML4 entries to PDP table | |
mov eax, pdp_table - KERNEL_BASE | |
or eax, 0b11 ; present + writable | |
mov [pml4_table - KERNEL_BASE], eax | |
mov [pml4_table - KERNEL_BASE + 384 * 8], eax ; CHANGE! 384 has to be changed to the correct KERNEL_BASE P3 table | |
; map first PDP entry to PD table | |
mov eax, pd_table - KERNEL_BASE | |
or eax, 0b11 ; present + writable | |
mov [pdp_table - KERNEL_BASE], eax | |
; map each PD entry to a huge (2MiB) page | |
mov ecx, 0 | |
.next_pd_entry: | |
mov eax, 0x200000 | |
mul ecx | |
or eax, 0b10000011 ; present + writable + huge | |
mov [pd_table - KERNEL_BASE + ecx * 8], eax | |
inc ecx | |
cmp ecx, 512 | |
jne .next_pd_entry | |
ret | |
enable_paging: | |
; load PML4 to cr3 register | |
mov eax, pml4_table - KERNEL_BASE | |
mov cr3, eax | |
; enable PAE flag in cr4 | |
mov eax, cr4 | |
or eax, 1 << 5 | |
mov cr4, eax | |
; set the long mode bit in the EFER MSR | |
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 | |
bits 64 | |
prestart64: | |
mov rax, start64 | |
jmp rax | |
section .text | |
;bits 64 | |
start64: | |
mov rsp, stack_top | |
mov rax, gdt64.pointer | |
lgdt [rax] | |
; NOTE! Selectors must be set to zero otherwise you will get general protection faults upon interrupt return | |
; update selectors | |
mov ax, 0 | |
mov ss, ax | |
mov ds, ax | |
mov es, ax | |
mov fs, ax | |
mov gs, ax | |
jmp start64_2 | |
start64_2: | |
mov rax, pml4_table | |
mov qword [rax], 0 | |
invlpg [0] | |
mov rax, 0x2f592f412f4b2f4f | |
mov rcx, 0xffffc000000b8000 ; CHANGE! The constant needs to be changed to KERNEL_BASE + 0xb8000 not hard coded | |
mov qword [rcx], rax | |
hlt | |
section .bss | |
align 4096 | |
pml4_table: | |
resb 4096 | |
pdp_table: | |
resb 4096 | |
pd_table: | |
resb 4096 | |
page_table: | |
resb 4096 | |
stack_bottom: | |
resb 3 * 4096 | |
stack_top: | |
section .rodata | |
align 8 | |
gdt64: | |
dq 0 ; zero entry | |
.code: equ $ - gdt64 | |
dq (1<<44) | (1<<47) | (1<<41) | (1<<43) | (1<<53) ; code segment | |
.data: equ $ - gdt64 | |
dq (1<<44) | (1<<47) | (1<<41) ; data segment | |
.end: | |
.pointer: | |
dw gdt64.end - gdt64 - 1 | |
dq gdt64 | |
.pointer_low: | |
dw gdt64.end - gdt64 - 1 | |
dq gdt64 - KERNEL_BASE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ENTRY(start) | |
KERNEL_BASE = 0xFFFFC00000000000; | |
SECTIONS { | |
. = 1M; | |
.header : | |
{ | |
KEEP(*(.header)) | |
} | |
.inittext : { | |
*(.inittext) | |
} | |
# Sets the current *virtual* memory address | |
# Any symbols that are referenced past this point | |
# will take the *virtual* memory address | |
. += KERNEL_BASE; | |
# The 'AT' attribute sets the *load* memory address | |
# The boot loader will load the section at this specific address | |
# In this case, it will be loaded in the lower half. | |
# Any instructions that reference symbols here must be adjusted | |
# to reference the *lower* half. This usually involves subtracting | |
# the 'KERNEL_BASE' variable. | |
# However, once we create a page table from the higher half | |
# (virtual address) to the lower half (load memory address) | |
# we no longer need to adjust any instructions. | |
.text : AT(ADDR(.text) - KERNEL_BASE) { | |
*(.text) | |
} | |
.bss : AT(ADDR(.bss) - KERNEL_BASE) { | |
*(.bss) | |
} | |
.rodata : AT(ADDR(.rodata) - KERNEL_BASE) { | |
*(.rodata) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment