89 lines
2.8 KiB
Go
89 lines
2.8 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package verify provides the dynamic-analysis substrate for gasm: it
|
|
// JIT-assembles Plan 9 amd64 kernels into executable memory and calls them
|
|
// directly, enabling differential testing against portable Go references,
|
|
// runtime ABI checks and basic-block coverage profiling.
|
|
//
|
|
// The execution model is pure Go (stdlib only): machine code is mapped with
|
|
// syscall.Mmap and invoked through an assembly trampoline that switches to a
|
|
// prepared ABI0 stack. No cgo, no external toolchain.
|
|
package verify
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// Executable maps a copy of code into a read-execute memory region suitable
|
|
// for direct invocation. The mapping is anonymous and private; the original
|
|
// slice is not retained. Call Unmap to release the region.
|
|
type Executable struct {
|
|
addr uintptr // base address of the mapping
|
|
size int
|
|
mem []byte // the mmap'd slice (for Unmap)
|
|
}
|
|
|
|
// Map copies code into a freshly allocated RX region and returns it.
|
|
// The mapping is PROT_READ|PROT_EXEC; writes are not permitted after the
|
|
// copy, matching W^X policy.
|
|
func Map(code []byte) (*Executable, error) {
|
|
size := len(code)
|
|
if size == 0 {
|
|
return nil, fmt.Errorf("verify: cannot map zero-length code")
|
|
}
|
|
// Round up to the page size.
|
|
const pageSize = 4096
|
|
mapSize := (size + pageSize - 1) &^ (pageSize - 1)
|
|
|
|
mem, err := syscall.Mmap(-1, 0, mapSize,
|
|
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_PRIVATE|syscall.MAP_ANON)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("verify: mmap: %w", err)
|
|
}
|
|
copy(mem, code)
|
|
|
|
// Remove write permission (W^X).
|
|
if err := syscall.Mprotect(mem, syscall.PROT_READ|syscall.PROT_EXEC); err != nil {
|
|
syscall.Munmap(mem)
|
|
return nil, fmt.Errorf("verify: mprotect: %w", err)
|
|
}
|
|
return &Executable{
|
|
addr: uintptr(unsafe.Pointer(&mem[0])),
|
|
size: size,
|
|
mem: mem,
|
|
}, nil
|
|
}
|
|
|
|
// Unmap releases the executable region.
|
|
func (e *Executable) Unmap() {
|
|
if e.mem != nil {
|
|
syscall.Munmap(e.mem)
|
|
e.mem = nil
|
|
}
|
|
}
|
|
|
|
// FuncAddr returns the absolute address of a function at the given offset
|
|
// within the mapped image.
|
|
func (e *Executable) FuncAddr(offset int) uintptr {
|
|
return e.addr + uintptr(offset)
|
|
}
|
|
|
|
// PutUint64 writes v into buf at byte offset off (little-endian).
|
|
func PutUint64(buf []byte, off int, v uint64) {
|
|
binary.LittleEndian.PutUint64(buf[off:off+8], v)
|
|
}
|
|
|
|
// GetUint64 reads a little-endian uint64 from buf at byte offset off.
|
|
func GetUint64(buf []byte, off int) uint64 {
|
|
return binary.LittleEndian.Uint64(buf[off : off+8])
|
|
}
|
|
|
|
// PutPtr writes a pointer value into buf at byte offset off.
|
|
func PutPtr(buf []byte, off int, p unsafe.Pointer) {
|
|
binary.LittleEndian.PutUint64(buf[off:off+8], uint64(uintptr(p)))
|
|
}
|