sealevel, sbf: run SPL memo program
This commit is contained in:
parent
bfc538e03d
commit
2cc227561e
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package sbf
|
||||
|
||||
// This file contains helper routines for the calculation of compute units.
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package sealevel
|
||||
|
||||
const (
|
||||
CUSyscallBaseCost = 100
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
|
@ -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)
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue