mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
Merge pull request #31 from achilleasa/trampoline-based-runtime-call-redirects
Trampoline-based runtime call redirects
This commit is contained in:
commit
2ea1f43d0e
10
Makefile
10
Makefile
@ -21,7 +21,7 @@ GOARCH := amd64
|
||||
GOROOT := $(shell $(GO) env GOROOT)
|
||||
|
||||
LD_FLAGS := -n -T $(BUILD_DIR)/linker.ld -static --no-ld-generated-unwind-info
|
||||
AS_FLAGS := -g -f elf64 -F dwarf -I arch/$(ARCH)/asm/
|
||||
AS_FLAGS := -g -f elf64 -F dwarf -I arch/$(ARCH)/asm/ -dNUM_REDIRECTS=$(shell $(GO) run tools/redirects/redirects.go count)
|
||||
|
||||
MIN_OBJCOPY_VERSION := 2.26.0
|
||||
HAVE_VALID_OBJCOPY := $(shell objcopy -V | head -1 | awk -F ' ' '{print "$(MIN_OBJCOPY_VERSION)\n" $$NF}' | sort -ct. -k1,1n -k2,2n && echo "y")
|
||||
@ -31,7 +31,11 @@ asm_obj_files := $(patsubst arch/$(ARCH)/asm/%.s, $(BUILD_DIR)/arch/$(ARCH)/asm/
|
||||
|
||||
.PHONY: kernel iso clean binutils_version_check
|
||||
|
||||
kernel: binutils_version_check $(kernel_target)
|
||||
kernel: binutils_version_check kernel_image
|
||||
|
||||
kernel_image: $(kernel_target)
|
||||
@echo "[tools:redirects] populating kernel image redirect table"
|
||||
@$(GO) run tools/redirects/redirects.go populate-table $(kernel_target)
|
||||
|
||||
$(kernel_target): $(asm_obj_files) linker_script go.o
|
||||
@echo "[$(LD)] linking kernel-$(ARCH).bin"
|
||||
@ -88,7 +92,7 @@ $(BUILD_DIR)/arch/$(ARCH)/asm/%.o: arch/$(ARCH)/asm/%.s
|
||||
|
||||
iso: $(iso_target)
|
||||
|
||||
$(iso_target): iso_prereq $(kernel_target)
|
||||
$(iso_target): iso_prereq kernel_image
|
||||
@echo "[grub] building ISO kernel-$(ARCH).iso"
|
||||
|
||||
@mkdir -p $(BUILD_DIR)/isofiles/boot/grub
|
||||
|
@ -40,6 +40,7 @@ section .text
|
||||
;------------------------------------------------------------------------------
|
||||
global _rt0_64_entry
|
||||
_rt0_64_entry:
|
||||
call _rt0_install_redirect_trampolines
|
||||
call _rt0_64_load_idt
|
||||
|
||||
; According to the x86_64 ABI, the fs:0 should point to the address of
|
||||
@ -239,7 +240,6 @@ _rt0_64_gate_dispatcher_with_code:
|
||||
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:
|
||||
@ -307,3 +307,69 @@ write_string:
|
||||
|
||||
.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
|
||||
|
||||
|
||||
|
@ -39,5 +39,14 @@ SECTIONS {
|
||||
*(.bss)
|
||||
}
|
||||
|
||||
/* Go function redirection table. This table is used for hooking
|
||||
* Go runtime function symbols so that calls to them are redirected to
|
||||
* functions provided by the kernel.
|
||||
*/
|
||||
.goredirectstbl ALIGN(4K): AT(ADDR(.goredirectstbl) - PAGE_OFFSET)
|
||||
{
|
||||
*(.goredirectstbl)
|
||||
}
|
||||
|
||||
_kernel_end = ALIGN(4K);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
233
tools/redirects/redirects.go
Normal file
233
tools/redirects/redirects.go
Normal file
@ -0,0 +1,233 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type redirect struct {
|
||||
src string
|
||||
dst string
|
||||
|
||||
srcVMA uint64
|
||||
dstVMA uint64
|
||||
}
|
||||
|
||||
func exit(err error) {
|
||||
fmt.Fprintf(os.Stderr, "[redirects] error: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func pkgPrefix() (string, error) {
|
||||
goPath := os.Getenv("GOPATH") + "/src/"
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(cwd, goPath), nil
|
||||
}
|
||||
|
||||
func collectGoFiles(root string) ([]string, error) {
|
||||
var goFiles []string
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Ext(path) == ".go" && !strings.Contains(path, "_test") {
|
||||
goFiles = append(goFiles, path)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return goFiles, nil
|
||||
}
|
||||
|
||||
func findRedirects(goFiles []string) ([]*redirect, error) {
|
||||
var redirects []*redirect
|
||||
|
||||
prefix, err := pkgPrefix()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, goFile := range goFiles {
|
||||
fset := token.NewFileSet()
|
||||
|
||||
f, err := parser.ParseFile(fset, goFile, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", goFile, err)
|
||||
}
|
||||
|
||||
cmap := ast.NewCommentMap(fset, f, f.Comments)
|
||||
cmap.Filter(f)
|
||||
for astNode, commentGroups := range cmap {
|
||||
fnDecl, ok := astNode.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, commentGroup := range commentGroups {
|
||||
for _, comment := range commentGroup.List {
|
||||
if !strings.Contains(comment.Text, "go:redirect-from") {
|
||||
continue
|
||||
}
|
||||
|
||||
// build qualified name to fn
|
||||
fqName := fmt.Sprintf("%s/%s.%s",
|
||||
prefix,
|
||||
goFile[:strings.LastIndexByte(goFile, '/')],
|
||||
fnDecl.Name,
|
||||
)
|
||||
|
||||
fields := strings.Fields(comment.Text)
|
||||
if len(fields) != 2 || fields[0] != "//go:redirect-from" {
|
||||
return nil, fmt.Errorf("malformed go:redirect-from syntax for %q", fqName)
|
||||
}
|
||||
|
||||
redirects = append(redirects, &redirect{
|
||||
src: fields[1],
|
||||
dst: fqName,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirects, nil
|
||||
}
|
||||
|
||||
func elfRedirectTableOffset(imgFile string) (uint64, error) {
|
||||
f, err := elf.Open(imgFile)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
redirectsSection := f.Section(".goredirectstbl")
|
||||
if redirectsSection == nil {
|
||||
return 0, fmt.Errorf("%s: missing .goredirectstbl section", imgFile)
|
||||
}
|
||||
|
||||
return redirectsSection.Offset, nil
|
||||
}
|
||||
|
||||
func elfWriteRedirectTable(redirects []*redirect, imgFile string) error {
|
||||
redirectTableOffset, err := elfRedirectTableOffset(imgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open kernel image file and seek to table offset
|
||||
f, err := os.OpenFile(imgFile, os.O_WRONLY, os.ModeType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err = f.Seek(int64(redirectTableOffset), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, redirect := range redirects {
|
||||
binary.Write(f, binary.LittleEndian, redirect.srcVMA)
|
||||
binary.Write(f, binary.LittleEndian, redirect.dstVMA)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func elfResolveRedirectSymbols(redirects []*redirect, imgFile string) error {
|
||||
f, err := elf.Open(imgFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
symbols, err := f.Symbols()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, redirect := range redirects {
|
||||
for _, symbol := range symbols {
|
||||
if symbol.Name == redirect.src {
|
||||
redirect.srcVMA = symbol.Value
|
||||
}
|
||||
if symbol.Name == redirect.dst {
|
||||
redirect.dstVMA = symbol.Value
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case redirect.srcVMA == 0:
|
||||
return fmt.Errorf("%s: could not locate address of %q", imgFile, redirect.src)
|
||||
case redirect.dstVMA == 0:
|
||||
return fmt.Errorf("%s: could not locate address of %q", imgFile, redirect.dst)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if matches, _ := filepath.Glob("kernel/"); len(matches) != 1 {
|
||||
exit(errors.New("this tool must be run from the kernel root folder"))
|
||||
}
|
||||
|
||||
if len(flag.Args()) == 0 {
|
||||
exit(errors.New("missing command"))
|
||||
}
|
||||
|
||||
cmd := flag.Arg(0)
|
||||
var imgFile string
|
||||
switch cmd {
|
||||
case "count":
|
||||
case "populate-table":
|
||||
if len(flag.Args()) != 2 {
|
||||
exit(errors.New("populate-table requires the path to the kernel image as an argument"))
|
||||
}
|
||||
imgFile = flag.Arg(1)
|
||||
default:
|
||||
exit(fmt.Errorf("unknown command %q", cmd))
|
||||
}
|
||||
|
||||
goFiles, err := collectGoFiles("kernel/")
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
redirects, err := findRedirects(goFiles)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
if cmd == "count" {
|
||||
fmt.Printf("%d", len(redirects))
|
||||
return
|
||||
}
|
||||
|
||||
if err = elfResolveRedirectSymbols(redirects, imgFile); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
if err = elfWriteRedirectTable(redirects, imgFile); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user