Skip to content

Commit

Permalink
proc: Implement Step using Continue
Browse files Browse the repository at this point in the history
Instead of repeatedly calling StepInstruction set breakpoints to the
destination of CALL instructions (or on the CALL instructions
themselves for indirect CALLs), then call Continue.
Calls to unexported runtime functions are skipped.
Reduces the number of code paths managing inferior state from 3 to 2
(StepInstruction, Continue).

Fixes go-delve#561
  • Loading branch information
aarzilli committed Sep 27, 2016
1 parent 8d58262 commit 7c49d49
Show file tree
Hide file tree
Showing 8 changed files with 587 additions and 177 deletions.
11 changes: 11 additions & 0 deletions _fixtures/issue561.go
@@ -0,0 +1,11 @@
package main

import "fmt"

func testfunction() {
fmt.Printf("here!\n")
}

func main() {
testfunction()
}
42 changes: 42 additions & 0 deletions _fixtures/teststepconcurrent.go
@@ -0,0 +1,42 @@
package main

import (
"fmt"
"time"
"sync"
)

var v int = 99
var wg sync.WaitGroup
var s string

func Foo(x, y int) (z int) {
//s = fmt.Sprintf("x=%d, y=%d, z=%d\n", x, y, z)
z = x + y
return
}

func Threads(fn func(x, y int) int) {
for j := 0; j < 100; j++ {
wg.Add(1)
go func() {
for k := 0; k < 100; k++ {
fn(1, 2)
time.Sleep(10 * time.Millisecond)
}
wg.Done()
}()
}
}

func main() {
x := v
y := x * x
var z int
Threads(Foo)
for i := 0; i < 100; i++ {
z = Foo(x, y)
}
fmt.Printf("z=%d\n", z)
wg.Wait()
}
22 changes: 22 additions & 0 deletions _fixtures/teststepprog.go
@@ -0,0 +1,22 @@
package main

var n = 0

func CallFn2() {
n++
}

func CallFn(fn func()) {
fn()
}

func CallEface(eface interface{}) {
if eface != nil {
n++
}
}

func main() {
CallFn(CallFn2)
CallEface(n)
}
56 changes: 41 additions & 15 deletions proc/breakpoints.go
Expand Up @@ -17,11 +17,11 @@ type Breakpoint struct {
File string
Line int

Addr uint64 // Address breakpoint is set for.
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
Name string // User defined name of the breakpoint
ID int // Monotonically increasing ID.
Temp bool // Whether this is a temp breakpoint (for next'ing).
Addr uint64 // Address breakpoint is set for.
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
Name string // User defined name of the breakpoint
ID int // Monotonically increasing ID.
Kind BreakpointKind // Whether this is a temp breakpoint (for next'ing or stepping).

// Breakpoint information
Tracepoint bool // Tracepoint flag
Expand All @@ -33,20 +33,41 @@ type Breakpoint struct {
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached

// When DeferCond is set the breakpoint will only trigger
// if the caller is runtime.gopanic or if the return address
// is in the DeferReturns array.
// Next sets DeferCond for the breakpoint it sets on the
// DeferReturns: when kind == NextDeferBreakpoint this breakpoint
// will also check if the caller is runtime.gopanic or if the return
// address is in the DeferReturns array.
// Next uses NextDeferBreakpoints for the breakpoint it sets on the
// deferred function, DeferReturns is populated with the
// addresses of calls to runtime.deferreturn in the current
// function. This insures that the breakpoint on the deferred
// function only triggers on panic or on the defer call to
// the function, not when the function is called directly
DeferCond bool
DeferReturns []uint64
Cond ast.Expr // When Cond is not nil the breakpoint will be triggered only if evaluating Cond returns true
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
}

// Breakpoint Kind determines the behavior of delve when the
// breakpoint is reached.
type BreakpointKind int

const (
// UserBreakpoint is a user set breakpoint
UserBreakpoint BreakpointKind = iota
// NextBreakpoint is a breakpoint set by Next, Continue
// will stop on it and delete it
NextBreakpoint
// NextDeferBreakpoint is a breakpoint set by Next on the
// first deferred function. In addition to checking their condition
// breakpoints of this kind will also check that the function has been
// called by runtime.gopanic or through runtime.deferreturn.
NextDeferBreakpoint
// StepBreakpoint is a breakpoint set by Step on a CALL instruction,
// Continue will set a new breakpoint (of NextBreakpoint kind) on the
// destination of CALL, delete this breakpoint and then continue again
StepBreakpoint
)

func (bp *Breakpoint) String() string {
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.ID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
}
Expand Down Expand Up @@ -82,7 +103,7 @@ func (iae InvalidAddressError) Error() string {
return fmt.Sprintf("Invalid address %#v\n", iae.address)
}

func (dbp *Process) setBreakpoint(tid int, addr uint64, temp bool) (*Breakpoint, error) {
func (dbp *Process) setBreakpoint(tid int, addr uint64, kind BreakpointKind) (*Breakpoint, error) {
if bp, ok := dbp.FindBreakpoint(addr); ok {
return nil, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
}
Expand All @@ -97,12 +118,12 @@ func (dbp *Process) setBreakpoint(tid int, addr uint64, temp bool) (*Breakpoint,
File: f,
Line: l,
Addr: addr,
Temp: temp,
Kind: kind,
Cond: nil,
HitCount: map[int]uint64{},
}

if temp {
if kind != UserBreakpoint {
dbp.tempBreakpointIDCounter++
newBreakpoint.ID = dbp.tempBreakpointIDCounter
} else {
Expand Down Expand Up @@ -133,7 +154,7 @@ func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) {
if bp.Cond == nil {
return true, nil
}
if bp.DeferCond {
if bp.Kind == NextDeferBreakpoint {
frames, err := thread.Stacktrace(2)
if err == nil {
ispanic := len(frames) >= 3 && frames[2].Current.Fn != nil && frames[2].Current.Fn.Name == "runtime.gopanic"
Expand Down Expand Up @@ -168,6 +189,11 @@ func (bp *Breakpoint) checkCondition(thread *Thread) (bool, error) {
return constant.BoolVal(v.Value), nil
}

// Internal returns true for breakpoints not set directly by the user.
func (bp *Breakpoint) Internal() bool {
return bp.Kind != UserBreakpoint
}

// NoBreakpointError is returned when trying to
// clear a breakpoint that does not exist.
type NoBreakpointError struct {
Expand Down

0 comments on commit 7c49d49

Please sign in to comment.