Merge pull request #416 from nspcc-dev/vm_step_debug

vm: add stepInto,stepOver,stepOut

Original C# vm debugger behavior implemented.

closes #187
This commit is contained in:
Roman Khimov 2019-10-14 19:30:46 +03:00 committed by GitHub
commit 88a3f50a0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 125 additions and 1 deletions

View file

@ -128,6 +128,30 @@ Example:
> step 10`, > step 10`,
Func: handleStep, Func: handleStep,
}, },
{
Name: "stepinto",
Help: "Stepinto instruction to take in the debugger",
LongHelp: `Usage: stepInto
example:
> stepinto`,
Func: handleStepInto,
},
{
Name: "stepout",
Help: "Stepout instruction to take in the debugger",
LongHelp: `Usage: stepOut
example:
> stepout`,
Func: handleStepOut,
},
{
Name: "stepover",
Help: "Stepover instruction to take in the debugger",
LongHelp: `Usage: stepOver
example:
> stepover`,
Func: handleStepOver,
},
{ {
Name: "ops", Name: "ops",
Help: "Dump opcodes of the current loaded program", Help: "Dump opcodes of the current loaded program",
@ -136,7 +160,7 @@ Example:
}, },
} }
// VMCLI object for interacting with the VM. // VMCLI object for interacting with the VM.
type VMCLI struct { type VMCLI struct {
vm *vm.VM vm *vm.VM
shell *ishell.Shell shell *ishell.Shell
@ -297,6 +321,34 @@ func handleStep(c *ishell.Context) {
changePrompt(c, v) changePrompt(c, v)
} }
func handleStepInto(c *ishell.Context) {
handleStepType(c, "into")
}
func handleStepOut(c *ishell.Context) {
handleStepType(c, "out")
}
func handleStepOver(c *ishell.Context) {
handleStepType(c, "over")
}
func handleStepType(c *ishell.Context, stepType string) {
if !checkVMIsReady(c) {
return
}
v := getVMFromContext(c)
switch stepType {
case "into":
v.StepInto()
case "out":
v.StepOut()
case "over":
v.StepOver()
}
changePrompt(c, v)
}
func handleOps(c *ishell.Context) { func handleOps(c *ishell.Context) {
if !checkVMIsReady(c) { if !checkVMIsReady(c) {
return return

View file

@ -289,12 +289,84 @@ func (v *VM) Step() {
v.execute(ctx, op, param) v.execute(ctx, op, param)
} }
// StepInto behaves the same as “step over” in case if the line does not contain a function it otherwise
// the debugger will enter the called function and continue line-by-line debugging there.
func (v *VM) StepInto() {
ctx := v.Context()
if ctx == nil {
v.state |= haltState
}
if v.HasStopped() {
return
}
if ctx != nil && ctx.prog != nil {
op, param, err := ctx.Next()
if err != nil {
log.Printf("error encountered at instruction %d (%s)", ctx.ip, op)
log.Println(err)
v.state = faultState
}
v.execute(ctx, op, param)
i, op := ctx.CurrInstr()
fmt.Printf("at breakpoint %d (%s)\n", i, op.String())
}
cctx := v.Context()
if cctx != nil && cctx.atBreakPoint() {
v.state = breakState
}
}
// StepOut takes the debugger to the line where the current function was called.
func (v *VM) StepOut() {
if v.state == breakState {
v.state = noneState
} else {
v.state = breakState
}
expSize := v.istack.len
for v.state.HasFlag(noneState) && v.istack.len >= expSize {
v.StepInto()
}
}
// StepOver takes the debugger to the line that will step over a given line.
// If the line contains a function the function will be executed and the result returned without debugging each line.
func (v *VM) StepOver() {
if v.HasStopped() {
return
}
if v.state == breakState {
v.state = noneState
} else {
v.state = breakState
}
expSize := v.istack.len
for {
v.StepInto()
if !(v.state.HasFlag(noneState) && v.istack.len > expSize) {
break
}
}
}
// HasFailed returns whether VM is in the failed state now. Usually used to // HasFailed returns whether VM is in the failed state now. Usually used to
// check status after Run. // check status after Run.
func (v *VM) HasFailed() bool { func (v *VM) HasFailed() bool {
return v.state.HasFlag(faultState) return v.state.HasFlag(faultState)
} }
// HasStopped returns whether VM is in Halt or Failed state.
func (v *VM) HasStopped() bool {
return v.state.HasFlag(haltState) || v.state.HasFlag(faultState)
}
// SetCheckedHash sets checked hash for CHECKSIG and CHECKMULTISIG instructions. // SetCheckedHash sets checked hash for CHECKSIG and CHECKMULTISIG instructions.
func (v *VM) SetCheckedHash(h []byte) { func (v *VM) SetCheckedHash(h []byte) {
v.checkhash = make([]byte, len(h)) v.checkhash = make([]byte, len(h))