From dca332f333666d1e3cdf32ede475455201a10b97 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 3 Oct 2019 16:56:48 +0300 Subject: [PATCH] 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). --- cli/vm/vm.go | 32 ++++++++++++++++++++++++++ pkg/vm/compiler/compiler.go | 23 +++---------------- pkg/vm/vm.go | 45 +++++++++++++++++++++++++++++-------- 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/cli/vm/vm.go b/cli/vm/vm.go index 0bb55e1db..35a8f92c2 100644 --- a/cli/vm/vm.go +++ b/cli/vm/vm.go @@ -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 +} diff --git a/pkg/vm/compiler/compiler.go b/pkg/vm/compiler/compiler.go index e16da65e5..94bf31ee8 100644 --- a/pkg/vm/compiler/compiler.go +++ b/pkg/vm/compiler/compiler.go @@ -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 } diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 2a698febb..8262f6b0c 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -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") - cursor := "" - ip, _ := v.Context().CurrInstr() - for i := 0; i < len(prog); i++ { - if i == ip { + fmt.Fprintln(w, "INDEX\tOPCODE\tPARAMETER\t") + realctx := v.Context() + ctx := realctx.Copy() + ctx.ip = 0 + ctx.nextip = 0 + for { + cursor := "" + 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() }