mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #29 from achilleasa/install-exception-handlers
Install exception handlers for page fault and GPF exceptions
This commit is contained in:
commit
37ba897ca4
3
Makefile
3
Makefile
@ -59,6 +59,7 @@ go.o:
|
||||
@echo "[objcopy] creating global symbol alias 'kernel.Kmain' for 'github.com/achilleasa/gopher-os/kernel.Kmain' in go.o"
|
||||
@objcopy \
|
||||
--add-symbol kernel.Kmain=.text:0x`nm $(BUILD_DIR)/go.o | grep "kmain.Kmain$$" | cut -d' ' -f1` \
|
||||
--globalize-symbol _rt0_interrupt_handlers \
|
||||
$(BUILD_DIR)/go.o $(BUILD_DIR)/go.o
|
||||
|
||||
binutils_version_check:
|
||||
@ -110,7 +111,7 @@ iso:
|
||||
endif
|
||||
|
||||
run: iso
|
||||
qemu-system-$(ARCH) -cdrom $(iso_target)
|
||||
qemu-system-$(ARCH) -cdrom $(iso_target) -d int,cpu_reset -no-reboot
|
||||
|
||||
gdb: iso
|
||||
qemu-system-$(ARCH) -M accel=tcg -s -S -cdrom $(iso_target) &
|
||||
|
@ -321,6 +321,7 @@ _rt0_enter_long_mode:
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
|
||||
jmp CS_SEG:.flush_gdt - PAGE_OFFSET
|
||||
.flush_gdt:
|
||||
|
@ -1,9 +1,25 @@
|
||||
; 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
|
||||
|
||||
r0_g_ptr: resq 1 ; fs:0x00 is a pointer to the current g struct
|
||||
r0_g:
|
||||
r0_g_stack_lo: resq 1
|
||||
@ -12,7 +28,6 @@ r0_g_stackguard0: resq 1 ; rsp compared to this value in go stack growth prolog
|
||||
r0_g_stackguard1: resq 1 ; rsp compared to this value in C stack growth prologue
|
||||
|
||||
section .text
|
||||
bits 64
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; Kernel 64-bit entry point
|
||||
@ -25,6 +40,8 @@ bits 64
|
||||
;------------------------------------------------------------------------------
|
||||
global _rt0_64_entry
|
||||
_rt0_64_entry:
|
||||
call _rt0_64_load_idt
|
||||
|
||||
; According to the x86_64 ABI, the fs:0 should point to the address of
|
||||
; the user-space thread structure. The actual TLS structure is located
|
||||
; just before that (aligned). Go code tries to fetch the address to the
|
||||
@ -75,6 +92,196 @@ _rt0_64_entry:
|
||||
cli
|
||||
hlt
|
||||
|
||||
|
||||
;------------------------------------------------------------------------------
|
||||
; 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
|
||||
;------------------------------------------------------------------------------
|
||||
|
23
kernel/cpu/cpu_amd64.go
Normal file
23
kernel/cpu/cpu_amd64.go
Normal file
@ -0,0 +1,23 @@
|
||||
package cpu
|
||||
|
||||
// EnableInterrupts enables interrupt handling.
|
||||
func EnableInterrupts()
|
||||
|
||||
// DisableInterrupts disables interrupt handling.
|
||||
func DisableInterrupts()
|
||||
|
||||
// Halt stops instruction execution.
|
||||
func Halt()
|
||||
|
||||
// FlushTLBEntry flushes a TLB entry for a particular virtual address.
|
||||
func FlushTLBEntry(virtAddr uintptr)
|
||||
|
||||
// SwitchPDT sets the root page table directory to point to the specified
|
||||
// physical address and flushes the TLB.
|
||||
func SwitchPDT(pdtPhysAddr uintptr)
|
||||
|
||||
// ActivePDT returns the physical address of the currently active page table.
|
||||
func ActivePDT() uintptr
|
||||
|
||||
// ReadCR2 returns the value stored in the CR2 register.
|
||||
func ReadCR2() uint64
|
33
kernel/cpu/cpu_amd64.s
Normal file
33
kernel/cpu/cpu_amd64.s
Normal file
@ -0,0 +1,33 @@
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·EnableInterrupts(SB),NOSPLIT,$0
|
||||
STI
|
||||
RET
|
||||
|
||||
TEXT ·DisableInterrupts(SB),NOSPLIT,$0
|
||||
CLI
|
||||
RET
|
||||
|
||||
TEXT ·Halt(SB),NOSPLIT,$0
|
||||
CLI
|
||||
HLT
|
||||
RET
|
||||
|
||||
TEXT ·FlushTLBEntry(SB),NOSPLIT,$0
|
||||
INVLPG virtAddr+0(FP)
|
||||
RET
|
||||
|
||||
TEXT ·SwitchPDT(SB),NOSPLIT,$0
|
||||
// loading CR3 also triggers a TLB flush
|
||||
MOVQ pdtPhysAddr+0(FP), CR3
|
||||
RET
|
||||
|
||||
TEXT ·ActivePDT(SB),NOSPLIT,$0
|
||||
MOVQ CR3, AX
|
||||
MOVQ AX, ret+0(FP)
|
||||
RET
|
||||
|
||||
TEXT ·ReadCR2(SB),NOSPLIT,$0
|
||||
MOVQ CR2, AX
|
||||
MOVQ AX, ret+0(FP)
|
||||
RET
|
41
kernel/irq/handler_amd64.go
Normal file
41
kernel/irq/handler_amd64.go
Normal file
@ -0,0 +1,41 @@
|
||||
package irq
|
||||
|
||||
// ExceptionNum defines an exception number that can be
|
||||
// passed to the HandleException and HandleExceptionWithCode
|
||||
// functions.
|
||||
type ExceptionNum uint8
|
||||
|
||||
const (
|
||||
// DoubleFault occurs when an exception is unhandled
|
||||
// or when an exception occurs while the CPU is
|
||||
// trying to call an exception handler.
|
||||
DoubleFault = ExceptionNum(8)
|
||||
|
||||
// GPFException is raised when a general protection fault occurs.
|
||||
GPFException = ExceptionNum(13)
|
||||
|
||||
// PageFaultException is raised when a PDT or
|
||||
// PDT-entry is not present or when a privilege
|
||||
// and/or RW protection check fails.
|
||||
PageFaultException = ExceptionNum(14)
|
||||
)
|
||||
|
||||
// ExceptionHandler is a function that handles an exception that does not push
|
||||
// an error code to the stack. If the handler returns, any modifications to the
|
||||
// supplied Frame and/or Regs pointers will be propagated back to the location
|
||||
// where the exception occurred.
|
||||
type ExceptionHandler func(*Frame, *Regs)
|
||||
|
||||
// ExceptionHandlerWithCode is a function that handles an exception that pushes
|
||||
// an error code to the stack. If the handler returns, any modifications to the
|
||||
// supplied Frame and/or Regs pointers will be propagated back to the location
|
||||
// where the exception occurred.
|
||||
type ExceptionHandlerWithCode func(uint64, *Frame, *Regs)
|
||||
|
||||
// HandleException registers an exception handler (without an error code) for
|
||||
// the given interrupt number.
|
||||
func HandleException(exceptionNum ExceptionNum, handler ExceptionHandler)
|
||||
|
||||
// HandleExceptionWithCode registers an exception handler (with an error code)
|
||||
// for the given interrupt number.
|
||||
func HandleExceptionWithCode(exceptionNum ExceptionNum, handler ExceptionHandlerWithCode)
|
36
kernel/irq/handler_amd64.s
Normal file
36
kernel/irq/handler_amd64.s
Normal file
@ -0,0 +1,36 @@
|
||||
#include "textflag.h"
|
||||
|
||||
// The maximum number of interrupt handlers is 256 so we need to allocate space
|
||||
// for 256 x 8-byte pointers. This symbol is made global by the Makefile so it
|
||||
// can be accessed by the gate entries defined in the rt0 assembly code.
|
||||
GLOBL _rt0_interrupt_handlers(SB), NOPTR, $2048
|
||||
|
||||
// In 64-bit mode SIDT stores 8+2 bytes for the IDT address and limit
|
||||
GLOBL _rt0_idtr<>(SB), NOPTR, $10
|
||||
|
||||
TEXT ·HandleException(SB),NOSPLIT,$0
|
||||
JMP ·HandleExceptionWithCode(SB)
|
||||
RET
|
||||
|
||||
TEXT ·HandleExceptionWithCode(SB),NOSPLIT,$0
|
||||
// Install the handler address in _rt0_interrupt_handlers
|
||||
LEAQ _rt0_interrupt_handlers+0(SB), CX
|
||||
MOVBQZX exceptionNum+0(FP), AX // exceptionNum is a uint8 so we zero-extend it to 64bits
|
||||
MOVQ handler+8(FP), BX
|
||||
MOVQ 0(BX), BX // dereference pointer to handler fn
|
||||
MOVQ BX, (CX)(AX*8)
|
||||
|
||||
// To enable the handler we need to lookup the appropriate IDT entry
|
||||
// and modify its type/attribute byte. To acquire the IDT base address
|
||||
// we use the SIDT instruction.
|
||||
MOVQ IDTR, _rt0_idtr<>+0(SB)
|
||||
LEAQ _rt0_idtr<>(SB), CX
|
||||
MOVQ 2(CX), CX // CX points to IDT base address
|
||||
SHLQ $4, AX // Each IDT entry uses 16 bytes so we multiply num by 16
|
||||
ADDQ AX, CX // and add it to CX to get the address of the IDT entry
|
||||
// we want to tweak
|
||||
|
||||
MOVB $0x8e, 5(CX) // 32/64-bit ring-0 interrupt gate that is present
|
||||
// see: http://wiki.osdev.org/Interrupt_Descriptor_Table
|
||||
|
||||
RET
|
51
kernel/irq/interrupt_amd64.go
Normal file
51
kernel/irq/interrupt_amd64.go
Normal file
@ -0,0 +1,51 @@
|
||||
package irq
|
||||
|
||||
import "github.com/achilleasa/gopher-os/kernel/kfmt/early"
|
||||
|
||||
// Regs contains a snapshot of the register values when an interrupt occurred.
|
||||
type Regs struct {
|
||||
RAX uint64
|
||||
RBX uint64
|
||||
RCX uint64
|
||||
RDX uint64
|
||||
RSI uint64
|
||||
RDI uint64
|
||||
RBP uint64
|
||||
R8 uint64
|
||||
R9 uint64
|
||||
R10 uint64
|
||||
R11 uint64
|
||||
R12 uint64
|
||||
R13 uint64
|
||||
R14 uint64
|
||||
R15 uint64
|
||||
}
|
||||
|
||||
// Print outputs a dump of the register values to the active console.
|
||||
func (r *Regs) Print() {
|
||||
early.Printf("RAX = %16x RBX = %16x\n", r.RAX, r.RBX)
|
||||
early.Printf("RCX = %16x RDX = %16x\n", r.RCX, r.RDX)
|
||||
early.Printf("RSI = %16x RDI = %16x\n", r.RSI, r.RDI)
|
||||
early.Printf("RBP = %16x\n", r.RBP)
|
||||
early.Printf("R8 = %16x R9 = %16x\n", r.R8, r.R9)
|
||||
early.Printf("R10 = %16x R11 = %16x\n", r.R10, r.R11)
|
||||
early.Printf("R12 = %16x R13 = %16x\n", r.R12, r.R13)
|
||||
early.Printf("R14 = %16x R15 = %16x\n", r.R14, r.R15)
|
||||
}
|
||||
|
||||
// Frame describes an exception frame that is automatically pushed by the CPU
|
||||
// to the stack when an exception occurs.
|
||||
type Frame struct {
|
||||
RIP uint64
|
||||
CS uint64
|
||||
RFlags uint64
|
||||
RSP uint64
|
||||
SS uint64
|
||||
}
|
||||
|
||||
// Print outputs a dump of the exception frame to the active console.
|
||||
func (f *Frame) Print() {
|
||||
early.Printf("RIP = %16x CS = %16x\n", f.RIP, f.CS)
|
||||
early.Printf("RSP = %16x SS = %16x\n", f.RSP, f.SS)
|
||||
early.Printf("RFL = %16x\n", f.RFlags)
|
||||
}
|
84
kernel/irq/interrupt_amd64_test.go
Normal file
84
kernel/irq/interrupt_amd64_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package irq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||
)
|
||||
|
||||
func TestRegsPrint(t *testing.T) {
|
||||
fb := mockTTY()
|
||||
regs := Regs{
|
||||
RAX: 1,
|
||||
RBX: 2,
|
||||
RCX: 3,
|
||||
RDX: 4,
|
||||
RSI: 5,
|
||||
RDI: 6,
|
||||
RBP: 7,
|
||||
R8: 8,
|
||||
R9: 9,
|
||||
R10: 10,
|
||||
R11: 11,
|
||||
R12: 12,
|
||||
R13: 13,
|
||||
R14: 14,
|
||||
R15: 15,
|
||||
}
|
||||
regs.Print()
|
||||
|
||||
exp := "RAX = 0000000000000001 RBX = 0000000000000002\nRCX = 0000000000000003 RDX = 0000000000000004\nRSI = 0000000000000005 RDI = 0000000000000006\nRBP = 0000000000000007\nR8 = 0000000000000008 R9 = 0000000000000009\nR10 = 000000000000000a R11 = 000000000000000b\nR12 = 000000000000000c R13 = 000000000000000d\nR14 = 000000000000000e R15 = 000000000000000f"
|
||||
|
||||
if got := readTTY(fb); got != exp {
|
||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFramePrint(t *testing.T) {
|
||||
fb := mockTTY()
|
||||
frame := Frame{
|
||||
RIP: 1,
|
||||
CS: 2,
|
||||
RFlags: 3,
|
||||
RSP: 4,
|
||||
SS: 5,
|
||||
}
|
||||
frame.Print()
|
||||
|
||||
exp := "RIP = 0000000000000001 CS = 0000000000000002\nRSP = 0000000000000004 SS = 0000000000000005\nRFL = 0000000000000003"
|
||||
|
||||
if got := readTTY(fb); got != exp {
|
||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func readTTY(fb []byte) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(fb); i += 2 {
|
||||
ch := fb[i]
|
||||
if ch == 0 {
|
||||
if i+2 < len(fb) && fb[i+2] != 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteByte(ch)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func mockTTY() []byte {
|
||||
// Mock a tty to handle early.Printf output
|
||||
mockConsoleFb := make([]byte, 160*25)
|
||||
mockConsole := &console.Ega{}
|
||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
||||
|
||||
return mockConsoleFb
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package kmain
|
||||
|
||||
import (
|
||||
"github.com/achilleasa/gopher-os/kernel"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal/multiboot"
|
||||
"github.com/achilleasa/gopher-os/kernel/kfmt/early"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem/pmm/allocator"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem/vmm"
|
||||
)
|
||||
|
||||
// Kmain is the only Go symbol that is visible (exported) from the rt0 initialization
|
||||
@ -24,7 +25,10 @@ func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) {
|
||||
hal.InitTerminal()
|
||||
hal.ActiveTerminal.Clear()
|
||||
|
||||
if err := allocator.Init(kernelStart, kernelEnd); err != nil {
|
||||
early.Printf("[%s] error: %s\n", err.Module, err.Message)
|
||||
var err *kernel.Error
|
||||
if err = allocator.Init(kernelStart, kernelEnd); err != nil {
|
||||
kernel.Panic(err)
|
||||
} else if err = vmm.Init(); err != nil {
|
||||
kernel.Panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel"
|
||||
"github.com/achilleasa/gopher-os/kernel/cpu"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||
)
|
||||
@ -18,7 +19,7 @@ var (
|
||||
|
||||
// flushTLBEntryFn is used by tests to override calls to flushTLBEntry
|
||||
// which will cause a fault if called in user-mode.
|
||||
flushTLBEntryFn = flushTLBEntry
|
||||
flushTLBEntryFn = cpu.FlushTLBEntry
|
||||
|
||||
errNoHugePageSupport = &kernel.Error{Module: "vmm", Message: "huge pages are not supported"}
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel"
|
||||
"github.com/achilleasa/gopher-os/kernel/cpu"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||
)
|
||||
@ -11,11 +12,11 @@ import (
|
||||
var (
|
||||
// activePDTFn is used by tests to override calls to activePDT which
|
||||
// will cause a fault if called in user-mode.
|
||||
activePDTFn = activePDT
|
||||
activePDTFn = cpu.ActivePDT
|
||||
|
||||
// switchPDTFn is used by tests to override calls to switchPDT which
|
||||
// will cause a fault if called in user-mode.
|
||||
switchPDTFn = switchPDT
|
||||
switchPDTFn = cpu.SwitchPDT
|
||||
|
||||
// mapFn is used by tests and is automatically inlined by the compiler.
|
||||
mapFn = Map
|
||||
|
@ -1,11 +0,0 @@
|
||||
package vmm
|
||||
|
||||
// flushTLBEntry flushes a TLB entry for a particular virtual address.
|
||||
func flushTLBEntry(virtAddr uintptr)
|
||||
|
||||
// switchPDT sets the root page table directory to point to the specified
|
||||
// physical address and flushes the TLB.
|
||||
func switchPDT(pdtPhysAddr uintptr)
|
||||
|
||||
// activePDT returns the physical address of the currently active page table.
|
||||
func activePDT() uintptr
|
@ -1,15 +0,0 @@
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·flushTLBEntry(SB),NOSPLIT,$0
|
||||
INVLPG virtAddr+0(FP)
|
||||
RET
|
||||
|
||||
TEXT ·switchPDT(SB),NOSPLIT,$0
|
||||
// loading CR3 also triggers a TLB flush
|
||||
MOVQ pdtPhysAddr+0(FP), CR3
|
||||
RET
|
||||
|
||||
TEXT ·activePDT(SB),NOSPLIT,$0
|
||||
MOVQ CR3, AX
|
||||
MOVQ AX, ret+0(FP)
|
||||
RET
|
@ -2,10 +2,23 @@ package vmm
|
||||
|
||||
import (
|
||||
"github.com/achilleasa/gopher-os/kernel"
|
||||
"github.com/achilleasa/gopher-os/kernel/cpu"
|
||||
"github.com/achilleasa/gopher-os/kernel/irq"
|
||||
"github.com/achilleasa/gopher-os/kernel/kfmt/early"
|
||||
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
|
||||
)
|
||||
|
||||
var frameAllocator FrameAllocatorFn
|
||||
var (
|
||||
// frameAllocator points to a frame allocator function registered using
|
||||
// SetFrameAllocator.
|
||||
frameAllocator FrameAllocatorFn
|
||||
|
||||
// the following functions are mocked by tests and are automatically
|
||||
// inlined by the compiler.
|
||||
panicFn = kernel.Panic
|
||||
handleExceptionWithCodeFn = irq.HandleExceptionWithCode
|
||||
readCR2Fn = cpu.ReadCR2
|
||||
)
|
||||
|
||||
// FrameAllocatorFn is a function that can allocate physical frames.
|
||||
type FrameAllocatorFn func() (pmm.Frame, *kernel.Error)
|
||||
@ -15,3 +28,51 @@ type FrameAllocatorFn func() (pmm.Frame, *kernel.Error)
|
||||
func SetFrameAllocator(allocFn FrameAllocatorFn) {
|
||||
frameAllocator = allocFn
|
||||
}
|
||||
|
||||
func pageFaultHandler(errorCode uint64, frame *irq.Frame, regs *irq.Regs) {
|
||||
early.Printf("\nPage fault while accessing address: 0x%16x\nReason: ", readCR2Fn())
|
||||
switch {
|
||||
case errorCode == 0:
|
||||
early.Printf("read from non-present page")
|
||||
case errorCode == 1:
|
||||
early.Printf("page protection violation (read)")
|
||||
case errorCode == 2:
|
||||
early.Printf("write to non-present page")
|
||||
case errorCode == 3:
|
||||
early.Printf("page protection violation (write)")
|
||||
case errorCode == 4:
|
||||
early.Printf("page-fault in user-mode")
|
||||
case errorCode == 8:
|
||||
early.Printf("page table has reserved bit set")
|
||||
case errorCode == 16:
|
||||
early.Printf("instruction fetch")
|
||||
default:
|
||||
early.Printf("unknown")
|
||||
}
|
||||
|
||||
early.Printf("\n\nRegisters:\n")
|
||||
regs.Print()
|
||||
frame.Print()
|
||||
|
||||
// TODO: Revisit this when user-mode tasks are implemented
|
||||
panicFn(nil)
|
||||
}
|
||||
|
||||
func generalProtectionFaultHandler(_ uint64, frame *irq.Frame, regs *irq.Regs) {
|
||||
early.Printf("\nGeneral protection fault while accessing address: 0x%x\n", readCR2Fn())
|
||||
early.Printf("Registers:\n")
|
||||
regs.Print()
|
||||
frame.Print()
|
||||
|
||||
// TODO: Revisit this when user-mode tasks are implemented
|
||||
panicFn(nil)
|
||||
}
|
||||
|
||||
// Init initializes the vmm system and installs paging-related exception
|
||||
// handlers.
|
||||
func Init() *kernel.Error {
|
||||
handleExceptionWithCodeFn(irq.PageFaultException, pageFaultHandler)
|
||||
handleExceptionWithCodeFn(irq.GPFException, generalProtectionFaultHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
169
kernel/mem/vmm/vmm_test.go
Normal file
169
kernel/mem/vmm/vmm_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
package vmm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel"
|
||||
"github.com/achilleasa/gopher-os/kernel/cpu"
|
||||
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||
"github.com/achilleasa/gopher-os/kernel/irq"
|
||||
)
|
||||
|
||||
func TestPageFaultHandler(t *testing.T) {
|
||||
defer func() {
|
||||
panicFn = kernel.Panic
|
||||
readCR2Fn = cpu.ReadCR2
|
||||
}()
|
||||
|
||||
specs := []struct {
|
||||
errCode uint64
|
||||
expReason string
|
||||
expPanic bool
|
||||
}{
|
||||
{
|
||||
0,
|
||||
"read from non-present page",
|
||||
true,
|
||||
},
|
||||
{
|
||||
1,
|
||||
"page protection violation (read)",
|
||||
true,
|
||||
},
|
||||
{
|
||||
2,
|
||||
"write to non-present page",
|
||||
true,
|
||||
},
|
||||
{
|
||||
3,
|
||||
"page protection violation (write)",
|
||||
true,
|
||||
},
|
||||
{
|
||||
4,
|
||||
"page-fault in user-mode",
|
||||
true,
|
||||
},
|
||||
{
|
||||
8,
|
||||
"page table has reserved bit set",
|
||||
true,
|
||||
},
|
||||
{
|
||||
16,
|
||||
"instruction fetch",
|
||||
true,
|
||||
},
|
||||
{
|
||||
0xf00,
|
||||
"unknown",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
regs irq.Regs
|
||||
frame irq.Frame
|
||||
)
|
||||
|
||||
readCR2Fn = func() uint64 {
|
||||
return 0xbadf00d000
|
||||
}
|
||||
|
||||
panicCalled := false
|
||||
panicFn = func(_ *kernel.Error) {
|
||||
panicCalled = true
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
fb := mockTTY()
|
||||
panicCalled = false
|
||||
|
||||
pageFaultHandler(spec.errCode, &frame, ®s)
|
||||
if got := readTTY(fb); !strings.Contains(got, spec.expReason) {
|
||||
t.Errorf("[spec %d] expected reason %q; got output:\n%q", specIndex, spec.expReason, got)
|
||||
continue
|
||||
}
|
||||
|
||||
if spec.expPanic != panicCalled {
|
||||
t.Errorf("[spec %d] expected panic %t; got %t", specIndex, spec.expPanic, panicCalled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGPtHandler(t *testing.T) {
|
||||
defer func() {
|
||||
panicFn = kernel.Panic
|
||||
readCR2Fn = cpu.ReadCR2
|
||||
}()
|
||||
|
||||
var (
|
||||
regs irq.Regs
|
||||
frame irq.Frame
|
||||
fb = mockTTY()
|
||||
)
|
||||
|
||||
readCR2Fn = func() uint64 {
|
||||
return 0xbadf00d000
|
||||
}
|
||||
|
||||
panicCalled := false
|
||||
panicFn = func(_ *kernel.Error) {
|
||||
panicCalled = true
|
||||
}
|
||||
|
||||
generalProtectionFaultHandler(0, &frame, ®s)
|
||||
|
||||
exp := "\nGeneral protection fault while accessing address: 0xbadf00d000\nRegisters:\nRAX = 0000000000000000 RBX = 0000000000000000\nRCX = 0000000000000000 RDX = 0000000000000000\nRSI = 0000000000000000 RDI = 0000000000000000\nRBP = 0000000000000000\nR8 = 0000000000000000 R9 = 0000000000000000\nR10 = 0000000000000000 R11 = 0000000000000000\nR12 = 0000000000000000 R13 = 0000000000000000\nR14 = 0000000000000000 R15 = 0000000000000000\nRIP = 0000000000000000 CS = 0000000000000000\nRSP = 0000000000000000 SS = 0000000000000000\nRFL = 0000000000000000"
|
||||
if got := readTTY(fb); got != exp {
|
||||
t.Errorf("expected output:\n%q\ngot:\n%q", exp, got)
|
||||
}
|
||||
|
||||
if !panicCalled {
|
||||
t.Error("expected kernel.Panic to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
defer func() {
|
||||
handleExceptionWithCodeFn = irq.HandleExceptionWithCode
|
||||
}()
|
||||
|
||||
handleExceptionWithCodeFn = func(_ irq.ExceptionNum, _ irq.ExceptionHandlerWithCode) {}
|
||||
|
||||
if err := Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func readTTY(fb []byte) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(fb); i += 2 {
|
||||
ch := fb[i]
|
||||
if ch == 0 {
|
||||
if i+2 < len(fb) && fb[i+2] != 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteByte(ch)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func mockTTY() []byte {
|
||||
// Mock a tty to handle early.Printf output
|
||||
mockConsoleFb := make([]byte, 160*25)
|
||||
mockConsole := &console.Ega{}
|
||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
||||
|
||||
return mockConsoleFb
|
||||
}
|
24
kernel/panic.go
Normal file
24
kernel/panic.go
Normal file
@ -0,0 +1,24 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"github.com/achilleasa/gopher-os/kernel/cpu"
|
||||
"github.com/achilleasa/gopher-os/kernel/kfmt/early"
|
||||
)
|
||||
|
||||
var (
|
||||
// cpuHaltFn is mocked by tests and is automatically inlined by the compiler.
|
||||
cpuHaltFn = cpu.Halt
|
||||
)
|
||||
|
||||
// Panic outputs the supplied error (if not nil) to the console and halts the
|
||||
// CPU. Calls to Panic never return.
|
||||
func Panic(err *Error) {
|
||||
early.Printf("\n-----------------------------------\n")
|
||||
if err != nil {
|
||||
early.Printf("[%s] unrecoverable error: %s\n", err.Module, err.Message)
|
||||
}
|
||||
early.Printf("*** kernel panic: system halted ***")
|
||||
early.Printf("\n-----------------------------------\n")
|
||||
|
||||
cpuHaltFn()
|
||||
}
|
84
kernel/panic_test.go
Normal file
84
kernel/panic_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/achilleasa/gopher-os/kernel/cpu"
|
||||
"github.com/achilleasa/gopher-os/kernel/driver/video/console"
|
||||
"github.com/achilleasa/gopher-os/kernel/hal"
|
||||
)
|
||||
|
||||
func TestPanic(t *testing.T) {
|
||||
defer func() {
|
||||
cpuHaltFn = cpu.Halt
|
||||
}()
|
||||
|
||||
var cpuHaltCalled bool
|
||||
cpuHaltFn = func() {
|
||||
cpuHaltCalled = true
|
||||
}
|
||||
|
||||
t.Run("with error", func(t *testing.T) {
|
||||
cpuHaltCalled = false
|
||||
fb := mockTTY()
|
||||
err := &Error{Module: "test", Message: "panic test"}
|
||||
|
||||
Panic(err)
|
||||
|
||||
exp := "\n-----------------------------------\n[test] unrecoverable error: panic test\n*** kernel panic: system halted ***\n-----------------------------------"
|
||||
|
||||
if got := readTTY(fb); got != exp {
|
||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||
}
|
||||
|
||||
if !cpuHaltCalled {
|
||||
t.Fatal("expected cpu.Halt() to be called by Panic")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("without error", func(t *testing.T) {
|
||||
cpuHaltCalled = false
|
||||
fb := mockTTY()
|
||||
|
||||
Panic(nil)
|
||||
|
||||
exp := "\n-----------------------------------\n*** kernel panic: system halted ***\n-----------------------------------"
|
||||
|
||||
if got := readTTY(fb); got != exp {
|
||||
t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got)
|
||||
}
|
||||
|
||||
if !cpuHaltCalled {
|
||||
t.Fatal("expected cpu.Halt() to be called by Panic")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func readTTY(fb []byte) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(fb); i += 2 {
|
||||
ch := fb[i]
|
||||
if ch == 0 {
|
||||
if i+2 < len(fb) && fb[i+2] != 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteByte(ch)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func mockTTY() []byte {
|
||||
// Mock a tty to handle early.Printf output
|
||||
mockConsoleFb := make([]byte, 160*25)
|
||||
mockConsole := &console.Ega{}
|
||||
mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0])))
|
||||
hal.ActiveTerminal.AttachTo(mockConsole)
|
||||
|
||||
return mockConsoleFb
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user