diff --git a/pkg/sbf/interpreter.go b/pkg/sbf/interpreter.go index a2c2f85..c6771db 100644 --- a/pkg/sbf/interpreter.go +++ b/pkg/sbf/interpreter.go @@ -56,11 +56,11 @@ func (i *Interpreter) Run() (err error) { // TODO step to next instruction +mainLoop: for { // Fetch ins := i.getSlot(pc) // Execute - pc++ switch ins.Op() { case OpLdxb: vma := uint64(int64(r[ins.Src()]) + int64(ins.Off())) @@ -373,15 +373,20 @@ func (i *Interpreter) Run() (err error) { case OpCallx: panic("callx not implemented") case OpExit: - return nil + // TODO implement function returns + break mainLoop default: panic(fmt.Sprintf("unimplemented opcode %#02x", ins.Op())) } + // Post execute if err != nil { // TODO return CPU exception error type here return err } + pc++ } + + return nil } func (i *Interpreter) getSlot(pc int64) Slot { diff --git a/pkg/sbf/loader/arithmetic.go b/pkg/sbf/loader/arithmetic.go index f9d164d..4b33aa2 100644 --- a/pkg/sbf/loader/arithmetic.go +++ b/pkg/sbf/loader/arithmetic.go @@ -13,6 +13,7 @@ func clampAddUint64(x uint64, y uint64) uint64 { return z } +/* func clampSubUint64(x uint64, y uint64) uint64 { z, borrow := bits.Sub64(x, y, 0) if borrow != 0 { @@ -20,6 +21,7 @@ func clampSubUint64(x uint64, y uint64) uint64 { } return z } +*/ type addrRange struct { min, max uint64 diff --git a/pkg/sbf/loader/loader.go b/pkg/sbf/loader/loader.go index 44e81fc..b49e15c 100644 --- a/pkg/sbf/loader/loader.go +++ b/pkg/sbf/loader/loader.go @@ -49,8 +49,7 @@ type Loader struct { entrypoint uint64 // program counter // Symbols - funcs map[uint32]uint64 - syscalls map[uint32]string + funcs map[uint32]uint64 } // Bounds checks diff --git a/pkg/sbf/loader/relocate.go b/pkg/sbf/loader/relocate.go index 91b4bad..d579fd4 100644 --- a/pkg/sbf/loader/relocate.go +++ b/pkg/sbf/loader/relocate.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/certusone/radiance/pkg/sbf" - "github.com/spaolacci/murmur3" ) // relocate applies ELF relocations (for syscalls and position-independent code). @@ -57,7 +56,7 @@ func (l *Loader) fixupRelativeCalls() error { } func (l *Loader) registerFunc(target uint64) (uint32, error) { - hash := PCHash(target) + hash := sbf.PCHash(target) // TODO check for collision with syscalls if _, ok := l.funcs[hash]; ok { return 0, fmt.Errorf("symbol hash collision") @@ -155,7 +154,7 @@ func (l *Loader) applyReloc(reloc *elf.Rel64) error { } } else { // Syscall - hash = SymbolHash(name) + hash = sbf.SymbolHash(name) // TODO check whether syscall is known } @@ -175,24 +174,6 @@ func (l *Loader) getEntrypoint() error { return nil } -const ( - // EntrypointHash equals SymbolHash("entrypoint") - EntrypointHash = uint32(0x71e3cf81) -) - -// SymbolHash returns the murmur3 32-bit hash of a symbol name. -func SymbolHash(s string) uint32 { - return murmur3.Sum32([]byte(s)) -} - -// PCHash returns the murmur3 32-bit hash of a program counter. -func PCHash(addr uint64) uint32 { - // TODO this is kinda pointless … - var key [8]byte - binary.LittleEndian.PutUint64(key[:], addr) - return murmur3.Sum32(key[:]) -} - // Relocation types for eBPF. type R_BPF int diff --git a/pkg/sbf/sbf.go b/pkg/sbf/sbf.go index 3ee4f85..6c9ebcc 100644 --- a/pkg/sbf/sbf.go +++ b/pkg/sbf/sbf.go @@ -41,12 +41,12 @@ func (s Slot) Op() uint8 { // Dst returns the destination register field. func (s Slot) Dst() uint8 { - return uint8(s>>12) & 0xF + return uint8(s>>8) & 0xF } // Src returns the source register field. func (s Slot) Src() uint8 { - return uint8(s>>8) & 0xF + return uint8(s>>12) & 0xF } // Off returns the offset field. diff --git a/pkg/sbf/syscalls.go b/pkg/sbf/syscalls.go new file mode 100644 index 0000000..8fe004b --- /dev/null +++ b/pkg/sbf/syscalls.go @@ -0,0 +1,43 @@ +package sbf + +import ( + "encoding/binary" + + "github.com/spaolacci/murmur3" +) + +const ( + // EntrypointHash equals SymbolHash("entrypoint") + EntrypointHash = uint32(0x71e3cf81) +) + +// SymbolHash returns the murmur3 32-bit hash of a symbol name. +func SymbolHash(s string) uint32 { + return murmur3.Sum32([]byte(s)) +} + +// PCHash returns the murmur3 32-bit hash of a program counter. +// +// Used by VM for non-syscall functions +func PCHash(addr uint64) uint32 { + // TODO this is kinda pointless … + var key [8]byte + binary.LittleEndian.PutUint64(key[:], addr) + return murmur3.Sum32(key[:]) +} + +type SyscallRegistry map[uint32]Syscall + +func NewSyscallRegistry() SyscallRegistry { + return make(SyscallRegistry) +} + +func (s SyscallRegistry) Register(name string, syscall Syscall) (hash uint32, ok bool) { + hash = SymbolHash(name) + if _, exist := s[hash]; exist { + return 0, false // collision or duplicate + } + s[hash] = syscall + ok = true + return +} diff --git a/pkg/sbf/vm.go b/pkg/sbf/vm.go index 4e788a2..73e0f4b 100644 --- a/pkg/sbf/vm.go +++ b/pkg/sbf/vm.go @@ -27,7 +27,7 @@ type VMOpts struct { // Machine parameters StackSize int HeapSize int - Syscalls map[uint32]Syscall + Syscalls SyscallRegistry // Execution parameters Context any // passed to syscalls @@ -65,3 +65,41 @@ func NewExcBadAccess(addr uint64, size uint32, write bool, reason string) ExcBad func (e ExcBadAccess) Error() string { return fmt.Sprintf("bad memory access at %#x (size=%d write=%v), reason: %s", e.Addr, e.Size, e.Write, e.Reason) } + +// Convenience Methods + +type SyscallFunc0 func(vm VM, cuIn int64) (r0 uint64, cuOut int64, err error) + +func (f SyscallFunc0) Invoke(vm VM, _, _, _, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + return f(vm, cuIn) +} + +type SyscallFunc1 func(vm VM, r1 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) + +func (f SyscallFunc1) Invoke(vm VM, r1, _, _, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + return f(vm, r1, cuIn) +} + +type SyscallFunc2 func(vm VM, r1, r2 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) + +func (f SyscallFunc2) Invoke(vm VM, r1, r2, _, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + return f(vm, r1, r2, cuIn) +} + +type SyscallFunc3 func(vm VM, r1, r2, r3 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) + +func (f SyscallFunc3) Invoke(vm VM, r1, r2, r3, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + return f(vm, r1, r2, r3, cuIn) +} + +type SyscallFunc4 func(vm VM, r1, r2, r3, r4 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) + +func (f SyscallFunc4) Invoke(vm VM, r1, r2, r3, r4, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + return f(vm, r1, r2, r3, r4, cuIn) +} + +type SyscallFunc5 func(vm VM, r1, r2, r3, r4, r5 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) + +func (f SyscallFunc5) Invoke(vm VM, r1, r2, r3, r4, r5 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + return f(vm, r1, r2, r3, r4, r5, cuIn) +} diff --git a/pkg/sbf/loader/interpeter_test.go b/pkg/sealevel/interpeter_test.go similarity index 62% rename from pkg/sbf/loader/interpeter_test.go rename to pkg/sealevel/interpeter_test.go index aa50a09..41ab075 100644 --- a/pkg/sbf/loader/interpeter_test.go +++ b/pkg/sealevel/interpeter_test.go @@ -1,15 +1,18 @@ -package loader +package sealevel import ( _ "embed" "testing" + "github.com/certusone/radiance/fixtures" "github.com/certusone/radiance/pkg/sbf" + "github.com/certusone/radiance/pkg/sbf/loader" "github.com/stretchr/testify/require" ) func TestInterpreter_Noop(t *testing.T) { - loader, err := NewLoaderFromBytes(soNoop) + // TODO simplify API? + loader, err := loader.NewLoaderFromBytes(fixtures.SBF(t, "noop.so")) require.NotNil(t, loader) require.NoError(t, err) @@ -19,11 +22,16 @@ func TestInterpreter_Noop(t *testing.T) { require.NoError(t, program.Verify()) + syscalls := sbf.NewSyscallRegistry() + syscalls.Register("log", SyscallLog) + syscalls.Register("log_64", SyscallLog64) + interpreter := sbf.NewInterpreter(program, &sbf.VMOpts{ StackSize: 1024, HeapSize: 1024, // TODO Input: nil, MaxCU: 10000, + Syscalls: syscalls, }) require.NotNil(t, interpreter) diff --git a/pkg/sealevel/log.go b/pkg/sealevel/log.go new file mode 100644 index 0000000..b6d9f94 --- /dev/null +++ b/pkg/sealevel/log.go @@ -0,0 +1,29 @@ +package sealevel + +import ( + "fmt" + "strings" + + "github.com/certusone/radiance/pkg/sbf" +) + +// TODO These are naive stubs + +func SyscallLogImpl(vm sbf.VM, r1, r2 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + buf := make([]byte, r2) + if err = vm.Read(r1, buf); err != nil { + return + } + fmt.Println("Program Log:", strings.Trim(string(buf), " \t\x00")) + //panic("log syscall unimplemented") + return +} + +var SyscallLog = sbf.SyscallFunc2(SyscallLogImpl) + +func SyscallLog64Impl(vm sbf.VM, r1, r2, r3, r4, r5 uint64, cuIn int64) (r0 uint64, cuOut int64, err error) { + fmt.Printf("Program Log: r1=%#x r2=%#x r3=%#x r4=%#x r5=%#x\n", r1, r2, r3, r4, r5) + return +} + +var SyscallLog64 = sbf.SyscallFunc5(SyscallLog64Impl)