sealevel, sbf: run SPL memo program

This commit is contained in:
Richard Patel 2022-09-05 00:28:39 +02:00
parent bfc538e03d
commit 2cc227561e
21 changed files with 430 additions and 167 deletions

View File

@ -9,11 +9,14 @@ import (
"github.com/stretchr/testify/require"
)
// SBF returns the given SBF fixture.
func SBF(t *testing.T, name string) []byte {
// Load returns the fixture at the given path.
func Load(t *testing.T, strs ...string) []byte {
_, file, _, ok := runtime.Caller(0)
require.True(t, ok, "runtime.Caller failed")
data, err := os.ReadFile(filepath.Join(filepath.Dir(file), "sbf", name))
parts := make([]string, 1, 1+len(strs))
parts[0] = filepath.Dir(file)
parts = append(parts, strs...)
data, err := os.ReadFile(filepath.Join(parts...))
require.NoError(t, err)
return data
}

103
pkg/sbf/asm.go Normal file
View File

@ -0,0 +1,103 @@
package sbf
var mnemonicTable = [0x100]string{
OpLddw: "lddw",
OpLdxb: "ldxb",
OpLdxh: "ldxh",
OpLdxw: "ldxw",
OpLdxdw: "ldxdw",
OpStb: "stb",
OpSth: "sth",
OpStw: "stw",
OpStdw: "stdw",
OpStxb: "stxb",
OpStxh: "stxh",
OpStxw: "stxw",
OpStxdw: "stxdw",
OpAdd32Imm: "add32",
OpAdd32Reg: "add32",
OpSub32Imm: "sub32",
OpSub32Reg: "sub32",
OpMul32Imm: "mul32",
OpMul32Reg: "mul32",
OpDiv32Imm: "div32",
OpDiv32Reg: "div32",
OpOr32Imm: "or32",
OpOr32Reg: "or32",
OpAnd32Imm: "and32",
OpAnd32Reg: "and32",
OpLsh32Imm: "lsh32",
OpLsh32Reg: "lsh32",
OpRsh32Imm: "rsh32",
OpRsh32Reg: "rsh32",
OpNeg32: "neg32",
OpMod32Imm: "mod32",
OpMod32Reg: "mod32",
OpXor32Imm: "xor32",
OpXor32Reg: "xor32",
OpMov32Imm: "mov32",
OpMov32Reg: "mov32",
OpArsh32Imm: "arsh32",
OpArsh32Reg: "arsh32",
OpSdiv32Imm: "sdiv32",
OpSdiv32Reg: "sdiv32",
OpLe: "le",
OpBe: "be",
OpAdd64Imm: "add64",
OpAdd64Reg: "add64",
OpSub64Imm: "sub64",
OpSub64Reg: "sub64",
OpMul64Imm: "mul64",
OpMul64Reg: "mul64",
OpDiv64Imm: "div64",
OpDiv64Reg: "div64",
OpOr64Imm: "or64",
OpOr64Reg: "or64",
OpAnd64Imm: "and64",
OpAnd64Reg: "and64",
OpLsh64Imm: "lsh64",
OpLsh64Reg: "lsh64",
OpRsh64Imm: "rsh64",
OpRsh64Reg: "rsh64",
OpNeg64: "neg64",
OpMod64Imm: "mod64",
OpMod64Reg: "mod64",
OpXor64Imm: "xor64",
OpXor64Reg: "xor64",
OpMov64Imm: "mov64",
OpMov64Reg: "mov64",
OpArsh64Imm: "arsh64",
OpArsh64Reg: "arsh64",
OpSdiv64Imm: "sdiv64",
OpSdiv64Reg: "sdiv64",
OpJa: "ja",
OpJeqImm: "jeq",
OpJeqReg: "jeq",
OpJgtImm: "jgt",
OpJgtReg: "jgt",
OpJgeImm: "jge",
OpJgeReg: "jge",
OpJltImm: "jlt",
OpJltReg: "jlt",
OpJleImm: "jle",
OpJleReg: "jle",
OpJsetImm: "jset",
OpJsetReg: "jset",
OpJneImm: "jne",
OpJneReg: "jne",
OpJsgtImm: "jsgt",
OpJsgtReg: "jsgt",
OpJsgeImm: "jsge",
OpJsgeReg: "jsge",
OpJsltImm: "jslt",
OpJsltReg: "jslt",
OpJsleImm: "jsle",
OpJsleReg: "jsle",
OpCall: "call",
OpCallx: "callx",
OpExit: "exit",
}
func GetOpcodeName(opc uint8) string {
return mnemonicTable[opc]
}

View File

@ -1,3 +0,0 @@
package sbf
// This file contains helper routines for the calculation of compute units.

10
pkg/sbf/cu/cu.go Normal file
View File

@ -0,0 +1,10 @@
package cu
// This file contains helper routines for the calculation of compute units.
func ConsumeLowerBound(cu int, lower int, x int) int {
if x < lower {
return cu - lower
}
return cu - x
}

View File

@ -17,7 +17,7 @@ type Interpreter struct {
input []byte
entry uint64
cuMax uint64
cuMax int
syscalls map[uint32]Syscall
funcs map[uint32]int64
@ -30,6 +30,7 @@ type Interpreter struct {
// In other words, Run may only be called once per interpreter.
func NewInterpreter(p *Program, opts *VMOpts) *Interpreter {
return &Interpreter{
textVA: p.TextVA,
text: p.Text,
ro: p.RO,
stack: NewStack(),
@ -38,6 +39,7 @@ func NewInterpreter(p *Program, opts *VMOpts) *Interpreter {
entry: p.Entrypoint,
cuMax: opts.MaxCU,
syscalls: opts.Syscalls,
funcs: p.Funcs,
vmContext: opts.Context,
}
}
@ -45,12 +47,13 @@ func NewInterpreter(p *Program, opts *VMOpts) *Interpreter {
// Run executes the program.
//
// This function may panic given code that doesn't pass the static verifier.
func (i *Interpreter) Run() (err error) {
func (ip *Interpreter) Run() (err error) {
var r [11]uint64
r[1] = VaddrInput
r[10] = ip.stack.GetFramePtr()
// TODO frame pointer
pc := int64(i.entry)
cuLeft := int64(i.cuMax)
pc := int64(ip.entry)
cuLeft := int(ip.cuMax)
// Design notes
// - The interpreter is deliberately implemented in a single big loop,
@ -60,53 +63,56 @@ func (i *Interpreter) Run() (err error) {
// The interpreter may panic when it notices these invariants are violated (e.g. invalid opcode)
mainLoop:
for {
for i := 0; true; i++ {
// Fetch
ins := i.getSlot(pc)
ins := ip.getSlot(pc)
fmt.Printf("% 5d [%016x, %016x, %016x, %016x, %016x, %016x, %016x, %016x, %016x, %016x, %016x] %-5d: %s\n",
i, r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], pc, GetOpcodeName(ins.Op()))
fmt.Printf(" ins=%016x op=%s\n", bits.ReverseBytes64(uint64(ins)), GetOpcodeName(ins.Op()))
// Execute
switch ins.Op() {
case OpLdxb:
vma := uint64(int64(r[ins.Src()]) + int64(ins.Off()))
var v uint8
v, err = i.Read8(vma)
v, err = ip.Read8(vma)
r[ins.Dst()] = uint64(v)
case OpLdxh:
vma := uint64(int64(r[ins.Src()]) + int64(ins.Off()))
var v uint16
v, err = i.Read16(vma)
v, err = ip.Read16(vma)
r[ins.Dst()] = uint64(v)
case OpLdxw:
vma := uint64(int64(r[ins.Src()]) + int64(ins.Off()))
var v uint32
v, err = i.Read32(vma)
v, err = ip.Read32(vma)
r[ins.Dst()] = uint64(v)
case OpLdxdw:
vma := uint64(int64(r[ins.Src()]) + int64(ins.Off()))
r[ins.Dst()], err = i.Read64(vma)
r[ins.Dst()], err = ip.Read64(vma)
case OpStb:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write8(vma, uint8(ins.Uimm()))
err = ip.Write8(vma, uint8(ins.Uimm()))
case OpSth:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write16(vma, uint16(ins.Uimm()))
err = ip.Write16(vma, uint16(ins.Uimm()))
case OpStw:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write32(vma, ins.Uimm())
err = ip.Write32(vma, ins.Uimm())
case OpStdw:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write64(vma, uint64(ins.Imm()))
err = ip.Write64(vma, uint64(ins.Imm()))
case OpStxb:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write8(vma, uint8(r[ins.Src()]))
err = ip.Write8(vma, uint8(r[ins.Src()]))
case OpStxh:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write16(vma, uint16(r[ins.Src()]))
err = ip.Write16(vma, uint16(r[ins.Src()]))
case OpStxw:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write32(vma, uint32(r[ins.Src()]))
err = ip.Write32(vma, uint32(r[ins.Src()]))
case OpStxdw:
vma := uint64(int64(r[ins.Dst()]) + int64(ins.Off()))
err = i.Write64(vma, r[ins.Src()])
err = ip.Write64(vma, r[ins.Src()])
case OpAdd32Imm:
r[ins.Dst()] = uint64(int32(r[ins.Dst()]) + ins.Imm())
case OpAdd32Reg:
@ -274,7 +280,7 @@ mainLoop:
panic("invalid be instruction")
}
case OpLddw:
r[ins.Dst()] = uint64(ins.Uimm()) | (uint64(i.getSlot(pc+1).Uimm()) << 32)
r[ins.Dst()] = uint64(ins.Uimm()) | (uint64(ip.getSlot(pc+1).Uimm()) << 32)
pc++
case OpJa:
pc += int64(ins.Off())
@ -368,35 +374,36 @@ mainLoop:
}
case OpCall:
// TODO use src reg hint
if sc, ok := i.syscalls[ins.Uimm()]; ok {
r[0], cuLeft, err = sc.Invoke(i, r[1], r[2], r[3], r[4], r[5], cuLeft)
} else if target, ok := i.funcs[ins.Uimm()]; ok {
r[10], ok = i.stack.Push((*[4]uint64)(r[6:10]), pc+1)
if sc, ok := ip.syscalls[ins.Uimm()]; ok {
r[0], cuLeft, err = sc.Invoke(ip, r[1], r[2], r[3], r[4], r[5], cuLeft)
} else if target, ok := ip.funcs[ins.Uimm()]; ok {
r[10], ok = ip.stack.Push((*[4]uint64)(r[6:10]), pc+1)
if !ok {
err = ExcCallDepth
}
pc = target
pc = target - 1
} else {
err = ExcCallDest
err = ExcCallDest{ins.Uimm()}
}
case OpCallx:
target := r[ins.Uimm()]
target &= ^(uint64(0x7))
var ok bool
r[10], ok = i.stack.Push((*[4]uint64)(r[6:10]), pc+1)
r[10], ok = ip.stack.Push((*[4]uint64)(r[6:10]), pc+1)
if !ok {
err = ExcCallDepth
}
if target < i.textVA || target >= VaddrStack || target >= i.textVA+uint64(len(i.text)) {
if target < ip.textVA || target >= VaddrStack || target >= ip.textVA+uint64(len(ip.text)) {
err = NewExcBadAccess(target, 8, false, "jump out-of-bounds")
}
pc = int64((target - i.textVA) / 8)
pc = int64((target-ip.textVA)/8) - 1
case OpExit:
var ok bool
r[10], pc, ok = i.stack.Pop((*[4]uint64)(r[6:10]))
r[10], pc, ok = ip.stack.Pop((*[4]uint64)(r[6:10]))
if !ok {
break mainLoop
}
pc--
default:
panic(fmt.Sprintf("unimplemented opcode %#02x", ins.Op()))
}
@ -420,15 +427,15 @@ mainLoop:
return nil
}
func (i *Interpreter) getSlot(pc int64) Slot {
return GetSlot(i.text[pc*SlotSize:])
func (ip *Interpreter) getSlot(pc int64) Slot {
return GetSlot(ip.text[pc*SlotSize:])
}
func (i *Interpreter) VMContext() any {
return i.vmContext
func (ip *Interpreter) VMContext() any {
return ip.vmContext
}
func (i *Interpreter) Translate(addr uint64, size uint32, write bool) (unsafe.Pointer, error) {
func (ip *Interpreter) Translate(addr uint64, size uint32, write bool) (unsafe.Pointer, error) {
// TODO exhaustive testing against rbpf
// TODO review generated asm for performance
@ -438,30 +445,33 @@ func (i *Interpreter) Translate(addr uint64, size uint32, write bool) (unsafe.Po
if write {
return nil, NewExcBadAccess(addr, size, write, "write to program")
}
if lo+uint64(size) >= uint64(len(i.ro)) {
if lo+uint64(size) >= uint64(len(ip.ro)) {
return nil, NewExcBadAccess(addr, size, write, "out-of-bounds program read")
}
return unsafe.Pointer(&i.ro[lo]), nil
return unsafe.Pointer(&ip.ro[lo]), nil
case VaddrStack >> 32:
mem := i.stack.GetFrame(uint32(addr))
mem := ip.stack.GetFrame(uint32(addr))
if uint32(len(mem)) < size {
return nil, NewExcBadAccess(addr, size, write, "out-of-bounds stack access")
}
return unsafe.Pointer(&mem[0]), nil
case VaddrHeap >> 32:
panic("todo implement heap access check")
case VaddrInput >> 32:
if lo+uint64(size) >= uint64(len(i.input)) {
return nil, NewExcBadAccess(addr, size, write, "out-of-bounds input read")
if lo+uint64(size) >= uint64(len(ip.heap)) {
return nil, NewExcBadAccess(addr, size, write, "out-of-bounds heap access")
}
return unsafe.Pointer(&i.input[lo]), nil
return unsafe.Pointer(&ip.heap[lo]), nil
case VaddrInput >> 32:
if lo+uint64(size) >= uint64(len(ip.input)) {
return nil, NewExcBadAccess(addr, size, write, "out-of-bounds input access")
}
return unsafe.Pointer(&ip.input[lo]), nil
default:
return nil, NewExcBadAccess(addr, size, write, "unmapped region")
}
}
func (i *Interpreter) Read(addr uint64, p []byte) error {
ptr, err := i.Translate(addr, uint32(len(p)), false)
func (ip *Interpreter) Read(addr uint64, p []byte) error {
ptr, err := ip.Translate(addr, uint32(len(p)), false)
if err != nil {
return err
}
@ -470,8 +480,8 @@ func (i *Interpreter) Read(addr uint64, p []byte) error {
return nil
}
func (i *Interpreter) Read8(addr uint64) (uint8, error) {
ptr, err := i.Translate(addr, 1, false)
func (ip *Interpreter) Read8(addr uint64) (uint8, error) {
ptr, err := ip.Translate(addr, 1, false)
if err != nil {
return 0, err
}
@ -480,32 +490,32 @@ func (i *Interpreter) Read8(addr uint64) (uint8, error) {
// TODO is it safe and portable to deref unaligned integer types?
func (i *Interpreter) Read16(addr uint64) (uint16, error) {
ptr, err := i.Translate(addr, 2, false)
func (ip *Interpreter) Read16(addr uint64) (uint16, error) {
ptr, err := ip.Translate(addr, 2, false)
if err != nil {
return 0, err
}
return *(*uint16)(ptr), nil
}
func (i *Interpreter) Read32(addr uint64) (uint32, error) {
ptr, err := i.Translate(addr, 4, false)
func (ip *Interpreter) Read32(addr uint64) (uint32, error) {
ptr, err := ip.Translate(addr, 4, false)
if err != nil {
return 0, err
}
return *(*uint32)(ptr), nil
}
func (i *Interpreter) Read64(addr uint64) (uint64, error) {
ptr, err := i.Translate(addr, 8, false)
func (ip *Interpreter) Read64(addr uint64) (uint64, error) {
ptr, err := ip.Translate(addr, 8, false)
if err != nil {
return 0, err
}
return *(*uint64)(ptr), nil
}
func (i *Interpreter) Write(addr uint64, p []byte) error {
ptr, err := i.Translate(addr, uint32(len(p)), true)
func (ip *Interpreter) Write(addr uint64, p []byte) error {
ptr, err := ip.Translate(addr, uint32(len(p)), true)
if err != nil {
return err
}
@ -514,8 +524,8 @@ func (i *Interpreter) Write(addr uint64, p []byte) error {
return nil
}
func (i *Interpreter) Write8(addr uint64, x uint8) error {
ptr, err := i.Translate(addr, 1, true)
func (ip *Interpreter) Write8(addr uint64, x uint8) error {
ptr, err := ip.Translate(addr, 1, true)
if err != nil {
return err
}
@ -523,8 +533,8 @@ func (i *Interpreter) Write8(addr uint64, x uint8) error {
return nil
}
func (i *Interpreter) Write16(addr uint64, x uint16) error {
ptr, err := i.Translate(addr, 2, true)
func (ip *Interpreter) Write16(addr uint64, x uint16) error {
ptr, err := ip.Translate(addr, 2, true)
if err != nil {
return err
}
@ -532,8 +542,8 @@ func (i *Interpreter) Write16(addr uint64, x uint16) error {
return nil
}
func (i *Interpreter) Write32(addr uint64, x uint32) error {
ptr, err := i.Translate(addr, 4, true)
func (ip *Interpreter) Write32(addr uint64, x uint32) error {
ptr, err := ip.Translate(addr, 4, true)
if err != nil {
return err
}
@ -541,8 +551,8 @@ func (i *Interpreter) Write32(addr uint64, x uint32) error {
return nil
}
func (i *Interpreter) Write64(addr uint64, x uint64) error {
ptr, err := i.Translate(addr, 8, false)
func (ip *Interpreter) Write64(addr uint64, x uint64) error {
ptr, err := ip.Translate(addr, 8, false)
if err != nil {
return err
}

View File

@ -49,7 +49,7 @@ type Loader struct {
entrypoint uint64 // program counter
// Symbols
funcs map[uint32]uint64
funcs map[uint32]int64
}
// Bounds checks
@ -98,6 +98,8 @@ func (l *Loader) getProgram() *sbf.Program {
return &sbf.Program{
RO: l.program,
Text: l.text,
TextVA: sbf.VaddrProgram + l.textRange.min,
Entrypoint: l.entrypoint,
Funcs: l.funcs,
}
}

View File

@ -10,7 +10,7 @@ import (
// relocate applies ELF relocations (for syscalls and position-independent code).
func (l *Loader) relocate() error {
l.funcs = make(map[uint32]uint64)
l.funcs = make(map[uint32]int64)
if err := l.fixupRelativeCalls(); err != nil {
return err
}
@ -31,9 +31,7 @@ func (l *Loader) fixupRelativeCalls() error {
off := i * sbf.SlotSize
slot := sbf.GetSlot(buf[off : off+sbf.SlotSize])
isCall := slot.Op() == sbf.OpCall &&
slot.Imm() != -1 &&
slot.Src() == 0
isCall := slot.Op() == sbf.OpCall && slot.Imm() != -1
if !isCall {
continue
}
@ -58,10 +56,10 @@ func (l *Loader) fixupRelativeCalls() error {
func (l *Loader) registerFunc(target uint64) (uint32, error) {
hash := sbf.PCHash(target)
// TODO check for collision with syscalls
if _, ok := l.funcs[hash]; ok {
return 0, fmt.Errorf("symbol hash collision")
}
l.funcs[hash] = target
//if _, ok := l.funcs[hash]; ok {
// return 0, fmt.Errorf("symbol hash collision for func at=%d hash=%#08x", target, hash)
//}
l.funcs[hash] = int64(target)
return hash, nil
}
@ -127,7 +125,7 @@ func (l *Loader) applyReloc(reloc *elf.Rel64) error {
} else {
// lol
addr = uint64(binary.LittleEndian.Uint32(l.program[rOff+4 : rOff+8]))
addr = clampAddUint64(addr, sbf.VaddrStack)
addr = clampAddUint64(addr, sbf.VaddrProgram)
}
binary.LittleEndian.PutUint64(l.program[rOff:rOff+8], addr)
}

View File

@ -4,7 +4,9 @@ package sbf
type Program struct {
RO []byte // read-only segment containing text and ELFs
Text []byte
TextVA uint64
Entrypoint uint64 // PC
Funcs map[uint32]int64
}
// Verify runs the static bytecode verifier.

View File

@ -87,7 +87,7 @@ func (s *Stack) Push(nvRegs *[4]uint64, ret int64) (fp uint64, ok bool) {
}
fp = s.GetFramePtr() + 2*StackFrameSize
s.shadow = s.shadow[len(s.shadow)+1:]
s.shadow = s.shadow[:len(s.shadow)+1]
s.shadow[len(s.shadow)-1] = Frame{
FramePtr: fp,
NVRegs: *nvRegs,
@ -109,10 +109,11 @@ func (s *Stack) Pop(nvRegs *[4]uint64) (fp uint64, ret int64, ok bool) {
}
var frame Frame
frame, s.shadow = s.shadow[0], s.shadow[1:]
frame, s.shadow = s.shadow[len(s.shadow)-1], s.shadow[:len(s.shadow)-1]
fp = s.GetFramePtr()
*nvRegs = frame.NVRegs
ret = frame.RetAddr
ok = true
return
}

View File

@ -28,7 +28,7 @@ func PCHash(addr uint64) uint32 {
// Syscall are callback handles from VM to Go. (work in progress)
type Syscall interface {
Invoke(vm VM, r1, r2, r3, r4, r5 uint64, cuIn int64) (r0 uint64, cuOut int64, err error)
Invoke(vm VM, r1, r2, r3, r4, r5 uint64, cuIn int) (r0 uint64, cuOut int, err error)
}
type SyscallRegistry map[uint32]Syscall
@ -49,38 +49,38 @@ func (s SyscallRegistry) Register(name string, syscall Syscall) (hash uint32, ok
// Convenience Methods
type SyscallFunc0 func(vm VM, cuIn int64) (r0 uint64, cuOut int64, err error)
type SyscallFunc0 func(vm VM, cuIn int) (r0 uint64, cuOut int, err error)
func (f SyscallFunc0) Invoke(vm VM, _, _, _, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) {
func (f SyscallFunc0) Invoke(vm VM, _, _, _, _, _ uint64, cuIn int) (r0 uint64, cuOut int, err error) {
return f(vm, cuIn)
}
type SyscallFunc1 func(vm VM, r1 uint64, cuIn int64) (r0 uint64, cuOut int64, err error)
type SyscallFunc1 func(vm VM, r1 uint64, cuIn int) (r0 uint64, cuOut int, err error)
func (f SyscallFunc1) Invoke(vm VM, r1, _, _, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) {
func (f SyscallFunc1) Invoke(vm VM, r1, _, _, _, _ uint64, cuIn int) (r0 uint64, cuOut int, err error) {
return f(vm, r1, cuIn)
}
type SyscallFunc2 func(vm VM, r1, r2 uint64, cuIn int64) (r0 uint64, cuOut int64, err error)
type SyscallFunc2 func(vm VM, r1, r2 uint64, cuIn int) (r0 uint64, cuOut int, err error)
func (f SyscallFunc2) Invoke(vm VM, r1, r2, _, _, _ uint64, cuIn int64) (r0 uint64, cuOut int64, err error) {
func (f SyscallFunc2) Invoke(vm VM, r1, r2, _, _, _ uint64, cuIn int) (r0 uint64, cuOut int, 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)
type SyscallFunc3 func(vm VM, r1, r2, r3 uint64, cuIn int) (r0 uint64, cuOut int, err error)
func (f SyscallFunc3) Invoke(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 int) (r0 uint64, cuOut int, 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)
type SyscallFunc4 func(vm VM, r1, r2, r3, r4 uint64, cuIn int) (r0 uint64, cuOut int, err error)
func (f SyscallFunc4) Invoke(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 int) (r0 uint64, cuOut int, 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)
type SyscallFunc5 func(vm VM, r1, r2, r3, r4, r5 uint64, cuIn int) (r0 uint64, cuOut int, err error)
func (f SyscallFunc5) Invoke(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 int) (r0 uint64, cuOut int, err error) {
return f(vm, r1, r2, r3, r4, r5, cuIn)
}

View File

@ -33,6 +33,8 @@ func (v *Verifier) Verify() error {
case OpMul32Imm, OpMul32Reg, OpMul64Imm, OpMul64Reg:
case OpOr32Imm, OpOr32Reg, OpOr64Imm, OpOr64Reg:
case OpAnd32Imm, OpAnd32Reg, OpAnd64Imm, OpAnd64Reg:
case OpLsh32Reg, OpLsh64Reg:
case OpRsh32Reg, OpRsh64Reg:
case OpNeg32, OpNeg64:
case OpXor32Imm, OpXor32Reg, OpXor64Imm, OpXor64Reg:
case OpMov32Imm, OpMov32Reg, OpMov64Imm, OpMov64Reg:

View File

@ -25,13 +25,12 @@ type VM interface {
// VMOpts specifies virtual machine parameters.
type VMOpts struct {
// Machine parameters
StackSize int
HeapSize int
Syscalls SyscallRegistry
HeapSize int
Syscalls SyscallRegistry
// Execution parameters
Context any // passed to syscalls
MaxCU uint64
MaxCU int
Input []byte // mapped at VaddrInput
}
@ -54,7 +53,6 @@ var (
ExcDivideOverflow = errors.New("divide overflow")
ExcOutOfCU = errors.New("compute unit overrun")
ExcCallDepth = errors.New("call depth exceeded")
ExcCallDest = errors.New("unknown symbol or syscall")
)
type ExcBadAccess struct {
@ -76,3 +74,11 @@ 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)
}
type ExcCallDest struct {
Imm uint32
}
func (e ExcCallDest) Error() string {
return fmt.Sprintf("unknown symbol or syscall 0x%08x", e.Imm)
}

5
pkg/sealevel/cu.go Normal file
View File

@ -0,0 +1,5 @@
package sealevel
const (
CUSyscallBaseCost = 100
)

View File

@ -1,40 +0,0 @@
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) {
// TODO simplify API?
loader, err := loader.NewLoaderFromBytes(fixtures.SBF(t, "noop.so"))
require.NotNil(t, loader)
require.NoError(t, err)
program, err := loader.Load()
require.NotNil(t, program)
require.NoError(t, err)
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)
err = interpreter.Run()
require.NoError(t, err)
}

View File

@ -1,29 +0,0 @@
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)

13
pkg/sealevel/logging.go Normal file
View File

@ -0,0 +1,13 @@
package sealevel
type Logger interface {
Log(s string)
}
type LogRecorder struct {
Logs []string
}
func (r *LogRecorder) Log(s string) {
r.Logs = append(r.Logs, s)
}

28
pkg/sealevel/sealevel.go Normal file
View File

@ -0,0 +1,28 @@
package sealevel
import (
"bytes"
"github.com/certusone/radiance/pkg/sbf"
)
type TxContext struct{}
type Execution struct {
Log Logger
}
func (t *TxContext) newVMOpts(params *Params) *sbf.VMOpts {
execution := &Execution{
Log: new(LogRecorder),
}
var buf bytes.Buffer
params.Serialize(&buf)
return &sbf.VMOpts{
HeapSize: 32 * 1024,
Syscalls: registry,
Context: execution,
MaxCU: 1_400_000,
Input: buf.Bytes(),
}
}

View File

@ -0,0 +1,79 @@
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/assert"
"github.com/stretchr/testify/require"
)
func TestExecute_Memo(t *testing.T) {
tx := TxContext{}
opts := tx.newVMOpts(&Params{
Accounts: nil,
Data: []byte("Bla"),
ProgramID: [32]byte{},
})
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sealevel", "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr.so"))
require.NoError(t, err)
require.NotNil(t, loader)
program, err := loader.Load()
require.NoError(t, err)
require.NotNil(t, program)
require.NoError(t, program.Verify())
interpreter := sbf.NewInterpreter(program, opts)
require.NotNil(t, interpreter)
err = interpreter.Run()
assert.NoError(t, err)
// TODO expose proper API for this
logs := opts.Context.(*Execution).Log.(*LogRecorder).Logs
assert.Equal(t, logs, []string{
`Memo (len 3): "Bla"`,
})
}
func TestInterpreter_Noop(t *testing.T) {
// TODO simplify API?
loader, err := loader.NewLoaderFromBytes(fixtures.Load(t, "sbf", "noop.so"))
require.NoError(t, err)
require.NotNil(t, loader)
program, err := loader.Load()
require.NoError(t, err)
require.NotNil(t, program)
require.NoError(t, program.Verify())
syscalls := sbf.NewSyscallRegistry()
syscalls.Register("log", SyscallLog)
syscalls.Register("log_64", SyscallLog64)
var log LogRecorder
interpreter := sbf.NewInterpreter(program, &sbf.VMOpts{
HeapSize: 32 * 1024,
Input: nil,
MaxCU: 10000,
Syscalls: syscalls,
Context: &Execution{Log: &log},
})
require.NotNil(t, interpreter)
err = interpreter.Run()
require.NoError(t, err)
assert.Equal(t, log.Logs, []string{
"entrypoint\x00",
"0x1, 0x2, 0x3, 0x4, 0x5\n",
})
}

18
pkg/sealevel/syscalls.go Normal file
View File

@ -0,0 +1,18 @@
package sealevel
import "github.com/certusone/radiance/pkg/sbf"
var registry = Syscalls()
// Syscalls creates a registry of all Sealevel syscalls.
func Syscalls() sbf.SyscallRegistry {
reg := sbf.NewSyscallRegistry()
reg.Register("abort", SyscallAbort)
reg.Register("sol_log_", SyscallLog)
reg.Register("sol_log_64_", SyscallLog64)
return reg
}
func syscallCtx(vm sbf.VM) *Execution {
return vm.VMContext().(*Execution)
}

View File

@ -0,0 +1,41 @@
package sealevel
import (
"fmt"
"github.com/certusone/radiance/pkg/sbf"
"github.com/certusone/radiance/pkg/sbf/cu"
)
func SyscallLogImpl(vm sbf.VM, ptr, strlen uint64, cuIn int) (r0 uint64, cuOut int, err error) {
if strlen > (1 << 30) {
cuOut = -1
return
}
cuOut = cu.ConsumeLowerBound(cuIn, CUSyscallBaseCost, int(strlen))
if cuOut < 0 {
return
}
buf := make([]byte, strlen)
if err = vm.Read(ptr, buf); err != nil {
return
}
syscallCtx(vm).Log.Log(string(buf))
return
}
var SyscallLog = sbf.SyscallFunc2(SyscallLogImpl)
func SyscallLog64Impl(vm sbf.VM, r1, r2, r3, r4, r5 uint64, cuIn int) (r0 uint64, cuOut int, err error) {
cuOut = cuIn - CUSyscallBaseCost
if cuOut < 0 {
return
}
msg := fmt.Sprintf("%#x, %#x, %#x, %#x, %#x\n", r1, r2, r3, r4, r5)
syscallCtx(vm).Log.Log(msg)
return
}
var SyscallLog64 = sbf.SyscallFunc5(SyscallLog64Impl)

View File

@ -0,0 +1,14 @@
package sealevel
import (
"errors"
"github.com/certusone/radiance/pkg/sbf"
)
func SyscallAbortImpl(_ sbf.VM, _ int) (r0 uint64, cuOut int, err error) {
err = errors.New("aborted")
return
}
var SyscallAbort = sbf.SyscallFunc0(SyscallAbortImpl)