pkg/terminal,pkg/proc: Implement next-instruction (#3671)
The next-instruction (nexti) command behaves like step-instruction (stepi) however, similar to the `next` command it will step over function calls.
This commit is contained in:
parent
5a9b835406
commit
29aa2ea8c9
@ -13,6 +13,7 @@ Command | Description
|
||||
[call](#call) | Resumes process, injecting a function call (EXPERIMENTAL!!!)
|
||||
[continue](#continue) | Run until breakpoint or program termination.
|
||||
[next](#next) | Step over to next source line.
|
||||
[next-instruction](#next-instruction) | Single step a single cpu instruction, skipping function calls.
|
||||
[rebuild](#rebuild) | Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
|
||||
[restart](#restart) | Restart process.
|
||||
[rev](#rev) | Reverses the execution of the target program for the command specified.
|
||||
@ -523,6 +524,11 @@ Optional [count] argument allows you to skip multiple lines.
|
||||
|
||||
Aliases: n
|
||||
|
||||
## next-instruction
|
||||
Single step a single cpu instruction, skipping function calls.
|
||||
|
||||
Aliases: ni nexti
|
||||
|
||||
## on
|
||||
Executes a command when a breakpoint is hit.
|
||||
|
||||
@ -658,7 +664,7 @@ Aliases: s
|
||||
## step-instruction
|
||||
Single step a single cpu instruction.
|
||||
|
||||
Aliases: si
|
||||
Aliases: si stepi
|
||||
|
||||
## stepout
|
||||
Step out of the current function.
|
||||
|
@ -22,7 +22,7 @@ func TestStepInstructionOnBreakpoint(t *testing.T) {
|
||||
assertNoError(grp.Continue(), t, "Continue()")
|
||||
|
||||
pc := getRegisters(p, t).PC()
|
||||
assertNoError(grp.StepInstruction(), t, "StepInstruction()")
|
||||
assertNoError(grp.StepInstruction(false), t, "StepInstruction()")
|
||||
if pc == getRegisters(p, t).PC() {
|
||||
t.Fatal("Could not step a single instruction")
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func TestSetYMMRegister(t *testing.T) {
|
||||
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
|
||||
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
|
||||
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}))
|
||||
assertNoError(grp.StepInstruction(), t, "SetpInstruction")
|
||||
assertNoError(grp.StepInstruction(false), t, "StepInstruction")
|
||||
|
||||
xmm0 := getReg("after")
|
||||
|
||||
|
@ -315,7 +315,7 @@ func TestHalt(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStep(t *testing.T) {
|
||||
func TestStepInstruction(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
setFunctionBreakpoint(p, t, "main.helloworld")
|
||||
@ -324,7 +324,7 @@ func TestStep(t *testing.T) {
|
||||
regs := getRegisters(p, t)
|
||||
rip := regs.PC()
|
||||
|
||||
err := grp.StepInstruction()
|
||||
err := grp.StepInstruction(false)
|
||||
assertNoError(err, t, "Step()")
|
||||
|
||||
regs = getRegisters(p, t)
|
||||
@ -334,6 +334,19 @@ func TestStep(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNextInstruction(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
setFileBreakpoint(p, t, fixture.Source, 19)
|
||||
assertNoError(grp.Continue(), t, "Continue()")
|
||||
|
||||
err := grp.StepInstruction(true)
|
||||
assertNoError(err, t, "Step()")
|
||||
|
||||
assertLineNumber(p, t, 20, "next-instruction did not step over call")
|
||||
})
|
||||
}
|
||||
|
||||
func TestBreakpoint(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
@ -2719,7 +2732,7 @@ func TestStepOnCallPtrInstr(t *testing.T) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
assertNoError(grp.StepInstruction(), t, "StepInstruction()")
|
||||
assertNoError(grp.StepInstruction(false), t, "StepInstruction()")
|
||||
}
|
||||
|
||||
if !found {
|
||||
@ -3095,7 +3108,7 @@ func TestStepInstructionNoGoroutine(t *testing.T) {
|
||||
withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
|
||||
// Call StepInstruction immediately after launching the program, it should
|
||||
// work even though no goroutine is selected.
|
||||
assertNoError(grp.StepInstruction(), t, "StepInstruction")
|
||||
assertNoError(grp.StepInstruction(false), t, "StepInstruction")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ func (grp *TargetGroup) Continue() error {
|
||||
if err := dbp.ClearSteppingBreakpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
return grp.StepInstruction()
|
||||
return grp.StepInstruction(false)
|
||||
}
|
||||
} else {
|
||||
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
|
||||
@ -455,7 +455,7 @@ func (grp *TargetGroup) Step() (err error) {
|
||||
|
||||
if bpstate := grp.Selected.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && grp.GetDirection() == Backward {
|
||||
grp.Selected.ClearSteppingBreakpoints()
|
||||
return grp.StepInstruction()
|
||||
return grp.StepInstruction(false)
|
||||
}
|
||||
|
||||
return grp.Continue()
|
||||
@ -567,7 +567,7 @@ func (grp *TargetGroup) StepOut() error {
|
||||
// one instruction. This method affects only the thread
|
||||
// associated with the selected goroutine. All other
|
||||
// threads will remain stopped.
|
||||
func (grp *TargetGroup) StepInstruction() (err error) {
|
||||
func (grp *TargetGroup) StepInstruction(skipCalls bool) (err error) {
|
||||
dbp := grp.Selected
|
||||
thread := dbp.CurrentThread()
|
||||
g := dbp.SelectedGoroutine()
|
||||
@ -586,6 +586,12 @@ func (grp *TargetGroup) StepInstruction() (err error) {
|
||||
if ok, err := dbp.Valid(); !ok {
|
||||
return err
|
||||
}
|
||||
var isCall bool
|
||||
instr, err := disassembleCurrentInstruction(dbp, thread, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isCall = len(instr) > 0 && instr[0].IsCall()
|
||||
err = grp.procgrp.StepInstruction(thread.ThreadID())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -599,6 +605,11 @@ func (grp *TargetGroup) StepInstruction() (err error) {
|
||||
dbp.selectedGoroutine = tg
|
||||
}
|
||||
dbp.StopReason = StopNextFinished
|
||||
|
||||
if skipCalls && isCall {
|
||||
return grp.StepOut()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,8 @@ For example:
|
||||
continue encoding/json.Marshal
|
||||
`},
|
||||
{aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, allowedPrefixes: revPrefix, helpMsg: "Single step through program."},
|
||||
{aliases: []string{"step-instruction", "si"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."},
|
||||
{aliases: []string{"step-instruction", "si", "stepi"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."},
|
||||
{aliases: []string{"next-instruction", "ni", "nexti"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.nextInstruction, helpMsg: "Single step a single cpu instruction, skipping function calls."},
|
||||
{aliases: []string{"next", "n"}, group: runCmds, cmdFn: c.next, allowedPrefixes: revPrefix, helpMsg: `Step over to next source line.
|
||||
|
||||
next [count]
|
||||
@ -1511,24 +1512,34 @@ func (c *Commands) step(t *Term, ctx callContext, args string) error {
|
||||
|
||||
var errNotOnFrameZero = errors.New("not on topmost frame")
|
||||
|
||||
// stepInstruction implements the step-instruction (stepi) command.
|
||||
func (c *Commands) stepInstruction(t *Term, ctx callContext, args string) error {
|
||||
return stepInstruction(t, ctx, c.frame, false)
|
||||
}
|
||||
|
||||
// nextInstruction implements the next-instruction (nexti) command.
|
||||
func (c *Commands) nextInstruction(t *Term, ctx callContext, args string) error {
|
||||
return stepInstruction(t, ctx, c.frame, true)
|
||||
}
|
||||
|
||||
func stepInstruction(t *Term, ctx callContext, frame int, skipCalls bool) error {
|
||||
if err := scopePrefixSwitch(t, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.frame != 0 {
|
||||
if frame != 0 {
|
||||
return errNotOnFrameZero
|
||||
}
|
||||
|
||||
defer t.onStop()
|
||||
|
||||
var fn func() (*api.DebuggerState, error)
|
||||
var fn func(bool) (*api.DebuggerState, error)
|
||||
if ctx.Prefix == revPrefix {
|
||||
fn = t.client.ReverseStepInstruction
|
||||
} else {
|
||||
fn = t.client.StepInstruction
|
||||
}
|
||||
|
||||
state, err := exitedToError(fn())
|
||||
state, err := exitedToError(fn(skipCalls))
|
||||
if err != nil {
|
||||
printcontextNoState(t)
|
||||
return err
|
||||
|
@ -456,8 +456,12 @@ const (
|
||||
ReverseStepOut = "reverseStepOut"
|
||||
// StepInstruction continues for exactly 1 cpu instruction.
|
||||
StepInstruction = "stepInstruction"
|
||||
// NextInstruction continues for 1 cpu instruction, skipping over CALL instructions.
|
||||
NextInstruction = "nextInstruction"
|
||||
// ReverseStepInstruction reverses execution for exactly 1 cpu instruction.
|
||||
ReverseStepInstruction = "reverseStepInstruction"
|
||||
// ReverseNextInstruction reverses execution for 1 cpu instruction, skipping over CALL instructions.
|
||||
ReverseNextInstruction = "reverseNextInstruction"
|
||||
// Next continues to the next source line, not entering function calls.
|
||||
Next = "next"
|
||||
// ReverseNext continues backward to the previous line of source code, not entering function calls.
|
||||
|
@ -53,9 +53,9 @@ type Client interface {
|
||||
Call(goroutineID int64, expr string, unsafe bool) (*api.DebuggerState, error)
|
||||
|
||||
// StepInstruction will step a single cpu instruction.
|
||||
StepInstruction() (*api.DebuggerState, error)
|
||||
StepInstruction(skipCalls bool) (*api.DebuggerState, error)
|
||||
// ReverseStepInstruction will reverse step a single cpu instruction.
|
||||
ReverseStepInstruction() (*api.DebuggerState, error)
|
||||
ReverseStepInstruction(skipCalls bool) (*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)
|
||||
|
@ -1255,13 +1255,25 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.target.StepInstruction()
|
||||
err = d.target.StepInstruction(false)
|
||||
case api.ReverseStepInstruction:
|
||||
d.log.Debug("reverse single stepping")
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.target.StepInstruction()
|
||||
err = d.target.StepInstruction(false)
|
||||
case api.NextInstruction:
|
||||
d.log.Debug("single stepping")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.target.StepInstruction(true)
|
||||
case api.ReverseNextInstruction:
|
||||
d.log.Debug("reverse single stepping")
|
||||
if err := d.target.ChangeDirection(proc.Backward); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.target.StepInstruction(true)
|
||||
case api.StepOut:
|
||||
d.log.Debug("step out")
|
||||
if err := d.target.ChangeDirection(proc.Forward); err != nil {
|
||||
|
@ -183,15 +183,23 @@ func (c *RPCClient) Call(goroutineID int64, expr string, unsafe bool) (*api.Debu
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) {
|
||||
func (c *RPCClient) StepInstruction(skipCalls bool) (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.StepInstruction}, &out)
|
||||
name := api.StepInstruction
|
||||
if skipCalls {
|
||||
name = api.NextInstruction
|
||||
}
|
||||
err := c.call("Command", api.DebuggerCommand{Name: name}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ReverseStepInstruction() (*api.DebuggerState, error) {
|
||||
func (c *RPCClient) ReverseStepInstruction(skipCalls bool) (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.ReverseStepInstruction}, &out)
|
||||
name := api.ReverseStepInstruction
|
||||
if skipCalls {
|
||||
name = api.ReverseNextInstruction
|
||||
}
|
||||
err := c.call("Command", api.DebuggerCommand{Name: name}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
|
@ -1334,7 +1334,7 @@ func TestIssue355(t *testing.T) {
|
||||
assertErrorOrExited(s, err, t, "Next()")
|
||||
s, err = c.Step()
|
||||
assertErrorOrExited(s, err, t, "Step()")
|
||||
s, err = c.StepInstruction()
|
||||
s, err = c.StepInstruction(false)
|
||||
assertErrorOrExited(s, err, t, "StepInstruction()")
|
||||
s, err = c.SwitchThread(tid)
|
||||
assertErrorOrExited(s, err, t, "SwitchThread()")
|
||||
@ -1452,7 +1452,7 @@ func TestDisasm(t *testing.T) {
|
||||
if count > 20 {
|
||||
t.Fatal("too many step instructions executed without finding a call instruction")
|
||||
}
|
||||
state, err := c.StepInstruction()
|
||||
state, err := c.StepInstruction(false)
|
||||
assertNoError(err, t, fmt.Sprintf("StepInstruction() %d", count))
|
||||
|
||||
d3, err = c.DisassemblePC(api.EvalScope{GoroutineID: -1}, state.CurrentThread.PC, api.IntelFlavour)
|
||||
@ -3105,3 +3105,19 @@ func TestClientServer_chanGoroutines(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNextInstruction(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestClient2("testprog", t, func(c service.Client) {
|
||||
fp := testProgPath(t, "testprog")
|
||||
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 19})
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
|
||||
state, err = c.StepInstruction(true)
|
||||
assertNoError(err, t, "Step()")
|
||||
if state.CurrentThread.Line != 20 {
|
||||
t.Fatalf("expected line %d got %d", 20, state.CurrentThread.Line)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user