mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
The rt0 code implements a dedicated function for initializing the Go runtime structures. Instead of reserving space for a dummy g struct, the rt0 code now uses the g0 and m0 symbols defined by the runtime package. In addition to setting up g0, the rt0 also sets up the m0 struct and links it to g0. Setting up m0 is a requirement for properly bootstapping the malloc-related code in the following commits
398 lines
12 KiB
ArmAsm
398 lines
12 KiB
ArmAsm
; vim: set ft=nasm :
|
|
%include "constants.inc"
|
|
|
|
bits 64
|
|
|
|
section .bss
|
|
align 8
|
|
|
|
; Allocate space for the interrupt descriptor table (IDT).
|
|
; This arch supports up to 256 interrupt handlers
|
|
%define IDT_ENTRIES 0xff
|
|
_rt0_idt_start:
|
|
resq 2 * IDT_ENTRIES ; each 64-bit IDT entry is 16 bytes
|
|
_rt0_idt_end:
|
|
|
|
_rt0_idt_desc:
|
|
resw 1
|
|
resq 1
|
|
|
|
; Allocates space for the IRQ handlers pointers registered by the IRQ package
|
|
_rt0_irq_handlers resq IDT_ENTRIES
|
|
|
|
; The FS register is loaded with the address of r0_g_ptr. fs:0x00 should contain
|
|
; a pointer to the currently active g struct (in this case runtime.g0)
|
|
r0_g_ptr: resq 1
|
|
|
|
section .text
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Kernel 64-bit entry point
|
|
;
|
|
; The 32-bit entrypoint code jumps to this entrypoint after:
|
|
; - it has entered long mode and enabled paging
|
|
; - it has loaded a 64bit GDT
|
|
; - it has set up identity paging for the physical 0-8M region and the
|
|
; PAGE_OFFSET to PAGE_OFFSET+8M region.
|
|
;------------------------------------------------------------------------------
|
|
global _rt0_64_entry
|
|
_rt0_64_entry:
|
|
call _rt0_install_redirect_trampolines
|
|
call _rt0_64_load_idt
|
|
call _rt0_64_setup_go_runtime_structs
|
|
|
|
; Call the kernel entry point passing a pointer to the multiboot data
|
|
; copied by the 32-bit entry code
|
|
extern multiboot_data
|
|
extern _kernel_start
|
|
extern _kernel_end
|
|
extern kernel.Kmain
|
|
|
|
mov rax, _kernel_end - PAGE_OFFSET
|
|
push rax
|
|
mov rax, _kernel_start - PAGE_OFFSET
|
|
push rax
|
|
mov rax, multiboot_data
|
|
push rax
|
|
call kernel.Kmain
|
|
|
|
; Main should never return; halt the CPU
|
|
mov rdi, err_kmain_returned
|
|
call write_string
|
|
|
|
cli
|
|
hlt
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Setup m0, g0 and other symbols required for bootstrapping the Go runtime.
|
|
; For the definitions of g and m see the Go runtime src: src/runtime/runtime2.go
|
|
;------------------------------------------------------------------------------
|
|
_rt0_64_setup_go_runtime_structs:
|
|
%include "go_asm_offsets.inc" ; generated by tools/offsets
|
|
|
|
; The Go allocator expects this symbol to be set to the system page size
|
|
; As the kernel bypass osinit() this needs to be set here.
|
|
extern runtime.physPageSize
|
|
mov rax, runtime.physPageSize
|
|
mov qword [rax], 0x1000 ; 4096
|
|
|
|
; Setup r0_g stack limits using the reserved stack
|
|
extern stack_top
|
|
extern stack_bottom
|
|
extern runtime.g0
|
|
mov rax, stack_bottom
|
|
mov rbx, stack_top
|
|
mov rsi, runtime.g0
|
|
mov qword [rsi+GO_G_STACK+GO_STACK_LO], rax ; g.stack.lo
|
|
mov qword [rsi+GO_G_STACK+GO_STACK_HI], rbx ; g.stack.hi
|
|
mov qword [rsi+GO_G_STACKGUARD0], rax ; g.stackguard0
|
|
|
|
; Link m0 to the g0
|
|
extern runtime.m0
|
|
mov rbx, runtime.m0
|
|
mov qword [rbx+GO_M_G0], rsi ; m.g0 = g0
|
|
mov qword [rsi+GO_G_M], rbx ; g.m = m
|
|
|
|
; Store the address of g0 in r0_g_ptr
|
|
mov rax, r0_g_ptr
|
|
mov qword [rax], rsi
|
|
|
|
; According to the x86_64 ABI, the fs register should contain the
|
|
; address after the pointer to the pointer to the user-space thread
|
|
; structure. This allows the Go runtime to retrieve the address of
|
|
; the currently active g structure by accessing fs:-0x8.
|
|
;
|
|
; Load 64-bit FS register address
|
|
; eax -> lower 32 bits
|
|
; edx -> upper 32 bits
|
|
mov ecx, 0xc0000100 ; fs_base
|
|
mov rsi, r0_g_ptr
|
|
add rsi, 8 ; fs -> r0_g_ptr + 0x8
|
|
mov rax, rsi ; lower 32 bits
|
|
shr rsi, 32
|
|
mov rdx, rsi ; high 32 bits
|
|
wrmsr
|
|
|
|
ret
|
|
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Setup and load IDT. We preload each IDT entry with a pointer to a gate handler
|
|
; but set it as inactive. The code in irq_amd64 is responsible for enabling
|
|
; individual IDT entries when handlers are installed.
|
|
;------------------------------------------------------------------------------
|
|
_rt0_64_load_idt:
|
|
mov rax, _rt0_idt_start
|
|
|
|
%assign gate_num 0
|
|
%rep IDT_ENTRIES
|
|
mov rbx, _rt0_64_gate_entry_%+ gate_num
|
|
mov word [rax], bx ; gate entry bits 0-15
|
|
mov word [rax+2], 0x8 ; GDT descriptor
|
|
mov byte [rax+5], 0x0 ; Mark the entry as NOT present
|
|
shr rbx, 16
|
|
mov word [rax+6], bx ; gate entry bits 16-31
|
|
shr rbx, 16
|
|
mov dword [rax+8], ebx ; gate entry bits 32-63
|
|
|
|
add rax, 16 ; size of IDT entry
|
|
%assign gate_num gate_num+1
|
|
%endrep
|
|
mov rax, _rt0_idt_desc
|
|
mov word [rax], _rt0_idt_end - _rt0_idt_start - 1 ; similar to GDT this must be len(IDT) - 1
|
|
mov rbx, _rt0_idt_start
|
|
mov qword [rax+2], rbx
|
|
lidt [rax]
|
|
ret
|
|
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Generate gate entries. Each gate handler pushes the address of the registered
|
|
; handler to the stack before jumping to a dispatcher function.
|
|
;
|
|
; Some exceptions push an error code to the stack after the stack frame. This
|
|
; code must be popped off the stack before calling iretq. The generated handlers
|
|
; are aware whether they need to deal with the code or not and jump to the
|
|
; appropriate get dispatcher.
|
|
;------------------------------------------------------------------------------
|
|
%assign gate_num 0
|
|
%rep IDT_ENTRIES
|
|
extern _rt0_interrupt_handlers
|
|
_rt0_64_gate_entry_%+ gate_num:
|
|
push rax
|
|
mov rax, _rt0_interrupt_handlers
|
|
add rax, 8*gate_num
|
|
mov rax, [rax]
|
|
xchg rax, [rsp] ; store handler address and restore original rax
|
|
|
|
; For a list of gate numbers that push an error code see:
|
|
; http://wiki.osdev.org/Exceptions
|
|
%if (gate_num == 8) || (gate_num >= 10 && gate_num <= 14) || (gate_num == 17) || (gate_num == 30)
|
|
jmp _rt0_64_gate_dispatcher_with_code
|
|
%else
|
|
jmp _rt0_64_gate_dispatcher_without_code
|
|
%endif
|
|
%assign gate_num gate_num+1
|
|
%endrep
|
|
|
|
%macro save_regs 0
|
|
push r15
|
|
push r14
|
|
push r13
|
|
push r12
|
|
push r11
|
|
push r10
|
|
push r9
|
|
push r8
|
|
push rbp
|
|
push rdi
|
|
push rsi
|
|
push rdx
|
|
push rcx
|
|
push rbx
|
|
push rax
|
|
%endmacro
|
|
|
|
%macro restore_regs 0
|
|
pop rax
|
|
pop rbx
|
|
pop rcx
|
|
pop rdx
|
|
pop rsi
|
|
pop rdi
|
|
pop rbp
|
|
pop r8
|
|
pop r9
|
|
pop r10
|
|
pop r11
|
|
pop r12
|
|
pop r13
|
|
pop r14
|
|
pop r15
|
|
%endmacro
|
|
|
|
;------------------------------------------------------------------------------
|
|
; This dispatcher is invoked by gate entries that expect a code to be pushed
|
|
; by the CPU to the stack. It performs the following functions:
|
|
; - save registers
|
|
; - push pointer to saved regs
|
|
; - push pointer to stack frame
|
|
; - read and push exception code
|
|
; - invoke handler(code, &frame, ®s)
|
|
; - restore registers
|
|
; - pop exception code from stack so rsp points to the stack frame
|
|
;------------------------------------------------------------------------------
|
|
_rt0_64_gate_dispatcher_with_code:
|
|
; This is how the stack looks like when entering this function:
|
|
; (each item is 8-bytes wide)
|
|
;
|
|
;------------------
|
|
; handler address | <- pushed by gate_entry_xxx (RSP points here)
|
|
;-----------------|
|
|
; Exception code | <- needs to be removed from stack before calling iretq
|
|
;-----------------|
|
|
; RIP | <- exception frame
|
|
; CS |
|
|
; RFLAGS |
|
|
; RSP |
|
|
; SS |
|
|
;-----------------
|
|
cld
|
|
|
|
; save regs and push a pointer to them
|
|
save_regs
|
|
mov rax, rsp ; rax points to saved rax
|
|
push rax ; push pointer to saved regs
|
|
|
|
; push pointer to exception stack frame (we have used 15 qwords for the
|
|
; saved registers plus one qword for the data pushed by the gate entry
|
|
; plus one extra qword to jump over the exception code)
|
|
add rax, 17*8
|
|
push rax
|
|
|
|
; push exception code (located between the stack frame and the saved regs)
|
|
sub rax, 8
|
|
push qword [rax]
|
|
|
|
call [rsp + 18*8] ; call registered irq handler
|
|
|
|
add rsp, 3 * 8 ; unshift the pushed arguments so rsp points to the saved regs
|
|
restore_regs
|
|
|
|
add rsp, 16 ; pop handler address and exception code off the stack before returning
|
|
iretq
|
|
|
|
;------------------------------------------------------------------------------
|
|
; This dispatcher is invoked by gate entries that do not use exception codes.
|
|
; It performs the following functions:
|
|
; - save registers
|
|
; - push pointer to saved regs
|
|
; - push pointer to stack frame
|
|
; - invoke handler(&frame, ®s)
|
|
; - restore registers
|
|
;------------------------------------------------------------------------------
|
|
_rt0_64_gate_dispatcher_without_code:
|
|
; This is how the stack looks like when entering this function:
|
|
; (each item is 8-bytes wide)
|
|
;
|
|
;------------------
|
|
; handler address | <- pushed by gate_entry_xxx (RSP points here)
|
|
;-----------------|
|
|
; RIP | <- exception frame
|
|
; CS |
|
|
; RFLAGS |
|
|
; RSP |
|
|
; SS |
|
|
;-----------------
|
|
cld
|
|
|
|
; save regs and push a pointer to them
|
|
save_regs
|
|
mov rax, rsp ; rax points to saved rax
|
|
push rax ; push pointer to saved regs
|
|
|
|
; push pointer to exception stack frame (we have used 15 qwords for the
|
|
; saved registers plus one qword for the data pushed by the gate entry)
|
|
add rax, 16*8
|
|
push rax
|
|
|
|
call [rsp + 17*8] ; call registered irq handler
|
|
|
|
add rsp, 2 * 8 ; unshift the pushed arguments so rsp points to the saved regs
|
|
restore_regs
|
|
|
|
add rsp, 8 ; pop handler address off the stack before returning
|
|
iretq
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Error messages
|
|
;------------------------------------------------------------------------------
|
|
err_kmain_returned db '[rt0_64] kmain returned', 0
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Write the NULL-terminated string contained in rdi to the screen using white
|
|
; text on red background. Assumes that text-mode is enabled and that its
|
|
; physical address is 0xb8000.
|
|
;------------------------------------------------------------------------------
|
|
write_string:
|
|
mov rbx,0xb8000
|
|
mov ah, 0x4F
|
|
.next_char:
|
|
mov al, byte[rdi]
|
|
test al, al
|
|
jz write_string.done
|
|
|
|
mov word [rbx], ax
|
|
add rbx, 2
|
|
inc rdi
|
|
jmp write_string.next_char
|
|
|
|
.done:
|
|
ret
|
|
|
|
;------------------------------------------------------------------------------
|
|
; Install redirect trampolines. This hack allows us to redirect calls to Go
|
|
; runtime functions to the kernel's own implementation without the need to
|
|
; export/globalize any symbols. This works by first setting up a redirect table
|
|
; (populated by a post-link step) that contains the addresses of the symbol to
|
|
; hook and the address where calls to that symbol should be redirected.
|
|
;
|
|
; This function iterates the redirect table entries and for each entry it
|
|
; sets up a trampoline to the dst symbol and overwrites the code in src with
|
|
; the 14-byte long _rt0_redirect_trampoline code.
|
|
;
|
|
; Note: this code modification is only possible because we are currently
|
|
; operating in supervisor mode with no memory protection enabled. Under normal
|
|
; conditions the .text section should be flagged as read-only.
|
|
;------------------------------------------------------------------------------
|
|
_rt0_install_redirect_trampolines:
|
|
mov rax, _rt0_redirect_table
|
|
mov rdx, NUM_REDIRECTS
|
|
|
|
_rt0_install_redirect_rampolines.next:
|
|
mov rdi, [rax] ; the symbol address to hook
|
|
mov rbx, [rax+8] ; the symbol to redirect to
|
|
|
|
; setup trampoline target and copy it to the hooked symbol
|
|
mov rsi, _rt0_redirect_trampoline
|
|
mov qword [rsi+6], rbx
|
|
mov rcx, 14
|
|
rep movsb ; copy rcx bytes from rsi to rdi
|
|
|
|
add rax, 16
|
|
dec rdx
|
|
jnz _rt0_install_redirect_rampolines.next
|
|
|
|
ret
|
|
|
|
;------------------------------------------------------------------------------
|
|
; This trampoline exploits rip-relative addressing to allow a jump to a
|
|
; 64-bit address without the need to touch any registers. The generated
|
|
; code is equivalent to:
|
|
;
|
|
; jmp [rip+0]
|
|
; dq abs_address_to_jump_to
|
|
;------------------------------------------------------------------------------
|
|
_rt0_redirect_trampoline:
|
|
db 0xff ; the first 6 bytes encode a "jmp [rip+0]" instruction
|
|
db 0x25
|
|
dd 0x00
|
|
dq 0x00 ; the absolute address to jump to
|
|
|
|
;------------------------------------------------------------------------------
|
|
; The redirect table is placed in a dedicated section allowing us to easily
|
|
; find its offset in the kernel image file. As the VMA addresses of the src
|
|
; and target symbols for the redirect are now known in advance we just reserve
|
|
; enough space space for the src and dst addresses using the NUM_REDIRECTS
|
|
; define which is calculated by the Makefile and passed to nasm.
|
|
;------------------------------------------------------------------------------
|
|
section .goredirectstbl
|
|
|
|
_rt0_redirect_table:
|
|
%rep NUM_REDIRECTS
|
|
dq 0 ; src: address of the symbol we want to redirect
|
|
dq 0 ; dst: address of the symbol where calls to src are redirected to
|
|
%endrep
|
|
|
|
|