proc, terminal: stepout command
Command to step out of the currently executing function. Implements #358
This commit is contained in:
parent
54d3eab63a
commit
0f4b5150c3
54
proc/proc.go
54
proc/proc.go
@ -9,6 +9,7 @@ import (
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -505,6 +506,59 @@ func (dbp *Process) StepInstruction() (err error) {
|
||||
return dbp.SelectedGoroutine.thread.SetCurrentBreakpoint()
|
||||
}
|
||||
|
||||
// StepOut will continue until the current goroutine exits the
|
||||
// function currently being executed or a deferred function is executed
|
||||
func (dbp *Process) StepOut() error {
|
||||
cond := sameGoroutineCondition(dbp.SelectedGoroutine)
|
||||
|
||||
topframe, err := topframe(dbp.SelectedGoroutine, dbp.CurrentThread)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pcs := []uint64{}
|
||||
|
||||
var deferpc uint64 = 0
|
||||
if filepath.Ext(topframe.Current.File) == ".go" {
|
||||
if dbp.SelectedGoroutine != nil && dbp.SelectedGoroutine.DeferPC != 0 {
|
||||
_, _, deferfn := dbp.goSymTable.PCToLine(dbp.SelectedGoroutine.DeferPC)
|
||||
deferpc, err = dbp.FirstPCAfterPrologue(deferfn, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pcs = append(pcs, deferpc)
|
||||
}
|
||||
}
|
||||
|
||||
if topframe.Ret == 0 && deferpc == 0 {
|
||||
return errors.New("nothing to stepout to")
|
||||
}
|
||||
|
||||
if deferpc != 0 && deferpc != topframe.Current.PC {
|
||||
bp, err := dbp.SetBreakpoint(deferpc, NextDeferBreakpoint, cond)
|
||||
if err != nil {
|
||||
if _, ok := err.(BreakpointExistsError); !ok {
|
||||
dbp.ClearInternalBreakpoints()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if bp != nil {
|
||||
// For StepOut we do not want to step into the deferred function
|
||||
// when it's called by runtime.deferreturn so we do not populate
|
||||
// DeferReturns.
|
||||
bp.DeferReturns = []uint64{}
|
||||
}
|
||||
}
|
||||
|
||||
if topframe.Ret != 0 {
|
||||
if err := dbp.setInternalBreakpoints(topframe.Current.PC, []uint64{topframe.Ret}, NextBreakpoint, cond); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return dbp.Continue()
|
||||
}
|
||||
|
||||
// SwitchThread changes from current thread to the thread specified by `tid`.
|
||||
func (dbp *Process) SwitchThread(tid int) error {
|
||||
if dbp.exited {
|
||||
|
@ -141,6 +141,18 @@ func setFunctionBreakpoint(p *Process, fname string) (*Breakpoint, error) {
|
||||
return p.SetBreakpoint(addr, UserBreakpoint, nil)
|
||||
}
|
||||
|
||||
func setFileBreakpoint(p *Process, t *testing.T, fixture protest.Fixture, lineno int) *Breakpoint {
|
||||
addr, err := p.FindFileLocation(fixture.Source, lineno)
|
||||
if err != nil {
|
||||
t.Fatalf("FindFileLocation: %v", err)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addr, UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SetBreakpoint: %v", err)
|
||||
}
|
||||
return bp
|
||||
}
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
stopChan := make(chan interface{})
|
||||
withTestProcess("loopprog", t, func(p *Process, fixture protest.Fixture) {
|
||||
@ -2044,6 +2056,27 @@ func TestIssue561(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStepOut(t *testing.T) {
|
||||
withTestProcess("testnextprog", t, func(p *Process, fixture protest.Fixture) {
|
||||
bp, err := setFunctionBreakpoint(p, "main.helloworld")
|
||||
assertNoError(err, t, "SetBreakpoint()")
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
p.ClearBreakpoint(bp.Addr)
|
||||
|
||||
f, lno := currentLineNumber(p, t)
|
||||
if lno != 13 {
|
||||
t.Fatalf("wrong line number %s:%d, expected %d", f, lno, 13)
|
||||
}
|
||||
|
||||
assertNoError(p.StepOut(), t, "StepOut()")
|
||||
|
||||
f, lno = currentLineNumber(p, t)
|
||||
if lno != 35 {
|
||||
t.Fatalf("wrong line number %s:%d, expected %d", f, lno, 34)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStepConcurrentDirect(t *testing.T) {
|
||||
withTestProcess("teststepconcurrent", t, func(p *Process, fixture protest.Fixture) {
|
||||
pc, err := p.FindFileLocation(fixture.Source, 37)
|
||||
@ -2161,6 +2194,47 @@ func TestStepConcurrentPtr(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStepOutDefer(t *testing.T) {
|
||||
withTestProcess("testnextdefer", t, func(p *Process, fixture protest.Fixture) {
|
||||
pc, err := p.FindFileLocation(fixture.Source, 9)
|
||||
assertNoError(err, t, "FindFileLocation()")
|
||||
bp, err := p.SetBreakpoint(pc, UserBreakpoint, nil)
|
||||
assertNoError(err, t, "SetBreakpoint()")
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
p.ClearBreakpoint(bp.Addr)
|
||||
|
||||
f, lno := currentLineNumber(p, t)
|
||||
if lno != 9 {
|
||||
t.Fatalf("worng line number %s:%d, expected %d", f, lno, 5)
|
||||
}
|
||||
|
||||
assertNoError(p.StepOut(), t, "StepOut()")
|
||||
|
||||
f, l, _ := p.goSymTable.PCToLine(currentPC(p, t))
|
||||
if f == fixture.Source || l == 6 {
|
||||
t.Fatalf("wrong location %s:%d, expected to end somewhere in runtime", f, l)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStepOutDeferReturnAndDirectCall(t *testing.T) {
|
||||
// StepOut should not step into a deferred function if it is called
|
||||
// directly, only if it is called through a panic.
|
||||
// Here we test the case where the function is called by a deferreturn
|
||||
withTestProcess("defercall", t, func(p *Process, fixture protest.Fixture) {
|
||||
bp := setFileBreakpoint(p, t, fixture, 11)
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
p.ClearBreakpoint(bp.Addr)
|
||||
|
||||
assertNoError(p.StepOut(), t, "StepOut()")
|
||||
|
||||
f, ln := currentLineNumber(p, t)
|
||||
if ln != 28 {
|
||||
t.Fatalf("wrong line number, expected %d got %s:%d", 28, f, ln)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStepOnCallPtrInstr(t *testing.T) {
|
||||
withTestProcess("teststepprog", t, func(p *Process, fixture protest.Fixture) {
|
||||
pc, err := p.FindFileLocation(fixture.Source, 10)
|
||||
@ -2214,3 +2288,21 @@ func TestIssue594(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStepOutPanicAndDirectCall(t *testing.T) {
|
||||
// StepOut should not step into a deferred function if it is called
|
||||
// directly, only if it is called through a panic.
|
||||
// Here we test the case where the function is called by a panic
|
||||
withTestProcess("defercall", t, func(p *Process, fixture protest.Fixture) {
|
||||
bp := setFileBreakpoint(p, t, fixture, 17)
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
p.ClearBreakpoint(bp.Addr)
|
||||
|
||||
assertNoError(p.StepOut(), t, "StepOut()")
|
||||
|
||||
f, ln := currentLineNumber(p, t)
|
||||
if ln != 5 {
|
||||
t.Fatalf("wrong line number, expected %d got %s:%d", 5, f, ln)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -237,6 +237,8 @@ const (
|
||||
Continue = "continue"
|
||||
// Step continues to next source line, entering function calls.
|
||||
Step = "step"
|
||||
// StepOut continues to the return address of the current function
|
||||
StepOut = "stepOut"
|
||||
// SingleStep continues for exactly 1 cpu instruction.
|
||||
StepInstruction = "stepInstruction"
|
||||
// Next continues to the next source line, not entering function calls.
|
||||
|
@ -25,6 +25,9 @@ type Client interface {
|
||||
Next() (*api.DebuggerState, error)
|
||||
// Step continues to the next source line, entering function calls.
|
||||
Step() (*api.DebuggerState, error)
|
||||
// StepOut continues to the return address of the current function
|
||||
StepOut() (*api.DebuggerState, error)
|
||||
|
||||
// SingleStep will step a single cpu instruction.
|
||||
StepInstruction() (*api.DebuggerState, error)
|
||||
// SwitchThread switches the current thread context.
|
||||
|
@ -418,6 +418,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
||||
case api.StepInstruction:
|
||||
log.Print("single stepping")
|
||||
err = d.process.StepInstruction()
|
||||
case api.StepOut:
|
||||
log.Print("step out")
|
||||
err = d.process.StepOut()
|
||||
case api.SwitchThread:
|
||||
log.Printf("switching to thread %d", command.ThreadID)
|
||||
err = d.process.SwitchThread(command.ThreadID)
|
||||
|
@ -103,6 +103,12 @@ func (c *RPCClient) Step() (*api.DebuggerState, error) {
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) StepOut() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", &api.DebuggerCommand{ Name: api.StepOut}, &out)
|
||||
return &out.State, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) {
|
||||
var out CommandOut
|
||||
err := c.call("Command", api.DebuggerCommand{Name: api.StepInstruction}, &out)
|
||||
|
@ -199,6 +199,23 @@ func TestClientServer_step(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServer_stepout(t *testing.T) {
|
||||
withTestClient2("testnextprog", t, func(c service.Client) {
|
||||
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: -1})
|
||||
assertNoError(err, t, "CreateBreakpoint()")
|
||||
stateBefore := <-c.Continue()
|
||||
assertNoError(stateBefore.Err, t, "Continue()")
|
||||
if stateBefore.CurrentThread.Line != 13 {
|
||||
t.Fatalf("wrong line number %s:%d, expected %d", stateBefore.CurrentThread.File, stateBefore.CurrentThread.Line, 13)
|
||||
}
|
||||
stateAfter, err := c.StepOut()
|
||||
assertNoError(err, t, "StepOut()")
|
||||
if stateAfter.CurrentThread.Line != 35 {
|
||||
t.Fatalf("wrong line number %s:%d, expected %d", stateAfter.CurrentThread.File, stateAfter.CurrentThread.Line, 13)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testnext2(testcases []nextTest, initialLocation string, t *testing.T) {
|
||||
withTestClient2("testnextprog", t, func(c service.Client) {
|
||||
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1})
|
||||
@ -278,7 +295,7 @@ func TestNextGeneral(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
testnext(testcases, "main.testnext", t)
|
||||
testnext2(testcases, "main.testnext", t)
|
||||
}
|
||||
|
||||
func TestNextFunctionReturn(t *testing.T) {
|
||||
@ -287,7 +304,7 @@ func TestNextFunctionReturn(t *testing.T) {
|
||||
{14, 15},
|
||||
{15, 35},
|
||||
}
|
||||
testnext(testcases, "main.helloworld", t)
|
||||
testnext2(testcases, "main.helloworld", t)
|
||||
}
|
||||
|
||||
func TestClientServer_breakpointInMainThread(t *testing.T) {
|
||||
|
@ -102,6 +102,7 @@ See also: "help on", "help cond" and "help clear"`},
|
||||
{aliases: []string{"step", "s"}, allowedPrefixes: scopePrefix, cmdFn: step, helpMsg: "Single step through program."},
|
||||
{aliases: []string{"step-instruction", "si"}, allowedPrefixes: scopePrefix, cmdFn: stepInstruction, helpMsg: "Single step a single cpu instruction."},
|
||||
{aliases: []string{"next", "n"}, allowedPrefixes: scopePrefix, cmdFn: next, helpMsg: "Step over to next source line."},
|
||||
{aliases: []string{"stepout"}, allowedPrefixes: scopePrefix, cmdFn: stepout, helpMsg: "Step out of the current function."},
|
||||
{aliases: []string{"threads"}, cmdFn: threads, helpMsg: "Print out info for every traced thread."},
|
||||
{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: `Switch to the specified thread.
|
||||
|
||||
@ -658,6 +659,18 @@ func next(t *Term, ctx callContext, args string) error {
|
||||
return continueUntilCompleteNext(t, state, "next")
|
||||
}
|
||||
|
||||
func stepout(t *Term, ctx callContext, args string) error {
|
||||
if err := scopePrefixSwitch(t, ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := t.client.StepOut()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printcontext(t, state)
|
||||
return continueUntilCompleteNext(t, state, "stepout")
|
||||
}
|
||||
|
||||
func clear(t *Term, ctx callContext, args string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
|
Loading…
Reference in New Issue
Block a user