Merge pull request #2164 from nspcc-dev/vm/slots
vm: allow to dump slots
This commit is contained in:
commit
04bbeccca6
6 changed files with 190 additions and 11 deletions
|
@ -37,6 +37,7 @@ NEO-GO-VM >
|
|||
NEO-GO-VM > help
|
||||
|
||||
Commands:
|
||||
aslot Show arguments slot contents
|
||||
break Place a breakpoint
|
||||
clear clear the screen
|
||||
cont Continue execution of the current loaded script
|
||||
|
@ -49,9 +50,11 @@ Commands:
|
|||
loadgo Compile and load a Go file with the manifest into the VM
|
||||
loadhex Load a hex-encoded script string into the VM
|
||||
loadnef Load a NEF-consistent script into the VM
|
||||
lslot Show local slot contents
|
||||
ops Dump opcodes of the current loaded program
|
||||
parse Parse provided argument and convert it into other possible formats
|
||||
run Execute the current loaded script
|
||||
sslot Show static slot contents
|
||||
step Step (n) instruction in the program
|
||||
stepinto Stepinto instruction to take in the debugger
|
||||
stepout Stepout instruction to take in the debugger
|
||||
|
@ -224,3 +227,8 @@ NEO-GO-VM > estack
|
|||
There is one more stack that you can inspect.
|
||||
- `istack` invocation stack
|
||||
|
||||
There are slots that you can inspect.
|
||||
- `aslot` dumps arguments slot contents.
|
||||
- `lslot` dumps local slot contents.
|
||||
- `sslot` dumps static slot contents.
|
||||
|
||||
|
|
|
@ -73,6 +73,24 @@ var commands = []*ishell.Cmd{
|
|||
LongHelp: "Show invocation stack contents",
|
||||
Func: handleXStack,
|
||||
},
|
||||
{
|
||||
Name: "sslot",
|
||||
Help: "Show static slot contents",
|
||||
LongHelp: "Show static slot contents",
|
||||
Func: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "lslot",
|
||||
Help: "Show local slot contents",
|
||||
LongHelp: "Show local slot contents",
|
||||
Func: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "aslot",
|
||||
Help: "Show arguments slot contents",
|
||||
LongHelp: "Show arguments slot contents",
|
||||
Func: handleSlots,
|
||||
},
|
||||
{
|
||||
Name: "loadnef",
|
||||
Help: "Load a NEF-consistent script into the VM",
|
||||
|
@ -283,7 +301,39 @@ func handleBreak(c *ishell.Context) {
|
|||
|
||||
func handleXStack(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
c.Println(v.Stack(c.Cmd.Name))
|
||||
var stackDump string
|
||||
switch c.Cmd.Name {
|
||||
case "estack":
|
||||
stackDump = v.DumpEStack()
|
||||
case "istack":
|
||||
stackDump = v.DumpIStack()
|
||||
default:
|
||||
c.Err(errors.New("unknown stack"))
|
||||
return
|
||||
}
|
||||
c.Println(stackDump)
|
||||
}
|
||||
|
||||
func handleSlots(c *ishell.Context) {
|
||||
v := getVMFromContext(c)
|
||||
vmCtx := v.Context()
|
||||
if vmCtx == nil {
|
||||
c.Err(errors.New("no program loaded"))
|
||||
return
|
||||
}
|
||||
var rawSlot string
|
||||
switch c.Cmd.Name {
|
||||
case "sslot":
|
||||
rawSlot = vmCtx.DumpStaticSlot()
|
||||
case "lslot":
|
||||
rawSlot = vmCtx.DumpLocalSlot()
|
||||
case "aslot":
|
||||
rawSlot = vmCtx.DumpArgumentsSlot()
|
||||
default:
|
||||
c.Err(errors.New("unknown slot"))
|
||||
return
|
||||
}
|
||||
c.Println(rawSlot)
|
||||
}
|
||||
|
||||
func handleLoadNEF(c *ishell.Context) {
|
||||
|
@ -430,7 +480,7 @@ func runVMWithHandling(c *ishell.Context, v *vm.VM) {
|
|||
case v.HasFailed():
|
||||
message = "" // the error will be printed on return
|
||||
case v.HasHalted():
|
||||
message = v.Stack("estack")
|
||||
message = v.DumpEStack()
|
||||
case v.AtBreakpoint():
|
||||
ctx := v.Context()
|
||||
if ctx.NextIP() < ctx.LenInstr() {
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
@ -129,6 +130,37 @@ func (e *executor) checkStack(t *testing.T, items ...interface{}) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (e *executor) checkSlot(t *testing.T, items ...interface{}) {
|
||||
d := json.NewDecoder(e.out)
|
||||
var actual interface{}
|
||||
require.NoError(t, d.Decode(&actual))
|
||||
rawActual, err := json.Marshal(actual)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := make([]json.RawMessage, len(items))
|
||||
for i := range items {
|
||||
if items[i] == nil {
|
||||
expected[i] = []byte("null")
|
||||
continue
|
||||
}
|
||||
data, err := stackitem.ToJSONWithTypes(stackitem.Make(items[i]))
|
||||
require.NoError(t, err)
|
||||
expected[i] = data
|
||||
}
|
||||
rawExpected, err := json.MarshalIndent(expected, "", " ")
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(rawExpected), string(rawActual))
|
||||
|
||||
// Decoder has it's own buffer, we need to return unread part to the output.
|
||||
outRemain := e.out.String()
|
||||
e.out.Reset()
|
||||
_, err = gio.Copy(e.out, d.Buffered())
|
||||
require.NoError(t, err)
|
||||
e.out.WriteString(outRemain)
|
||||
_, err = e.out.ReadString('\n')
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
script := []byte{byte(opcode.PUSH3), byte(opcode.PUSH4), byte(opcode.ADD)}
|
||||
t.Run("loadhex", func(t *testing.T) {
|
||||
|
@ -376,6 +408,55 @@ func TestBreakpoint(t *testing.T) {
|
|||
e.checkStack(t, 9)
|
||||
}
|
||||
|
||||
func TestDumpSSlot(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 2, // init static slot with size=2
|
||||
opcode.PUSH5, opcode.STSFLD, 1, // put `int(5)` to sslot[1]; sslot[0] is nil
|
||||
opcode.LDSFLD1) // put sslot[1] to the top of estack
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadhex "+hex.EncodeToString(w.Bytes()),
|
||||
"break 5",
|
||||
"step", "sslot",
|
||||
"cont", "estack",
|
||||
)
|
||||
e.checkNextLine(t, "READY: loaded 6 instructions")
|
||||
e.checkNextLine(t, "breakpoint added at instruction 5")
|
||||
|
||||
e.checkNextLine(t, "at breakpoint 5.*LDSFLD1")
|
||||
e.checkSlot(t, nil, 5)
|
||||
|
||||
e.checkStack(t, 5)
|
||||
}
|
||||
|
||||
func TestDumpLSlot_DumpASlot(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
emit.Opcodes(w.BinWriter, opcode.PUSH4, opcode.PUSH5, opcode.PUSH6, // items for args slot
|
||||
opcode.INITSLOT, 2, 3, // init local slot with size=2 and args slot with size 3
|
||||
opcode.PUSH7, opcode.STLOC1, // put `int(7)` to lslot[1]; lslot[0] is nil
|
||||
opcode.LDLOC, 1) // put lslot[1] to the top of estack
|
||||
e := newTestVMCLI(t)
|
||||
e.runProg(t,
|
||||
"loadhex "+hex.EncodeToString(w.Bytes()),
|
||||
"break 6",
|
||||
"break 8",
|
||||
"cont", "aslot",
|
||||
"cont", "lslot",
|
||||
"cont", "estack",
|
||||
)
|
||||
e.checkNextLine(t, "READY: loaded 10 instructions")
|
||||
e.checkNextLine(t, "breakpoint added at instruction 6")
|
||||
e.checkNextLine(t, "breakpoint added at instruction 8")
|
||||
|
||||
e.checkNextLine(t, "at breakpoint 6.*PUSH7")
|
||||
e.checkSlot(t, 6, 5, 4) // args slot
|
||||
|
||||
e.checkNextLine(t, "at breakpoint 8.*LDLOC")
|
||||
e.checkSlot(t, nil, 7) // local slot
|
||||
|
||||
e.checkStack(t, 7)
|
||||
}
|
||||
|
||||
func TestStep(t *testing.T) {
|
||||
script := hex.EncodeToString([]byte{
|
||||
byte(opcode.PUSH0), byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.PUSH3),
|
||||
|
|
|
@ -2,6 +2,7 @@ package vm
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -278,6 +279,27 @@ func (c *Context) IsDeployed() bool {
|
|||
return c.NEF != nil
|
||||
}
|
||||
|
||||
// DumpStaticSlot returns json formatted representation of the given slot.
|
||||
func (c *Context) DumpStaticSlot() string {
|
||||
return dumpSlot(c.static)
|
||||
}
|
||||
|
||||
// DumpLocalSlot returns json formatted representation of the given slot.
|
||||
func (c *Context) DumpLocalSlot() string {
|
||||
return dumpSlot(c.local)
|
||||
}
|
||||
|
||||
// DumpArgumentsSlot returns json formatted representation of the given slot.
|
||||
func (c *Context) DumpArgumentsSlot() string {
|
||||
return dumpSlot(c.arguments)
|
||||
}
|
||||
|
||||
// dumpSlot returns json formatted representation of the given slot.
|
||||
func dumpSlot(s *Slot) string {
|
||||
b, _ := json.MarshalIndent(s, "", " ")
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// getContextScriptHash returns script hash of the invocation stack element
|
||||
// number n.
|
||||
func (v *VM) getContextScriptHash(n int) util.Uint160 {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
|
@ -66,3 +68,16 @@ func (s *Slot) Size() int {
|
|||
}
|
||||
return len(s.storage)
|
||||
}
|
||||
|
||||
// MarshalJSON implements JSON marshalling interface.
|
||||
func (s *Slot) MarshalJSON() ([]byte, error) {
|
||||
items := s.storage
|
||||
arr := make([]json.RawMessage, len(items))
|
||||
for i := range items {
|
||||
data, err := stackitem.ToJSONWithTypes(items[i])
|
||||
if err == nil {
|
||||
arr[i] = data
|
||||
}
|
||||
}
|
||||
return json.Marshal(arr)
|
||||
}
|
||||
|
|
21
pkg/vm/vm.go
21
pkg/vm/vm.go
|
@ -334,15 +334,18 @@ func (v *VM) PopResult() interface{} {
|
|||
return v.estack.Pop().Value()
|
||||
}
|
||||
|
||||
// Stack returns json formatted representation of the given stack.
|
||||
func (v *VM) Stack(n string) string {
|
||||
var s *Stack
|
||||
if n == "istack" {
|
||||
s = &v.istack
|
||||
}
|
||||
if n == "estack" {
|
||||
s = v.estack
|
||||
}
|
||||
// DumpIStack returns json formatted representation of the invocation stack.
|
||||
func (v *VM) DumpIStack() string {
|
||||
return dumpStack(&v.istack)
|
||||
}
|
||||
|
||||
// DumpEStack returns json formatted representation of the execution stack.
|
||||
func (v *VM) DumpEStack() string {
|
||||
return dumpStack(v.estack)
|
||||
}
|
||||
|
||||
// dumpStack returns json formatted representation of the given stack.
|
||||
func dumpStack(s *Stack) string {
|
||||
b, _ := json.MarshalIndent(s, "", " ")
|
||||
return string(b)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue