diff --git a/kernel/kmain/kmain.go b/kernel/kmain/kmain.go index 97347fe..2c59342 100644 --- a/kernel/kmain/kmain.go +++ b/kernel/kmain/kmain.go @@ -8,6 +8,10 @@ import ( "github.com/achilleasa/gopher-os/kernel/mem/vmm" ) +var ( + errKmainReturned = &kernel.Error{Module: "kmain", Message: "Kmain returned"} +) + // Kmain is the only Go symbol that is visible (exported) from the rt0 initialization // code. This function is invoked by the rt0 assembly code after setting up the GDT // and setting up a a minimal g0 struct that allows Go code using the 4K stack @@ -27,8 +31,12 @@ func Kmain(multibootInfoPtr, kernelStart, kernelEnd uintptr) { var err *kernel.Error if err = allocator.Init(kernelStart, kernelEnd); err != nil { - kernel.Panic(err) + panic(err) } else if err = vmm.Init(); err != nil { - kernel.Panic(err) + panic(err) } + + // Use kernel.Panic instead of panic to prevent the compiler from + // treating kernel.Panic as dead-code and eliminating it. + kernel.Panic(errKmainReturned) } diff --git a/kernel/mem/vmm/vmm.go b/kernel/mem/vmm/vmm.go index b698f5c..9044489 100644 --- a/kernel/mem/vmm/vmm.go +++ b/kernel/mem/vmm/vmm.go @@ -16,9 +16,10 @@ var ( // the following functions are mocked by tests and are automatically // inlined by the compiler. - panicFn = kernel.Panic handleExceptionWithCodeFn = irq.HandleExceptionWithCode readCR2Fn = cpu.ReadCR2 + + errUnrecoverableFault = &kernel.Error{Module: "vmm", Message: "page/gpf fault"} ) // FrameAllocatorFn is a function that can allocate physical frames. @@ -78,7 +79,7 @@ func pageFaultHandler(errorCode uint64, frame *irq.Frame, regs *irq.Regs) { } } - nonRecoverablePageFault(faultAddress, errorCode, frame, regs, nil) + nonRecoverablePageFault(faultAddress, errorCode, frame, regs, errUnrecoverableFault) } func nonRecoverablePageFault(faultAddress uintptr, errorCode uint64, frame *irq.Frame, regs *irq.Regs, err *kernel.Error) { @@ -107,7 +108,7 @@ func nonRecoverablePageFault(faultAddress uintptr, errorCode uint64, frame *irq. frame.Print() // TODO: Revisit this when user-mode tasks are implemented - panicFn(err) + panic(err) } func generalProtectionFaultHandler(_ uint64, frame *irq.Frame, regs *irq.Regs) { @@ -117,7 +118,7 @@ func generalProtectionFaultHandler(_ uint64, frame *irq.Frame, regs *irq.Regs) { frame.Print() // TODO: Revisit this when user-mode tasks are implemented - panicFn(nil) + panic(errUnrecoverableFault) } // reserveZeroedFrame reserves a physical frame to be used together with diff --git a/kernel/mem/vmm/vmm_test.go b/kernel/mem/vmm/vmm_test.go index f4ecbb0..030c922 100644 --- a/kernel/mem/vmm/vmm_test.go +++ b/kernel/mem/vmm/vmm_test.go @@ -2,6 +2,7 @@ package vmm import ( "bytes" + "fmt" "strings" "testing" "unsafe" @@ -17,18 +18,16 @@ import ( func TestRecoverablePageFault(t *testing.T) { var ( - frame irq.Frame - regs irq.Regs - panicCalled bool - pageEntry pageTableEntry - origPage = make([]byte, mem.PageSize) - clonedPage = make([]byte, mem.PageSize) - err = &kernel.Error{Module: "test", Message: "something went wrong"} + frame irq.Frame + regs irq.Regs + pageEntry pageTableEntry + origPage = make([]byte, mem.PageSize) + clonedPage = make([]byte, mem.PageSize) + err = &kernel.Error{Module: "test", Message: "something went wrong"} ) defer func(origPtePtr func(uintptr) unsafe.Pointer) { ptePtrFn = origPtePtr - panicFn = kernel.Panic readCR2Fn = cpu.ReadCR2 frameAllocator = nil mapTemporaryFn = MapTemporary @@ -58,97 +57,87 @@ func TestRecoverablePageFault(t *testing.T) { mockTTY() - panicFn = func(_ *kernel.Error) { - panicCalled = true - } - ptePtrFn = func(entry uintptr) unsafe.Pointer { return unsafe.Pointer(&pageEntry) } readCR2Fn = func() uint64 { return uint64(uintptr(unsafe.Pointer(&origPage[0]))) } unmapFn = func(_ Page) *kernel.Error { return nil } flushTLBEntryFn = func(_ uintptr) {} for specIndex, spec := range specs { - mapTemporaryFn = func(f pmm.Frame) (Page, *kernel.Error) { return Page(f), spec.mapError } - SetFrameAllocator(func() (pmm.Frame, *kernel.Error) { - addr := uintptr(unsafe.Pointer(&clonedPage[0])) - return pmm.Frame(addr >> mem.PageShift), spec.allocError - }) + t.Run(fmt.Sprint(specIndex), func(t *testing.T) { + defer func() { + err := recover() + if spec.expPanic && err == nil { + t.Error("expected a panic") + } else if !spec.expPanic { + if err != nil { + t.Error("unexpected panic") + return + } - for i := 0; i < len(origPage); i++ { - origPage[i] = byte(i % 256) - clonedPage[i] = 0 - } - - panicCalled = false - pageEntry = 0 - pageEntry.SetFlags(spec.pteFlags) - - pageFaultHandler(2, &frame, ®s) - - if spec.expPanic != panicCalled { - t.Errorf("[spec %d] expected panic %t; got %t", specIndex, spec.expPanic, panicCalled) - } - - if !spec.expPanic { - for i := 0; i < len(origPage); i++ { - if origPage[i] != clonedPage[i] { - t.Errorf("[spec %d] expected clone page to be a copy of the original page; mismatch at index %d", specIndex, i) + for i := 0; i < len(origPage); i++ { + if origPage[i] != clonedPage[i] { + t.Errorf("expected clone page to be a copy of the original page; mismatch at index %d", i) + } + } } + }() + + mapTemporaryFn = func(f pmm.Frame) (Page, *kernel.Error) { return Page(f), spec.mapError } + SetFrameAllocator(func() (pmm.Frame, *kernel.Error) { + addr := uintptr(unsafe.Pointer(&clonedPage[0])) + return pmm.Frame(addr >> mem.PageShift), spec.allocError + }) + + for i := 0; i < len(origPage); i++ { + origPage[i] = byte(i % 256) + clonedPage[i] = 0 } - } + + pageEntry = 0 + pageEntry.SetFlags(spec.pteFlags) + + pageFaultHandler(2, &frame, ®s) + }) } } func TestNonRecoverablePageFault(t *testing.T) { - defer func() { - panicFn = kernel.Panic - }() - 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, }, } @@ -157,58 +146,45 @@ func TestNonRecoverablePageFault(t *testing.T) { frame irq.Frame ) - panicCalled := false - panicFn = func(_ *kernel.Error) { - panicCalled = true - } - for specIndex, spec := range specs { - fb := mockTTY() - panicCalled = false + t.Run(fmt.Sprint(specIndex), func(t *testing.T) { + defer func() { + if err := recover(); err != errUnrecoverableFault { + t.Errorf("expected a panic with errUnrecoverableFault; got %v", err) + } + }() + fb := mockTTY() - nonRecoverablePageFault(0xbadf00d000, spec.errCode, &frame, ®s, nil) - 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) - } + nonRecoverablePageFault(0xbadf00d000, spec.errCode, &frame, ®s, errUnrecoverableFault) + if got := readTTY(fb); !strings.Contains(got, spec.expReason) { + t.Errorf("expected reason %q; got output:\n%q", spec.expReason, got) + } + }) } } 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 - } + defer func() { + if err := recover(); err != errUnrecoverableFault { + t.Errorf("expected a panic with errUnrecoverableFault; got %v", err) + } + }() + mockTTY() 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) { diff --git a/kernel/panic.go b/kernel/panic.go index b94cfae..b05e198 100644 --- a/kernel/panic.go +++ b/kernel/panic.go @@ -8,11 +8,28 @@ import ( var ( // cpuHaltFn is mocked by tests and is automatically inlined by the compiler. cpuHaltFn = cpu.Halt + + errRuntimePanic = &Error{Module: "rt", Message: "unknown cause"} ) // Panic outputs the supplied error (if not nil) to the console and halts the -// CPU. Calls to Panic never return. -func Panic(err *Error) { +// CPU. Calls to Panic never return. Panic also works as a redirection target +// for calls to panic() (resolved via runtime.gopanic) +//go:redirect-from runtime.gopanic +func Panic(e interface{}) { + var err *Error + + switch t := e.(type) { + case *Error: + err = t + case string: + errRuntimePanic.Message = t + err = errRuntimePanic + case error: + errRuntimePanic.Message = t.Error() + err = errRuntimePanic + } + early.Printf("\n-----------------------------------\n") if err != nil { early.Printf("[%s] unrecoverable error: %s\n", err.Module, err.Message) diff --git a/kernel/panic_test.go b/kernel/panic_test.go index 0339129..6e1e6bb 100644 --- a/kernel/panic_test.go +++ b/kernel/panic_test.go @@ -2,6 +2,7 @@ package kernel import ( "bytes" + "errors" "testing" "unsafe" @@ -20,7 +21,7 @@ func TestPanic(t *testing.T) { cpuHaltCalled = true } - t.Run("with error", func(t *testing.T) { + t.Run("with *kernel.Error", func(t *testing.T) { cpuHaltCalled = false fb := mockTTY() err := &Error{Module: "test", Message: "panic test"} @@ -38,6 +39,42 @@ func TestPanic(t *testing.T) { } }) + t.Run("with error", func(t *testing.T) { + cpuHaltCalled = false + fb := mockTTY() + err := errors.New("go error") + + Panic(err) + + exp := "\n-----------------------------------\n[rt] unrecoverable error: go error\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("with string", func(t *testing.T) { + cpuHaltCalled = false + fb := mockTTY() + err := "string error" + + Panic(err) + + exp := "\n-----------------------------------\n[rt] unrecoverable error: string error\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()