1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00
gopher-os/kernel/mem/vmm/vmm_test.go
Achilleas Anagnostopoulos 5fc6ce188e Use go:redirect-from directive to map panic to kernel.Panic
All calls (but one) to kernel.Panic have been replaced by calls to
panic. A call to kernel.Panic is still required to prevent the compiler
from treating kernel.Panic as dead code and eliminating it.
2017-06-25 21:39:56 +01:00

283 lines
7.0 KiB
Go

package vmm
import (
"bytes"
"fmt"
"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"
"github.com/achilleasa/gopher-os/kernel/mem"
"github.com/achilleasa/gopher-os/kernel/mem/pmm"
)
func TestRecoverablePageFault(t *testing.T) {
var (
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
readCR2Fn = cpu.ReadCR2
frameAllocator = nil
mapTemporaryFn = MapTemporary
unmapFn = Unmap
flushTLBEntryFn = cpu.FlushTLBEntry
}(ptePtrFn)
specs := []struct {
pteFlags PageTableEntryFlag
allocError *kernel.Error
mapError *kernel.Error
expPanic bool
}{
// Missing pge
{0, nil, nil, true},
// Page is present but CoW flag not set
{FlagPresent, nil, nil, true},
// Page is present but both CoW and RW flags set
{FlagPresent | FlagRW | FlagCopyOnWrite, nil, nil, true},
// Page is present with CoW flag set but allocating a page copy fails
{FlagPresent | FlagCopyOnWrite, err, nil, true},
// Page is present with CoW flag set but mapping the page copy fails
{FlagPresent | FlagCopyOnWrite, nil, err, true},
// Page is present with CoW flag set
{FlagPresent | FlagCopyOnWrite, nil, nil, false},
}
mockTTY()
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 {
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++ {
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, &regs)
})
}
}
func TestNonRecoverablePageFault(t *testing.T) {
specs := []struct {
errCode uint64
expReason string
}{
{
0,
"read from non-present page",
},
{
1,
"page protection violation (read)",
},
{
2,
"write to non-present page",
},
{
3,
"page protection violation (write)",
},
{
4,
"page-fault in user-mode",
},
{
8,
"page table has reserved bit set",
},
{
16,
"instruction fetch",
},
{
0xf00,
"unknown",
},
}
var (
regs irq.Regs
frame irq.Frame
)
for specIndex, spec := range specs {
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, &regs, 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() {
readCR2Fn = cpu.ReadCR2
}()
var (
regs irq.Regs
frame irq.Frame
)
readCR2Fn = func() uint64 {
return 0xbadf00d000
}
defer func() {
if err := recover(); err != errUnrecoverableFault {
t.Errorf("expected a panic with errUnrecoverableFault; got %v", err)
}
}()
mockTTY()
generalProtectionFaultHandler(0, &frame, &regs)
}
func TestInit(t *testing.T) {
defer func() {
frameAllocator = nil
mapTemporaryFn = MapTemporary
unmapFn = Unmap
handleExceptionWithCodeFn = irq.HandleExceptionWithCode
}()
// reserve space for an allocated page
reservedPage := make([]byte, mem.PageSize)
t.Run("success", func(t *testing.T) {
// fill page with junk
for i := 0; i < len(reservedPage); i++ {
reservedPage[i] = byte(i % 256)
}
SetFrameAllocator(func() (pmm.Frame, *kernel.Error) {
addr := uintptr(unsafe.Pointer(&reservedPage[0]))
return pmm.Frame(addr >> mem.PageShift), nil
})
unmapFn = func(p Page) *kernel.Error { return nil }
mapTemporaryFn = func(f pmm.Frame) (Page, *kernel.Error) { return Page(f), nil }
handleExceptionWithCodeFn = func(_ irq.ExceptionNum, _ irq.ExceptionHandlerWithCode) {}
if err := Init(); err != nil {
t.Fatal(err)
}
// reserved page should be zeroed
for i := 0; i < len(reservedPage); i++ {
if reservedPage[i] != 0 {
t.Errorf("expected reserved page to be zeroed; got byte %d at index %d", reservedPage[i], i)
}
}
})
t.Run("blank page allocation error", func(t *testing.T) {
expErr := &kernel.Error{Module: "test", Message: "out of memory"}
SetFrameAllocator(func() (pmm.Frame, *kernel.Error) { return pmm.InvalidFrame, expErr })
unmapFn = func(p Page) *kernel.Error { return nil }
mapTemporaryFn = func(f pmm.Frame) (Page, *kernel.Error) { return Page(f), nil }
handleExceptionWithCodeFn = func(_ irq.ExceptionNum, _ irq.ExceptionHandlerWithCode) {}
if err := Init(); err != expErr {
t.Fatalf("expected error: %v; got %v", expErr, err)
}
})
t.Run("blank page mapping error", func(t *testing.T) {
expErr := &kernel.Error{Module: "test", Message: "map failed"}
SetFrameAllocator(func() (pmm.Frame, *kernel.Error) {
addr := uintptr(unsafe.Pointer(&reservedPage[0]))
return pmm.Frame(addr >> mem.PageShift), nil
})
unmapFn = func(p Page) *kernel.Error { return nil }
mapTemporaryFn = func(f pmm.Frame) (Page, *kernel.Error) { return Page(f), expErr }
handleExceptionWithCodeFn = func(_ irq.ExceptionNum, _ irq.ExceptionHandlerWithCode) {}
if err := Init(); err != expErr {
t.Fatalf("expected error: %v; got %v", expErr, 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
}