mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Install exception handlers for page faults/GPFs and provide kernel.Panic
This commit is contained in:
parent
c5aa5fe05f
commit
6e8d504ae8
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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