diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 2845013a..e90389ec 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -32,6 +32,7 @@ Command | Description [print](#print) | Evaluate an expression. [regs](#regs) | Print contents of CPU registers. [restart](#restart) | Restart process from a checkpoint or event. +[rev](#rev) | Reverses the execution of the target program for the command specified. [rewind](#rewind) | Run backwards until breakpoint or program termination. [set](#set) | Changes the value of a variable. [source](#source) | Executes a file containing a list of delve commands @@ -329,6 +330,11 @@ Restart process from a checkpoint or event. Aliases: r +## rev +Reverses the execution of the target program for the command specified. +Currently, only the rev step-instruction command is supported. + + ## rewind Run backwards until breakpoint or program termination. diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index b10cb22a..1823eef7 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -34,6 +34,7 @@ const ( noPrefix = cmdPrefix(0) onPrefix = cmdPrefix(1 << iota) deferredPrefix + revPrefix ) type callContext struct { @@ -136,7 +137,7 @@ the arguments. With -noargs, the process starts with an empty commandline. `}, {aliases: []string{"continue", "c"}, cmdFn: c.cont, helpMsg: "Run until breakpoint or program termination."}, {aliases: []string{"step", "s"}, cmdFn: c.step, helpMsg: "Single step through program."}, - {aliases: []string{"step-instruction", "si"}, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."}, + {aliases: []string{"step-instruction", "si"}, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."}, {aliases: []string{"next", "n"}, cmdFn: c.next, helpMsg: "Step over to next source line."}, {aliases: []string{"stepout"}, cmdFn: c.stepout, helpMsg: "Step out of the current function."}, {aliases: []string{"call"}, cmdFn: c.call, helpMsg: `Resumes process, injecting a function call (EXPERIMENTAL!!!) @@ -379,6 +380,12 @@ The "note" is arbitrary text that can be used to identify the checkpoint, if it helpMsg: `Deletes checkpoint. clear-checkpoint `, + }) + c.cmds = append(c.cmds, command{ + aliases: []string{"rev"}, + cmdFn: c.revCmd, + helpMsg: `Reverses the execution of the target program for the command specified. +Currently, only the rev step-instruction command is supported.`, }) for i := range c.cmds { v := &c.cmds[i] @@ -988,7 +995,15 @@ func (c *Commands) stepInstruction(t *Term, ctx callContext, args string) error if c.frame != 0 { return notOnFrameZeroErr } - state, err := exitedToError(t.client.StepInstruction()) + + var fn func() (*api.DebuggerState, error) + if ctx.Prefix == revPrefix { + fn = t.client.ReverseStepInstruction + } else { + fn = t.client.StepInstruction + } + + state, err := exitedToError(fn()) if err != nil { printcontextNoState(t) return err @@ -998,6 +1013,18 @@ func (c *Commands) stepInstruction(t *Term, ctx callContext, args string) error return nil } +func (c *Commands) revCmd(t *Term, ctx callContext, args string) error { + if len(args) == 0 { + return errors.New("not enough arguments") + } + + ctx.Prefix = revPrefix + if err := c.CallWithContext(args, t, ctx); err != nil { + return err + } + return nil +} + func (c *Commands) next(t *Term, ctx callContext, args string) error { if err := scopePrefixSwitch(t, ctx); err != nil { return err diff --git a/service/api/types.go b/service/api/types.go index 3fa24196..b2707595 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -356,6 +356,8 @@ const ( StepOut = "stepOut" // StepInstruction continues for exactly 1 cpu instruction. StepInstruction = "stepInstruction" + // ReverseStepInstruction reverses execution for exactly 1 cpu instruction. + ReverseStepInstruction = "reverseStepInstruction" // Next continues to the next source line, not entering function calls. Next = "next" // SwitchThread switches the debugger's current thread context. diff --git a/service/client.go b/service/client.go index f7bed988..0b3a59e6 100644 --- a/service/client.go +++ b/service/client.go @@ -43,6 +43,8 @@ type Client interface { // SingleStep will step a single cpu instruction. StepInstruction() (*api.DebuggerState, error) + // ReverseSingleStep will reverse step a single cpu instruction. + ReverseStepInstruction() (*api.DebuggerState, error) // SwitchThread switches the current thread context. SwitchThread(threadID int) (*api.DebuggerState, error) // SwitchGoroutine switches the current goroutine (and the current thread as well) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 1340df53..7f2531e8 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -624,6 +624,15 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er case api.StepInstruction: d.log.Debug("single stepping") err = d.target.StepInstruction() + case api.ReverseStepInstruction: + d.log.Debug("reverse single stepping") + if err := d.target.Direction(proc.Backward); err != nil { + return nil, err + } + defer func() { + d.target.Direction(proc.Forward) + }() + err = d.target.StepInstruction() case api.StepOut: d.log.Debug("step out") err = proc.StepOut(d.target) diff --git a/service/rpc1/client.go b/service/rpc1/client.go index 945ac18b..410f60ec 100644 --- a/service/rpc1/client.go +++ b/service/rpc1/client.go @@ -125,6 +125,12 @@ func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) { return state, err } +func (c *RPCClient) ReverseStepInstruction() (*api.DebuggerState, error) { + state := new(api.DebuggerState) + err := c.call("Command", &api.DebuggerCommand{Name: api.ReverseStepInstruction}, state) + return state, err +} + func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) { state := new(api.DebuggerState) cmd := &api.DebuggerCommand{ diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 169f35fd..835cd199 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -160,6 +160,12 @@ func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) { return &out.State, err } +func (c *RPCClient) ReverseStepInstruction() (*api.DebuggerState, error) { + var out CommandOut + err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStepInstruction}, &out) + return &out.State, err +} + func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) { var out CommandOut cmd := api.DebuggerCommand{