Skip to content

Instantly share code, notes, and snippets.

@Techno-coder
Last active April 13, 2020 14:49
Show Gist options
  • Save Techno-coder/6c79a8849d88754c8d2cf9e7da5e97f9 to your computer and use it in GitHub Desktop.
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)
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
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