vm: fix debugger and add tests

1. `Run()` must be able to continue execution after a breakpoint.
2. VM must stop right before the breakpoint, not after.
3. Initial vm state is NONE, not HALT.
This commit is contained in:
Evgenii Stratonikov 2020-08-18 11:13:09 +03:00
parent 8659fd79e5
commit a080d24cf5
3 changed files with 52 additions and 11 deletions

View file

@ -221,7 +221,7 @@ func (c *Context) Equals(s stackitem.Item) bool {
func (c *Context) atBreakPoint() bool { func (c *Context) atBreakPoint() bool {
for _, n := range c.breakPoints { for _, n := range c.breakPoints {
if n == c.ip { if n == c.nextip {
return true return true
} }
} }

42
pkg/vm/debug_test.go Normal file
View file

@ -0,0 +1,42 @@
package vm
import (
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
)
func TestVM_Debug(t *testing.T) {
prog := makeProgram(opcode.CALL, 3, opcode.RET,
opcode.PUSH2, opcode.PUSH3, opcode.ADD, opcode.RET)
t.Run("BreakPoint", func(t *testing.T) {
v := load(prog)
v.AddBreakPoint(3)
v.AddBreakPoint(5)
require.NoError(t, v.Run())
require.Equal(t, 3, v.Context().NextIP())
require.NoError(t, v.Run())
require.Equal(t, 5, v.Context().NextIP())
require.NoError(t, v.Run())
require.Equal(t, 1, v.estack.len)
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
})
t.Run("StepInto", func(t *testing.T) {
v := load(prog)
require.NoError(t, v.StepInto())
require.Equal(t, 3, v.Context().NextIP())
require.NoError(t, v.StepOut())
require.Equal(t, 2, v.Context().NextIP())
require.Equal(t, 1, v.estack.len)
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
})
t.Run("StepOver", func(t *testing.T) {
v := load(prog)
require.NoError(t, v.StepOver())
require.Equal(t, 2, v.Context().NextIP())
require.Equal(t, 1, v.estack.len)
require.Equal(t, big.NewInt(5), v.estack.Top().Value())
})
}

View file

@ -89,7 +89,7 @@ func New() *VM {
// NewWithTrigger returns a new VM for executions triggered by t. // NewWithTrigger returns a new VM for executions triggered by t.
func NewWithTrigger(t trigger.Type) *VM { func NewWithTrigger(t trigger.Type) *VM {
vm := &VM{ vm := &VM{
state: HaltState, state: NoneState,
istack: NewStack("invocation"), istack: NewStack("invocation"),
refs: newRefCounter(), refs: newRefCounter(),
keys: make(map[string]*keys.PublicKey), keys: make(map[string]*keys.PublicKey),
@ -358,11 +358,6 @@ func (v *VM) Run() error {
// HaltState (the default) or BreakState are safe to continue. // HaltState (the default) or BreakState are safe to continue.
v.state = NoneState v.state = NoneState
for { for {
// check for breakpoint before executing the next instruction
ctx := v.Context()
if ctx != nil && ctx.atBreakPoint() {
v.state = BreakState
}
switch { switch {
case v.state.HasFlag(FaultState): case v.state.HasFlag(FaultState):
// Should be caught and reported already by the v.Step(), // Should be caught and reported already by the v.Step(),
@ -379,6 +374,11 @@ func (v *VM) Run() error {
v.state = FaultState v.state = FaultState
return errors.New("unknown state") return errors.New("unknown state")
} }
// check for breakpoint before executing the next instruction
ctx := v.Context()
if ctx != nil && ctx.atBreakPoint() {
v.state = BreakState
}
} }
} }
@ -430,14 +430,15 @@ func (v *VM) StepOut() error {
var err error var err error
if v.state == BreakState { if v.state == BreakState {
v.state = NoneState v.state = NoneState
} else {
v.state = BreakState
} }
expSize := v.istack.len expSize := v.istack.len
for v.state == NoneState && v.istack.len >= expSize { for v.state == NoneState && v.istack.len >= expSize {
err = v.StepInto() err = v.StepInto()
} }
if v.state == NoneState {
v.state = BreakState
}
return err return err
} }
@ -451,8 +452,6 @@ func (v *VM) StepOver() error {
if v.state == BreakState { if v.state == BreakState {
v.state = NoneState v.state = NoneState
} else {
v.state = BreakState
} }
expSize := v.istack.len expSize := v.istack.len