vm CLI: allow to dump slots

This commit is contained in:
Anna Shaleva 2021-09-08 17:27:11 +03:00
parent b502c5f148
commit 6da458365d
5 changed files with 166 additions and 0 deletions

View file

@ -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.

View file

@ -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",
@ -286,6 +304,28 @@ func handleXStack(c *ishell.Context) {
c.Println(v.Stack(c.Cmd.Name))
}
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) {
v := getVMFromContext(c)
if len(c.Args) < 2 {

View file

@ -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),

View file

@ -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 {

View file

@ -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)
}