forked from TrueCloudLab/neoneo-go
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:
parent
53a3b18652
commit
dca332f333
3 changed files with 71 additions and 29 deletions
32
cli/vm/vm.go
32
cli/vm/vm.go
|
@ -1,6 +1,10 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
vmcli "github.com/CityOfZion/neo-go/pkg/vm/cli"
|
vmcli "github.com/CityOfZion/neo-go/pkg/vm/cli"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +18,19 @@ func NewCommand() cli.Command {
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{Name: "debug, d"},
|
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()
|
p := vmcli.New()
|
||||||
return p.Run()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
|
@ -108,25 +107,9 @@ func CompileAndInspect(src string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
v := vm.New(0)
|
||||||
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
|
v.LoadScript(b)
|
||||||
for i := 0; i <= len(b)-1; {
|
v.PrintOps()
|
||||||
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()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
45
pkg/vm/vm.go
45
pkg/vm/vm.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"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.
|
// PrintOps will print the opcodes of the current loaded program to stdout.
|
||||||
func (v *VM) PrintOps() {
|
func (v *VM) PrintOps() {
|
||||||
prog := v.Context().Program()
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
|
||||||
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
|
fmt.Fprintln(w, "INDEX\tOPCODE\tPARAMETER\t")
|
||||||
cursor := ""
|
realctx := v.Context()
|
||||||
ip, _ := v.Context().CurrInstr()
|
ctx := realctx.Copy()
|
||||||
for i := 0; i < len(prog); i++ {
|
ctx.ip = 0
|
||||||
if i == ip {
|
ctx.nextip = 0
|
||||||
|
for {
|
||||||
|
cursor := ""
|
||||||
|
instr, parameter, err := ctx.Next()
|
||||||
|
if ctx.ip == realctx.ip {
|
||||||
cursor = "<<"
|
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()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue