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
|
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
|
// RequestManualStop sets the `halt` flag and
|
||||||
// sends SIGSTOP to all threads.
|
// sends SIGSTOP to all threads.
|
||||||
func (dbp *Process) RequestManualStop() error {
|
func (dbp *Process) RequestManualStop() error {
|
||||||
@ -381,7 +386,7 @@ func (dbp *Process) Continue() error {
|
|||||||
// runtime.Breakpoint or manual stop
|
// runtime.Breakpoint or manual stop
|
||||||
if dbp.CurrentThread.onRuntimeBreakpoint() {
|
if dbp.CurrentThread.onRuntimeBreakpoint() {
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
if err = dbp.CurrentThread.Step(); err != nil {
|
if err = dbp.CurrentThread.StepInstruction(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -446,18 +451,48 @@ func (dbp *Process) pickCurrentThread(trapthread *Thread) error {
|
|||||||
return dbp.SwitchThread(trapthread.ID)
|
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
|
// one instruction. This method affects only the thread
|
||||||
// asssociated with the selected goroutine. All other
|
// asssociated with the selected goroutine. All other
|
||||||
// threads will remain stopped.
|
// threads will remain stopped.
|
||||||
func (dbp *Process) Step() (err error) {
|
func (dbp *Process) StepInstruction() (err error) {
|
||||||
if dbp.SelectedGoroutine == nil {
|
if dbp.SelectedGoroutine == nil {
|
||||||
return errors.New("cannot single step: no selected goroutine")
|
return errors.New("cannot single step: no selected goroutine")
|
||||||
}
|
}
|
||||||
if dbp.SelectedGoroutine.thread == nil {
|
if dbp.SelectedGoroutine.thread == nil {
|
||||||
return fmt.Errorf("cannot single step: no thread associated with goroutine %d", dbp.SelectedGoroutine.ID)
|
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`.
|
// 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
|
// all threads stopped over a breakpoint are made to step over it
|
||||||
for _, thread := range dbp.Threads {
|
for _, thread := range dbp.Threads {
|
||||||
if thread.CurrentBreakpoint != nil {
|
if thread.CurrentBreakpoint != nil {
|
||||||
if err := thread.Step(); err != nil {
|
if err := thread.StepInstruction(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
thread.CurrentBreakpoint = nil
|
thread.CurrentBreakpoint = nil
|
||||||
|
@ -417,7 +417,7 @@ func (dbp *Process) resume() error {
|
|||||||
// all threads stopped over a breakpoint are made to step over it
|
// all threads stopped over a breakpoint are made to step over it
|
||||||
for _, thread := range dbp.Threads {
|
for _, thread := range dbp.Threads {
|
||||||
if thread.CurrentBreakpoint != nil {
|
if thread.CurrentBreakpoint != nil {
|
||||||
if err := thread.Step(); err != nil {
|
if err := thread.StepInstruction(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
thread.CurrentBreakpoint = nil
|
thread.CurrentBreakpoint = nil
|
||||||
|
@ -177,7 +177,7 @@ func TestStep(t *testing.T) {
|
|||||||
regs := getRegisters(p, t)
|
regs := getRegisters(p, t)
|
||||||
rip := regs.PC()
|
rip := regs.PC()
|
||||||
|
|
||||||
err = p.Step()
|
err = p.CurrentThread.StepInstruction()
|
||||||
assertNoError(err, t, "Step()")
|
assertNoError(err, t, "Step()")
|
||||||
|
|
||||||
regs = getRegisters(p, t)
|
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]
|
thread := dbp.Threads[dbp.os.breakThread]
|
||||||
// This relies on the same assumptions as dbp.setCurrentBreakpoints
|
// This relies on the same assumptions as dbp.setCurrentBreakpoints
|
||||||
if thread.CurrentBreakpoint != nil {
|
if thread.CurrentBreakpoint != nil {
|
||||||
if err := thread.Step(); err != nil {
|
if err := thread.StepInstruction(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
thread.CurrentBreakpoint = nil
|
thread.CurrentBreakpoint = nil
|
||||||
|
@ -4,11 +4,12 @@ import (
|
|||||||
"debug/gosym"
|
"debug/gosym"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/debug/dwarf"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/debug/dwarf"
|
||||||
|
|
||||||
"github.com/derekparker/delve/dwarf/frame"
|
"github.com/derekparker/delve/dwarf/frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -53,20 +54,20 @@ func (thread *Thread) Continue() error {
|
|||||||
// Check whether we are stopped at a breakpoint, and
|
// Check whether we are stopped at a breakpoint, and
|
||||||
// if so, single step over it before continuing.
|
// if so, single step over it before continuing.
|
||||||
if _, ok := thread.dbp.FindBreakpoint(pc); ok {
|
if _, ok := thread.dbp.FindBreakpoint(pc); ok {
|
||||||
if err := thread.Step(); err != nil {
|
if err := thread.StepInstruction(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return thread.resume()
|
return thread.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step a single instruction.
|
// StepInstruction steps a single instruction.
|
||||||
//
|
//
|
||||||
// Executes exactly one instruction and then returns.
|
// Executes exactly one instruction and then returns.
|
||||||
// If the thread is at a breakpoint, we first clear it,
|
// If the thread is at a breakpoint, we first clear it,
|
||||||
// execute the instruction, and then replace the breakpoint.
|
// execute the instruction, and then replace the breakpoint.
|
||||||
// Otherwise we simply execute the next instruction.
|
// Otherwise we simply execute the next instruction.
|
||||||
func (thread *Thread) Step() (err error) {
|
func (thread *Thread) StepInstruction() (err error) {
|
||||||
thread.running = true
|
thread.running = true
|
||||||
thread.singleStepping = true
|
thread.singleStepping = true
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -184,8 +184,10 @@ type EvalScope struct {
|
|||||||
const (
|
const (
|
||||||
// Continue resumes process execution.
|
// Continue resumes process execution.
|
||||||
Continue = "continue"
|
Continue = "continue"
|
||||||
// Step continues for a single instruction, entering function calls.
|
// Step continues to next source line, entering function calls.
|
||||||
Step = "step"
|
Step = "step"
|
||||||
|
// SingleStep continues for exactly 1 cpu instruction.
|
||||||
|
StepInstruction = "stepInstruction"
|
||||||
// Next continues to the next source line, not entering function calls.
|
// Next continues to the next source line, not entering function calls.
|
||||||
Next = "next"
|
Next = "next"
|
||||||
// SwitchThread switches the debugger's current thread context.
|
// SwitchThread switches the debugger's current thread context.
|
||||||
|
@ -25,6 +25,8 @@ type Client interface {
|
|||||||
Next() (*api.DebuggerState, error)
|
Next() (*api.DebuggerState, error)
|
||||||
// Step continues to the next source line, entering function calls.
|
// Step continues to the next source line, entering function calls.
|
||||||
Step() (*api.DebuggerState, error)
|
Step() (*api.DebuggerState, error)
|
||||||
|
// SingleStep will step a single cpu instruction.
|
||||||
|
StepInstruction() (*api.DebuggerState, error)
|
||||||
// SwitchThread switches the current thread context.
|
// SwitchThread switches the current thread context.
|
||||||
SwitchThread(threadID int) (*api.DebuggerState, error)
|
SwitchThread(threadID int) (*api.DebuggerState, error)
|
||||||
// SwitchGoroutine switches the current goroutine (and the current thread as well)
|
// 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:
|
case api.Step:
|
||||||
log.Print("stepping")
|
log.Print("stepping")
|
||||||
err = d.process.Step()
|
err = d.process.Step()
|
||||||
|
case api.StepInstruction:
|
||||||
|
log.Print("single stepping")
|
||||||
|
err = d.process.StepInstruction()
|
||||||
case api.SwitchThread:
|
case api.SwitchThread:
|
||||||
log.Printf("switching to thread %d", command.ThreadID)
|
log.Printf("switching to thread %d", command.ThreadID)
|
||||||
err = d.process.SwitchThread(command.ThreadID)
|
err = d.process.SwitchThread(command.ThreadID)
|
||||||
|
@ -101,6 +101,12 @@ func (c *RPCClient) Step() (*api.DebuggerState, error) {
|
|||||||
return state, err
|
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) {
|
func (c *RPCClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
|
||||||
state := new(api.DebuggerState)
|
state := new(api.DebuggerState)
|
||||||
cmd := &api.DebuggerCommand{
|
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{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
|
||||||
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
|
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
|
||||||
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
|
{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{"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{"threads"}, cmdFn: threads, helpMsg: "Print out info for every traced thread."},
|
||||||
{aliases: []string{"thread", "tr"}, cmdFn: thread, helpMsg: "Switch to the specified 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
|
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 {
|
func next(t *Term, args string) error {
|
||||||
state, err := t.client.Next()
|
state, err := t.client.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user