vm: use new Context.Next() to properly dump programs

Fix #295, deduplicate code and add `inspect` parameter to the vm command to
dump AVMs (`contract inspect` works with Go code).
This commit is contained in:
Roman Khimov 2019-10-03 16:56:48 +03:00
parent 53a3b18652
commit dca332f333
3 changed files with 71 additions and 29 deletions

View file

@ -1,6 +1,10 @@
package vm
import (
"errors"
"io/ioutil"
"github.com/CityOfZion/neo-go/pkg/vm"
vmcli "github.com/CityOfZion/neo-go/pkg/vm/cli"
"github.com/urfave/cli"
)
@ -14,6 +18,19 @@ func NewCommand() cli.Command {
Flags: []cli.Flag{
cli.BoolFlag{Name: "debug, d"},
},
Subcommands: []cli.Command{
{
Name: "inspect",
Usage: "dump instructions of the avm file given",
Action: inspect,
Flags: []cli.Flag{
cli.StringFlag{
Name: "in, i",
Usage: "input file of the program (AVM)",
},
},
},
},
}
}
@ -21,3 +38,18 @@ func startVMPrompt(ctx *cli.Context) error {
p := vmcli.New()
return p.Run()
}
func inspect(ctx *cli.Context) error {
avm := ctx.String("in")
if len(avm) == 0 {
return cli.NewExitError(errors.New("no input file given"), 1)
}
b, err := ioutil.ReadFile(avm)
if err != nil {
return cli.NewExitError(err, 1)
}
v := vm.New(0)
v.LoadScript(b)
v.PrintOps()
return nil
}

View file

@ -13,7 +13,6 @@ import (
"log"
"os"
"strings"
"text/tabwriter"
"github.com/CityOfZion/neo-go/pkg/vm"
"golang.org/x/tools/go/loader"
@ -108,25 +107,9 @@ func CompileAndInspect(src string) error {
return err
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
for i := 0; i <= len(b)-1; {
instr := vm.Instruction(b[i])
paramlength := 0
fmt.Fprintf(w, "%d\t0x%x\t%s\t\n", i, b[i], instr)
i++
if instr >= vm.PUSHBYTES1 && instr <= vm.PUSHBYTES75 {
paramlength = int(instr)
}
if instr == vm.JMP || instr == vm.JMPIF || instr == vm.JMPIFNOT || instr == vm.CALL {
paramlength = 2
}
for x := 0; x < paramlength; x++ {
fmt.Fprintf(w, "%d\t0x%x\t%s\t\n", i, b[i+1+x], string(b[i+1+x]))
}
i += paramlength
}
w.Flush()
v := vm.New(0)
v.LoadScript(b)
v.PrintOps()
return nil
}

View file

@ -10,6 +10,7 @@ import (
"os"
"reflect"
"text/tabwriter"
"unicode/utf8"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
@ -119,19 +120,45 @@ func (v *VM) LoadArgs(method []byte, args []StackItem) {
// PrintOps will print the opcodes of the current loaded program to stdout.
func (v *VM) PrintOps() {
prog := v.Context().Program()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
fmt.Fprintln(w, "INDEX\tOPCODE\tPARAMETER\t")
realctx := v.Context()
ctx := realctx.Copy()
ctx.ip = 0
ctx.nextip = 0
for {
cursor := ""
ip, _ := v.Context().CurrInstr()
for i := 0; i < len(prog); i++ {
if i == ip {
instr, parameter, err := ctx.Next()
if ctx.ip == realctx.ip {
cursor = "<<"
} else {
cursor = ""
}
fmt.Fprintf(w, "%d\t0x%2x\t%s\t%s\n", i, prog[i], Instruction(prog[i]).String(), cursor)
if err != nil {
fmt.Fprintf(w, "%d\t%s\tERROR: %s\t%s\n", ctx.ip, instr, err, cursor)
break
}
var desc = ""
if parameter != nil {
switch instr {
case JMP, JMPIF, JMPIFNOT, CALL:
offset := int16(binary.LittleEndian.Uint16(parameter))
desc = fmt.Sprintf("%d (%d/%x)", ctx.ip+int(offset), offset, parameter)
case SYSCALL:
desc = fmt.Sprintf("%q", parameter)
case APPCALL, TAILCALL:
desc = fmt.Sprintf("%x", parameter)
default:
if utf8.Valid(parameter) {
desc = fmt.Sprintf("%x (%q)", parameter, parameter)
} else {
desc = fmt.Sprintf("%x", parameter)
}
}
}
fmt.Fprintf(w, "%d\t%s\t%s\t%s\n", ctx.ip, instr, desc, cursor)
if ctx.nextip >= len(ctx.prog) {
break
}
}
w.Flush()
}