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
This commit is contained in:
parent
54f1c9b3d4
commit
1bda586115
15
_fixtures/teststep.go
Normal file
15
_fixtures/teststep.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func callme() {
|
||||
fmt.Println("hi")
|
||||
}
|
||||
|
||||
func main() {
|
||||
runtime.Breakpoint()
|
||||
callme()
|
||||
}
|
43
proc/proc.go
43
proc/proc.go
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user