提交 1bda5861 编写于 作者: D Derek Parker

proc: step now goes to next line, including funcs

This patch modifies the `step` command to step to the next source line,
stepping into any function encountered along the way.

Fixes #360
上级 54f1c9b3
package main
import (
"fmt"
"runtime"
)
func callme() {
fmt.Println("hi")
}
func main() {
runtime.Breakpoint()
callme()
}
......@@ -220,6 +220,11 @@ func (dbp *Process) FunctionEntryToFirstLine(entry uint64) (uint64, error) {
return entry, nil
}
// CurrentLocation returns the location of the current thread.
func (dbp *Process) CurrentLocation() (*Location, error) {
return dbp.CurrentThread.Location()
}
// RequestManualStop sets the `halt` flag and
// sends SIGSTOP to all threads.
func (dbp *Process) RequestManualStop() error {
......@@ -381,7 +386,7 @@ func (dbp *Process) Continue() error {
// runtime.Breakpoint or manual stop
if dbp.CurrentThread.onRuntimeBreakpoint() {
for i := 0; i < 2; i++ {
if err = dbp.CurrentThread.Step(); err != nil {
if err = dbp.CurrentThread.StepInstruction(); err != nil {
return err
}
}
......@@ -446,18 +451,48 @@ func (dbp *Process) pickCurrentThread(trapthread *Thread) error {
return dbp.SwitchThread(trapthread.ID)
}
// Step will continue the current thread for exactly
// Step will continue until another source line is reached.
// Will step into functions.
func (dbp *Process) Step() (err error) {
fn := func() error {
var nloc *Location
th := dbp.CurrentThread
loc, err := th.Location()
if err != nil {
return err
}
for {
err = dbp.CurrentThread.singleStep()
if err != nil {
return err
}
nloc, err = th.Location()
if err != nil {
return err
}
if nloc.File != loc.File {
return nil
}
if nloc.File == loc.File && nloc.Line != loc.Line {
return nil
}
}
}
return dbp.run(fn)
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// asssociated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Process) Step() (err error) {
func (dbp *Process) StepInstruction() (err error) {
if dbp.SelectedGoroutine == nil {
return errors.New("cannot single step: no selected goroutine")
}
if dbp.SelectedGoroutine.thread == nil {
return fmt.Errorf("cannot single step: no thread associated with goroutine %d", dbp.SelectedGoroutine.ID)
}
return dbp.run(dbp.SelectedGoroutine.thread.Step)
return dbp.run(dbp.SelectedGoroutine.thread.StepInstruction)
}
// SwitchThread changes from current thread to the thread specified by `tid`.
......
......@@ -396,7 +396,7 @@ func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.Step(); err != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
......
......@@ -417,7 +417,7 @@ func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.Step(); err != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
......
......@@ -177,7 +177,7 @@ func TestStep(t *testing.T) {
regs := getRegisters(p, t)
rip := regs.PC()
err = p.Step()
err = p.CurrentThread.StepInstruction()
assertNoError(err, t, "Step()")
regs = getRegisters(p, t)
......@@ -1438,3 +1438,27 @@ func TestIssue356(t *testing.T) {
}
})
}
func TestStepIntoFunction(t *testing.T) {
withTestProcess("teststep", t, func(p *Process, fixture protest.Fixture) {
// Continue until breakpoint
assertNoError(p.Continue(), t, "Continue() returned an error")
// Step into function
assertNoError(p.Step(), t, "Step() returned an error")
// We should now be inside the function.
loc, err := p.CurrentLocation()
if err != nil {
t.Fatal(err)
}
if loc.Fn.Name != "main.callme" {
t.Fatalf("expected to be within the 'callme' function, was in %s instead", loc.Fn.Name)
}
if !strings.Contains(loc.File, "teststep") {
t.Fatalf("debugger stopped at incorrect location: %s:%d", loc.File, loc.Line)
}
// TODO(derekparker) consider skipping function prologue when stepping into func.
if loc.Line != 8 {
t.Fatalf("debugger stopped at incorrect line: %d", loc.Line)
}
})
}
......@@ -461,7 +461,7 @@ func (dbp *Process) resume() error {
thread := dbp.Threads[dbp.os.breakThread]
// This relies on the same assumptions as dbp.setCurrentBreakpoints
if thread.CurrentBreakpoint != nil {
if err := thread.Step(); err != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
......
......@@ -4,11 +4,12 @@ import (
"debug/gosym"
"encoding/binary"
"fmt"
"golang.org/x/debug/dwarf"
"path/filepath"
"reflect"
"runtime"
"golang.org/x/debug/dwarf"
"github.com/derekparker/delve/dwarf/frame"
)
......@@ -53,20 +54,20 @@ func (thread *Thread) Continue() error {
// Check whether we are stopped at a breakpoint, and
// if so, single step over it before continuing.
if _, ok := thread.dbp.FindBreakpoint(pc); ok {
if err := thread.Step(); err != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
}
return thread.resume()
}
// Step a single instruction.
// StepInstruction steps a single instruction.
//
// Executes exactly one instruction and then returns.
// If the thread is at a breakpoint, we first clear it,
// execute the instruction, and then replace the breakpoint.
// Otherwise we simply execute the next instruction.
func (thread *Thread) Step() (err error) {
func (thread *Thread) StepInstruction() (err error) {
thread.running = true
thread.singleStepping = true
defer func() {
......
......@@ -184,8 +184,10 @@ type EvalScope struct {
const (
// Continue resumes process execution.
Continue = "continue"
// Step continues for a single instruction, entering function calls.
// Step continues to next source line, entering function calls.
Step = "step"
// SingleStep continues for exactly 1 cpu instruction.
StepInstruction = "stepInstruction"
// Next continues to the next source line, not entering function calls.
Next = "next"
// SwitchThread switches the debugger's current thread context.
......
......@@ -25,6 +25,8 @@ type Client interface {
Next() (*api.DebuggerState, error)
// Step continues to the next source line, entering function calls.
Step() (*api.DebuggerState, error)
// SingleStep will step a single cpu instruction.
StepInstruction() (*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)
......
......@@ -261,6 +261,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
case api.Step:
log.Print("stepping")
err = d.process.Step()
case api.StepInstruction:
log.Print("single stepping")
err = d.process.StepInstruction()
case api.SwitchThread:
log.Printf("switching to thread %d", command.ThreadID)
err = d.process.SwitchThread(command.ThreadID)
......
......@@ -101,6 +101,12 @@ func (c *RPCClient) Step() (*api.DebuggerState, error) {
return state, err
}
func (c *RPCClient) StepInstruction() (*api.DebuggerState, error) {
state := new(api.DebuggerState)
err := c.call("Command", &api.DebuggerCommand{Name: api.StepInstruction}, state)
return state, err
}
func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
state := new(api.DebuggerState)
cmd := &api.DebuggerCommand{
......
......@@ -60,7 +60,8 @@ func DebugCommands(client service.Client) *Commands {
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
{aliases: []string{"step", "s"}, cmdFn: step, helpMsg: "Single step through program."},
{aliases: []string{"step-instruction", "si"}, cmdFn: stepInstruction, helpMsg: "Single step a single cpu instruction."},
{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
{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."},
......@@ -471,6 +472,15 @@ func step(t *Term, args string) error {
return nil
}
func stepInstruction(t *Term, args string) error {
state, err := t.client.StepInstruction()
if err != nil {
return err
}
printcontext(t, state)
return nil
}
func next(t *Term, args string) error {
state, err := t.client.Next()
if err != nil {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册