提交 0f4b5150 编写于 作者: A aarzilli

proc, terminal: stepout command

Command to step out of the currently executing function.

Implements #358
上级 54d3eab6
......@@ -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")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册