proc,service: represent logical breakpoints explicitly (#2946)

Adds a LogicalBreakpoint type to represent logical breakpoints
explicitly. Until now logical breakpoints were constructed implicitly
by grouping physical breakpoints together by their LogicalID.

Having logical breakpoints represented explicitly allows for a simpler
implementation of disabled breakpoints, as well as allowing a simple
implementation of delayed breakpoints (#1653, #2551) and in general of
breakpoints spanning multiple processes if we implement debugging
process trees (#2551).

Updates #1653
Updates #2551
This commit is contained in:
Alessandro Arzilli 2022-05-25 22:58:26 +02:00 committed by GitHub
parent 5bc8460328
commit 78471b3a5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 413 additions and 417 deletions

@ -47,7 +47,6 @@ type Breakpoint struct {
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
WatchExpr string
WatchType WatchType
@ -59,14 +58,7 @@ type Breakpoint struct {
Breaklets []*Breaklet
// Breakpoint information
Tracepoint bool // Tracepoint flag
TraceReturn bool
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
UserData interface{} // Any additional information about the breakpoint
Logical *LogicalBreakpoint
// ReturnInfo describes how to collect return variables when this
// breakpoint is hit as a return breakpoint.
@ -85,9 +77,6 @@ type Breaklet struct {
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
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
// 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.
@ -99,13 +88,6 @@ type Breaklet struct {
// the function, not when the function is called directly
DeferReturns []uint64
// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
// true with the TotalHitCount.
HitCond *struct {
Op token.Token
Val int
}
// checkPanicCall checks that the breakpoint happened while the function was
// called by a panic. It is only checked for WatchOutOfScopeBreakpoint Kind.
checkPanicCall bool
@ -206,10 +188,12 @@ func (bp *Breakpoint) VerboseDescr() []string {
r = append(r, fmt.Sprintf("HWBreakIndex=%#x watchStackOff=%#x", bp.HWBreakIndex, bp.watchStackOff))
}
lbp := bp.Logical
for _, breaklet := range bp.Breaklets {
switch breaklet.Kind {
case UserBreakpoint:
r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", exprToString(breaklet.Cond), breaklet.HitCond))
r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", exprToString(breaklet.Cond), lbp.HitCond))
case NextBreakpoint:
r = append(r, fmt.Sprintf("Next Cond=%q", exprToString(breaklet.Cond)))
case NextDeferBreakpoint:
@ -280,11 +264,14 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
switch breaklet.Kind {
case UserBreakpoint:
if g, err := GetG(thread); err == nil {
breaklet.HitCount[g.ID]++
lbp := bpstate.Breakpoint.Logical
if lbp != nil {
if g, err := GetG(thread); err == nil {
lbp.HitCount[g.ID]++
}
lbp.TotalHitCount++
}
breaklet.TotalHitCount++
active = checkHitCond(breaklet)
active = checkHitCond(lbp)
case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint:
nextDeferOk := true
@ -331,26 +318,26 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
}
// checkHitCond evaluates bp's hit condition on thread.
func checkHitCond(breaklet *Breaklet) bool {
if breaklet.HitCond == nil {
func checkHitCond(lbp *LogicalBreakpoint) bool {
if lbp == nil || lbp.HitCond == nil {
return true
}
// Evaluate the breakpoint condition.
switch breaklet.HitCond.Op {
switch lbp.HitCond.Op {
case token.EQL:
return int(breaklet.TotalHitCount) == breaklet.HitCond.Val
return int(lbp.TotalHitCount) == lbp.HitCond.Val
case token.NEQ:
return int(breaklet.TotalHitCount) != breaklet.HitCond.Val
return int(lbp.TotalHitCount) != lbp.HitCond.Val
case token.GTR:
return int(breaklet.TotalHitCount) > breaklet.HitCond.Val
return int(lbp.TotalHitCount) > lbp.HitCond.Val
case token.LSS:
return int(breaklet.TotalHitCount) < breaklet.HitCond.Val
return int(lbp.TotalHitCount) < lbp.HitCond.Val
case token.GEQ:
return int(breaklet.TotalHitCount) >= breaklet.HitCond.Val
return int(lbp.TotalHitCount) >= lbp.HitCond.Val
case token.LEQ:
return int(breaklet.TotalHitCount) <= breaklet.HitCond.Val
return int(lbp.TotalHitCount) <= lbp.HitCond.Val
case token.REM:
return int(breaklet.TotalHitCount)%breaklet.HitCond.Val == 0
return int(lbp.TotalHitCount)%lbp.HitCond.Val == 0
}
return false
}
@ -468,6 +455,9 @@ func (nbp NoBreakpointError) Error() string {
type BreakpointMap struct {
M map[uint64]*Breakpoint
// Logical is a map of logical breakpoints.
Logical map[int]*LogicalBreakpoint
// WatchOutOfScope is the list of watchpoints that went out of scope during
// the last resume operation
WatchOutOfScope []*Breakpoint
@ -651,23 +641,42 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi
bpmap := t.Breakpoints()
newBreaklet := &Breaklet{Kind: kind, Cond: cond}
if kind == UserBreakpoint {
newBreaklet.HitCount = map[int]uint64{}
newBreaklet.LogicalID = logicalID
}
setLogicalBreakpoint := func(bp *Breakpoint) {
if kind != UserBreakpoint || bp.Logical != nil {
return
}
if bpmap.Logical == nil {
bpmap.Logical = make(map[int]*LogicalBreakpoint)
}
lbp := bpmap.Logical[logicalID]
if lbp == nil {
lbp = &LogicalBreakpoint{LogicalID: logicalID}
lbp.HitCount = make(map[int]uint64)
lbp.Enabled = true
bpmap.Logical[logicalID] = lbp
}
bp.Logical = lbp
breaklet := bp.UserBreaklet()
if breaklet != nil && breaklet.Cond == nil {
breaklet.Cond = lbp.Cond
}
lbp.File = bp.File
lbp.Line = bp.Line
fn := t.BinInfo().PCToFunc(bp.Addr)
if fn != nil {
lbp.FunctionName = fn.NameWithoutTypeParams()
}
}
if bp, ok := bpmap.M[addr]; ok {
if !bp.canOverlap(kind) {
return bp, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
}
if kind == UserBreakpoint {
bp.Tracepoint = false
bp.TraceReturn = false
bp.Goroutine = false
bp.Stacktrace = 0
bp.Variables = nil
bp.LoadArgs = nil
bp.LoadLocals = nil
}
bp.Breaklets = append(bp.Breaklets, newBreaklet)
setLogicalBreakpoint(bp)
return bp, nil
}
@ -708,6 +717,7 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi
}
newBreakpoint.Breaklets = append(newBreakpoint.Breaklets, newBreaklet)
setLogicalBreakpoint(newBreakpoint)
bpmap.M[addr] = newBreakpoint
@ -741,6 +751,9 @@ func (t *Target) ClearBreakpoint(addr uint64) error {
for i := range bp.Breaklets {
if bp.Breaklets[i].Kind == UserBreakpoint {
bp.Breaklets[i] = nil
if bp.WatchExpr == "" {
bp.Logical = nil
}
}
}
@ -805,6 +818,9 @@ func (t *Target) finishClearBreakpoint(bp *Breakpoint) (bool, error) {
}
delete(t.Breakpoints().M, bp.Addr)
if bp.WatchExpr != "" && bp.Logical != nil {
delete(t.Breakpoints().Logical, bp.Logical.LogicalID)
}
return true, nil
}
@ -925,3 +941,46 @@ func returnInfoError(descr string, err error, mem MemoryReadWriter) []*Variable
v.Name = "return value read error"
return []*Variable{v}
}
// LogicalBreakpoint represents a breakpoint set by a user.
// A logical breakpoint can be associated with zero or many physical
// breakpoints.
// Where a physical breakpoint is associated with a specific instruction
// address a logical breakpoint is associated with a source code location.
// Therefore a logical breakpoint can be associated with zero or many
// physical breakpoints.
// It will have one or more physical breakpoints when source code has been
// inlined (or in the case of type parametric code).
// It will have zero physical breakpoints when it represents a deferred
// breakpoint for code that will be loaded in the future.
type LogicalBreakpoint struct {
LogicalID int
Name string
FunctionName string
File string
Line int
Enabled bool
Tracepoint bool // Tracepoint flag
TraceReturn bool
Goroutine bool // Retrieve goroutine information
Stacktrace int // Number of stack frames to retrieve
Variables []string // Variables to evaluate
LoadArgs *LoadConfig
LoadLocals *LoadConfig
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
// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
// true with the TotalHitCount.
HitCond *struct {
Op token.Token
Val int
}
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr
UserData interface{} // Any additional information about the breakpoint
}

@ -128,7 +128,7 @@ func setFileBreakpoint(p *proc.Target, t *testing.T, fixture protest.Fixture, li
if len(addrs) != 1 {
t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many results %v", f, l, fixture.Source, lineno, addrs)
}
bp, err := p.SetBreakpoint(0, addrs[0], proc.UserBreakpoint, nil)
bp, err := p.SetBreakpoint(int(addrs[0]), addrs[0], proc.UserBreakpoint, nil)
if err != nil {
t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err)
}
@ -164,18 +164,18 @@ func TestReverseBreakpointCounts(t *testing.T) {
}
}
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
}
if len(bp.UserBreaklet().HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
}
for _, v := range bp.UserBreaklet().HitCount {
for _, v := range bp.Logical.HitCount {
if v != 100 {
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
}
}
})

@ -319,8 +319,8 @@ func TestBreakpoint(t *testing.T) {
assertNoError(err, t, "Registers")
pc := regs.PC()
if bp.UserBreaklet().TotalHitCount != 1 {
t.Fatalf("Breakpoint should be hit once, got %d\n", bp.UserBreaklet().TotalHitCount)
if bp.Logical.TotalHitCount != 1 {
t.Fatalf("Breakpoint should be hit once, got %d\n", bp.Logical.TotalHitCount)
}
if pc-1 != bp.Addr && pc != bp.Addr {
@ -1363,7 +1363,7 @@ func TestThreadFrameEvaluation(t *testing.T) {
assertNoError(p.Continue(), t, "Continue()")
bp := p.CurrentThread().Breakpoint()
if bp.Breakpoint == nil || bp.Name != deadlockBp {
if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp {
t.Fatalf("did not stop at deadlock breakpoint %v", bp)
}
@ -1471,18 +1471,18 @@ func TestBreakpointCounts(t *testing.T) {
}
}
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
}
if len(bp.UserBreaklet().HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
}
for _, v := range bp.UserBreaklet().HitCount {
for _, v := range bp.Logical.HitCount {
if v != 100 {
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
}
}
})
@ -1505,8 +1505,8 @@ func TestHardcodedBreakpointCounts(t *testing.T) {
if bp == nil {
continue
}
if bp.Name != proc.HardcodedBreakpoint {
t.Fatalf("wrong breakpoint name %s", bp.Name)
if bp.Logical.Name != proc.HardcodedBreakpoint {
t.Fatalf("wrong breakpoint name %s", bp.Logical.Name)
}
g, err := proc.GetG(th)
assertNoError(err, t, "GetG")
@ -1576,23 +1576,23 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
total += m[i] + 1
}
if uint64(total) != bp.UserBreaklet().TotalHitCount {
t.Fatalf("Mismatched total count %d %d\n", total, bp.UserBreaklet().TotalHitCount)
if uint64(total) != bp.Logical.TotalHitCount {
t.Fatalf("Mismatched total count %d %d\n", total, bp.Logical.TotalHitCount)
}
}
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
}
if len(bp.UserBreaklet().HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
}
for _, v := range bp.UserBreaklet().HitCount {
for _, v := range bp.Logical.HitCount {
if v != 100 {
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
}
}
})
@ -1767,7 +1767,7 @@ func TestCondBreakpointError(t *testing.T) {
func TestHitCondBreakpointEQ(t *testing.T) {
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().HitCond = &struct {
bp.Logical.HitCond = &struct {
Op token.Token
Val int
}{token.EQL, 3}
@ -1791,7 +1791,7 @@ func TestHitCondBreakpointGEQ(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().HitCond = &struct {
bp.Logical.HitCond = &struct {
Op token.Token
Val int
}{token.GEQ, 3}
@ -1814,7 +1814,7 @@ func TestHitCondBreakpointREM(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().HitCond = &struct {
bp.Logical.HitCond = &struct {
Op token.Token
Val int
}{token.REM, 2}
@ -2015,7 +2015,7 @@ func TestPanicBreakpoint(t *testing.T) {
withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()")
bp := p.CurrentThread().Breakpoint()
if bp.Breakpoint == nil || bp.Name != proc.UnrecoveredPanic {
if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic {
t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
}
})
@ -2025,7 +2025,7 @@ func TestCmdLineArgs(t *testing.T) {
expectSuccess := func(p *proc.Target, fixture protest.Fixture) {
err := p.Continue()
bp := p.CurrentThread().Breakpoint()
if bp.Breakpoint != nil && bp.Name == proc.UnrecoveredPanic {
if bp.Breakpoint != nil && bp.Logical.Name == proc.UnrecoveredPanic {
t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp)
}
exit, exited := err.(proc.ErrProcessExited)
@ -2041,7 +2041,7 @@ func TestCmdLineArgs(t *testing.T) {
expectPanic := func(p *proc.Target, fixture protest.Fixture) {
p.Continue()
bp := p.CurrentThread().Breakpoint()
if bp.Breakpoint == nil || bp.Name != proc.UnrecoveredPanic {
if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic {
t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
}
}
@ -2475,7 +2475,7 @@ func TestStepConcurrentDirect(t *testing.T) {
assertNoError(err, t, "ClearBreakpoint()")
for _, b := range p.Breakpoints().M {
if b.Name == proc.UnrecoveredPanic {
if b.Logical.Name == proc.UnrecoveredPanic {
err := p.ClearBreakpoint(b.Addr)
assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
break
@ -2535,7 +2535,7 @@ func TestStepConcurrentPtr(t *testing.T) {
setFileBreakpoint(p, t, fixture.Source, 24)
for _, b := range p.Breakpoints().M {
if b.Name == proc.UnrecoveredPanic {
if b.Logical.Name == proc.UnrecoveredPanic {
err := p.ClearBreakpoint(b.Addr)
assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
break
@ -2645,7 +2645,7 @@ func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
// Next should be interrupted by a tracepoint on the same goroutine.
bp = setFileBreakpoint(p, t, fixture.Source, 14)
bp.Tracepoint = true
bp.Logical.Tracepoint = true
assertNoError(p.Next(), t, "Next()")
assertLineNumber(p, t, 14, "wrong line number")
if !p.Breakpoints().HasSteppingBreakpoints() {
@ -4448,7 +4448,7 @@ func TestDeadlockBreakpoint(t *testing.T) {
assertNoError(p.Continue(), t, "Continue()")
bp := p.CurrentThread().Breakpoint()
if bp.Breakpoint == nil || bp.Name != deadlockBp {
if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp {
t.Fatalf("did not stop at deadlock breakpoint %v", bp)
}
})
@ -5526,18 +5526,18 @@ func TestWatchpointCounts(t *testing.T) {
}
}
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
}
if len(bp.UserBreaklet().HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
}
for _, v := range bp.UserBreaklet().HitCount {
for _, v := range bp.Logical.HitCount {
if v != 100 {
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
}
}
})
@ -5836,7 +5836,7 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) {
if bp != nil {
t.Logf("%#v\n", bp.Breakpoint)
}
if bp == nil || (bp.Name != proc.UnrecoveredPanic) {
if bp == nil || (bp.Logical.Name != proc.UnrecoveredPanic) {
t.Fatalf("no breakpoint hit or wrong breakpoint hit: %#v", bp)
}
})

@ -164,6 +164,7 @@ func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) {
log := logflags.DebuggerLogger()
log.Errorf("could not clear out-of-scope watchpoint: %v", err)
}
delete(t.Breakpoints().Logical, watchpoint.LogicalID())
}
// adjustStackWatchpoint is called when the goroutine of watchpoint resizes

@ -398,8 +398,8 @@ func (t *Target) createUnrecoveredPanicBreakpoint() {
if err == nil {
bp, err := t.SetBreakpoint(unrecoveredPanicID, panicpcs[0], UserBreakpoint, nil)
if err == nil {
bp.Name = UnrecoveredPanic
bp.Variables = []string{"runtime.curg._panic.arg"}
bp.Logical.Name = UnrecoveredPanic
bp.Logical.Variables = []string{"runtime.curg._panic.arg"}
}
}
}
@ -410,14 +410,14 @@ func (t *Target) createFatalThrowBreakpoint() {
if err == nil {
bp, err := t.SetBreakpoint(fatalThrowID, fatalpcs[0], UserBreakpoint, nil)
if err == nil {
bp.Name = FatalThrow
bp.Logical.Name = FatalThrow
}
}
fatalpcs, err = FindFunctionLocation(t.Process, "runtime.fatal", 0)
if err == nil {
bp, err := t.SetBreakpoint(fatalThrowID, fatalpcs[0], UserBreakpoint, nil)
if err == nil {
bp.Name = FatalThrow
bp.Logical.Name = FatalThrow
}
}
}

@ -171,13 +171,13 @@ func (dbp *Target) Continue() error {
return err
}
if onNextGoroutine &&
((!curbp.Tracepoint && !curbp.TraceReturn) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
(!isTraceOrTraceReturn(curbp.Breakpoint) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
err := dbp.ClearSteppingBreakpoints()
if err != nil {
return err
}
}
if curbp.Name == UnrecoveredPanic {
if curbp.LogicalID() == unrecoveredPanicID {
dbp.ClearSteppingBreakpoints()
}
if curbp.LogicalID() != hardcodedBreakpointID {
@ -199,6 +199,13 @@ func (dbp *Target) Continue() error {
}
}
func isTraceOrTraceReturn(bp *Breakpoint) bool {
if bp.Logical == nil {
return false
}
return bp.Logical.Tracepoint || bp.Logical.TraceReturn
}
func conditionErrors(threads []Thread) error {
var condErr error
for _, th := range threads {
@ -1119,7 +1126,8 @@ func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Threa
hcbp.File = loc.File
hcbp.Line = loc.Line
hcbp.Addr = loc.PC
hcbp.Name = HardcodedBreakpoint
hcbp.Logical = &LogicalBreakpoint{}
hcbp.Logical.Name = HardcodedBreakpoint
hcbp.Breaklets = []*Breaklet{&Breaklet{Kind: UserBreakpoint, LogicalID: hardcodedBreakpointID}}
tgt.StopReason = StopHardcodedBreakpoint
}

@ -16,80 +16,69 @@ import (
"github.com/go-delve/delve/pkg/proc"
)
// ConvertBreakpoint converts from a proc.Breakpoint to
// an api.Breakpoint.
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
// ConvertLogicalBreakpoint converts a proc.LogicalBreakpoint into an API breakpoint.
func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint {
b := &Breakpoint{
Name: bp.Name,
ID: bp.LogicalID(),
FunctionName: bp.FunctionName,
File: bp.File,
Line: bp.Line,
Addr: bp.Addr,
Tracepoint: bp.Tracepoint,
TraceReturn: bp.TraceReturn,
Stacktrace: bp.Stacktrace,
Goroutine: bp.Goroutine,
Variables: bp.Variables,
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
WatchExpr: bp.WatchExpr,
WatchType: WatchType(bp.WatchType),
Addrs: []uint64{bp.Addr},
UserData: bp.UserData,
ID: lbp.LogicalID,
FunctionName: lbp.FunctionName,
File: lbp.File,
Line: lbp.Line,
Name: lbp.Name,
Tracepoint: lbp.Tracepoint,
TraceReturn: lbp.TraceReturn,
Stacktrace: lbp.Stacktrace,
Goroutine: lbp.Goroutine,
Variables: lbp.Variables,
LoadArgs: LoadConfigFromProc(lbp.LoadArgs),
LoadLocals: LoadConfigFromProc(lbp.LoadLocals),
TotalHitCount: lbp.TotalHitCount,
Disabled: !lbp.Enabled,
UserData: lbp.UserData,
}
breaklet := bp.UserBreaklet()
if breaklet != nil {
b.TotalHitCount = breaklet.TotalHitCount
b.HitCount = map[string]uint64{}
for idx := range breaklet.HitCount {
b.HitCount[strconv.Itoa(idx)] = breaklet.HitCount[idx]
}
var buf bytes.Buffer
printer.Fprint(&buf, token.NewFileSet(), breaklet.Cond)
b.Cond = buf.String()
if breaklet.HitCond != nil {
b.HitCond = fmt.Sprintf("%s %d", breaklet.HitCond.Op.String(), breaklet.HitCond.Val)
}
b.HitCount = map[string]uint64{}
for idx := range lbp.HitCount {
b.HitCount[strconv.Itoa(idx)] = lbp.HitCount[idx]
}
if lbp.HitCond != nil {
b.HitCond = fmt.Sprintf("%s %d", lbp.HitCond.Op.String(), lbp.HitCond.Val)
}
var buf bytes.Buffer
printer.Fprint(&buf, token.NewFileSet(), lbp.Cond)
b.Cond = buf.String()
return b
}
// ConvertBreakpoints converts a slice of physical breakpoints into a slice
// of logical breakpoints.
// The input must be sorted by increasing LogicalID
func ConvertBreakpoints(bps []*proc.Breakpoint) []*Breakpoint {
if len(bps) <= 0 {
return nil
// ConvertPhysicalBreakpoints adds informations from physical breakpoints to an API breakpoint.
func ConvertPhysicalBreakpoints(b *Breakpoint, bps []*proc.Breakpoint) {
if len(bps) == 0 {
return
}
r := make([]*Breakpoint, 0, len(bps))
b.WatchExpr = bps[0].WatchExpr
b.WatchType = WatchType(bps[0].WatchType)
lg := false
for _, bp := range bps {
if len(r) > 0 {
if r[len(r)-1].ID == bp.LogicalID() {
r[len(r)-1].Addrs = append(r[len(r)-1].Addrs, bp.Addr)
if r[len(r)-1].FunctionName != bp.FunctionName && r[len(r)-1].FunctionName != "" {
if !lg {
r[len(r)-1].FunctionName = removeTypeParams(r[len(r)-1].FunctionName)
lg = true
}
fn := removeTypeParams(bp.FunctionName)
if r[len(r)-1].FunctionName != fn {
r[len(r)-1].FunctionName = "(multiple functions)"
}
}
continue
} else if r[len(r)-1].ID > bp.LogicalID() {
panic("input not sorted")
b.Addrs = append(b.Addrs, bp.Addr)
if b.FunctionName != bp.FunctionName && b.FunctionName != "" {
if !lg {
b.FunctionName = removeTypeParams(b.FunctionName)
lg = true
}
fn := removeTypeParams(bp.FunctionName)
if b.FunctionName != fn {
b.FunctionName = "(multiple functions)"
}
}
r = append(r, ConvertBreakpoint(bp))
lg = false
}
return r
if len(b.Addrs) > 0 {
b.Addr = b.Addrs[0]
}
}
func removeTypeParams(name string) string {
@ -99,7 +88,7 @@ func removeTypeParams(name string) string {
// ConvertThread converts a proc.Thread into an
// api thread.
func ConvertThread(th proc.Thread) *Thread {
func ConvertThread(th proc.Thread, bp *Breakpoint) *Thread {
var (
function *Function
file string
@ -116,12 +105,6 @@ func ConvertThread(th proc.Thread) *Thread {
function = ConvertFunction(loc.Fn)
}
var bp *Breakpoint
if b := th.Breakpoint(); b.Active {
bp = ConvertBreakpoint(b.Breakpoint)
}
if g, _ := proc.GetG(th); g != nil {
gid = g.ID
}
@ -138,10 +121,10 @@ func ConvertThread(th proc.Thread) *Thread {
}
// ConvertThreads converts a slice of proc.Thread into a slice of api.Thread.
func ConvertThreads(threads []proc.Thread) []*Thread {
func ConvertThreads(threads []proc.Thread, convertBreakpoint func(proc.Thread) *Breakpoint) []*Thread {
r := make([]*Thread, len(threads))
for i := range threads {
r[i] = ConvertThread(threads[i])
r[i] = ConvertThread(threads[i], convertBreakpoint(threads[i]))
}
return r
}

@ -1857,10 +1857,12 @@ func (s *Session) stoppedOnBreakpointGoroutineID(state *api.DebuggerState) (int,
return goid, nil
}
bp := g.Thread.Breakpoint()
if bp == nil || bp.Breakpoint == nil {
if bp == nil || bp.Breakpoint == nil || bp.Breakpoint.Logical == nil {
return goid, nil
}
return goid, api.ConvertBreakpoint(bp.Breakpoint)
abp := api.ConvertLogicalBreakpoint(bp.Breakpoint.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp.Breakpoint})
return goid, abp
}
// stepUntilStopAndNotify is a wrapper around runUntilStopAndNotify that
@ -3188,8 +3190,8 @@ func (s *Session) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
}
// Check if this goroutine ID is stopped at a breakpoint.
includeStackTrace := true
if bpState != nil && bpState.Breakpoint != nil && (bpState.Breakpoint.Name == proc.FatalThrow || bpState.Breakpoint.Name == proc.UnrecoveredPanic) {
switch bpState.Breakpoint.Name {
if bpState != nil && bpState.Breakpoint != nil && bpState.Breakpoint.Logical != nil && (bpState.Breakpoint.Logical.Name == proc.FatalThrow || bpState.Breakpoint.Logical.Name == proc.UnrecoveredPanic) {
switch bpState.Breakpoint.Logical.Name {
case proc.FatalThrow:
body.ExceptionId = "fatal error"
body.Description, err = s.throwReason(goroutineID)

@ -72,10 +72,6 @@ type Debugger struct {
recordMutex sync.Mutex
dumpState proc.DumpState
// Debugger keeps a map of disabled breakpoints
// so lower layers like proc doesn't need to deal
// with them
disabledBreakpoints map[int]*api.Breakpoint
breakpointIDCounter int
}
@ -209,8 +205,6 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
}
}
d.disabledBreakpoints = make(map[int]*api.Breakpoint)
return d, nil
}
@ -520,46 +514,24 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
}
discarded := []api.DiscardedBreakpoint{}
breakpoints := api.ConvertBreakpoints(d.breakpoints())
p.Breakpoints().Logical = d.target.Breakpoints().Logical
d.target = p
maxID := 0
for _, oldBp := range breakpoints {
if oldBp.ID < 0 {
for _, oldBp := range d.target.Breakpoints().Logical {
if oldBp.LogicalID < 0 || !oldBp.Enabled {
continue
}
if oldBp.ID > maxID {
maxID = oldBp.ID
}
if oldBp.WatchExpr != "" {
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate watchpoints on restart"})
} else if len(oldBp.File) > 0 {
addrs, err := proc.FindFileLocation(p, oldBp.File, oldBp.Line)
if len(oldBp.File) > 0 {
oldBp.TotalHitCount = 0
oldBp.HitCount = make(map[int]uint64)
err := d.createPhysicalBreakpoints(oldBp)
if err != nil {
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: err.Error()})
continue
}
createLogicalBreakpoint(d, addrs, oldBp, oldBp.ID)
} else {
// Avoid setting a breakpoint based on address when rebuilding
if rebuild {
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate address breakpoints on restart"})
continue
}
newBp, err := p.SetBreakpoint(oldBp.ID, oldBp.Addr, proc.UserBreakpoint, nil)
if err != nil {
return nil, err
}
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
return nil, err
}
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: "can not recreate watchpoint on restart"})
}
}
for _, bp := range d.disabledBreakpoints {
if bp.ID > maxID {
maxID = bp.ID
}
}
d.breakpointIDCounter = maxID
return discarded, nil
}
@ -609,7 +581,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
}
for _, thread := range d.target.ThreadList() {
th := api.ConvertThread(thread)
th := api.ConvertThread(thread, d.ConvertThreadBreakpoint(thread))
th.CallReturn = thread.Common().CallReturn
if retLoadCfg != nil {
@ -630,7 +602,9 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
state.WatchOutOfScope = make([]*api.Breakpoint, 0, len(d.target.Breakpoints().WatchOutOfScope))
for _, bp := range d.target.Breakpoints().WatchOutOfScope {
state.WatchOutOfScope = append(state.WatchOutOfScope, api.ConvertBreakpoint(bp))
abp := api.ConvertLogicalBreakpoint(bp.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
}
return state, nil
@ -673,7 +647,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
)
if requestedBp.Name != "" {
if (d.findBreakpointByName(requestedBp.Name) != nil) || (d.findDisabledBreakpointByName(requestedBp.Name) != nil) {
if d.findBreakpointByName(requestedBp.Name) != nil {
return nil, errors.New("breakpoint name already exists")
}
}
@ -714,32 +688,51 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
return createdBp, nil
}
func (d *Debugger) convertBreakpoint(lbp *proc.LogicalBreakpoint) *api.Breakpoint {
abp := api.ConvertLogicalBreakpoint(lbp)
api.ConvertPhysicalBreakpoints(abp, d.findBreakpoint(lbp.LogicalID))
return abp
}
func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint {
if b := thread.Breakpoint(); b.Active && b.Breakpoint.Logical != nil {
return d.convertBreakpoint(b.Breakpoint.Logical)
}
return nil
}
// createLogicalBreakpoint creates one physical breakpoint for each address
// in addrs and associates all of them with the same logical breakpoint.
func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
p := d.target
if dbp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
return dbp, proc.BreakpointExistsError{File: dbp.File, Line: dbp.Line, Addr: dbp.Addr}
if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
abp := d.convertBreakpoint(lbp)
return abp, proc.BreakpointExistsError{File: lbp.File, Line: lbp.Line}
}
var lbp *proc.LogicalBreakpoint
if id <= 0 {
d.breakpointIDCounter++
id = d.breakpointIDCounter
lbp = &proc.LogicalBreakpoint{LogicalID: id, HitCount: make(map[int]uint64), Enabled: true}
p.Breakpoints().Logical[id] = lbp
}
err := copyLogicalBreakpointInfo(lbp, requestedBp)
if err != nil {
return nil, err
}
bps := make([]*proc.Breakpoint, len(addrs))
var err error
for i := range addrs {
if id <= 0 {
d.breakpointIDCounter++
id = d.breakpointIDCounter
}
bps[i], err = p.SetBreakpoint(id, addrs[i], proc.UserBreakpoint, nil)
if err != nil {
break
}
err = copyBreakpointInfo(bps[i], requestedBp)
if err != nil {
break
}
}
if err != nil {
delete(p.Breakpoints().Logical, id)
if isBreakpointExistsErr(err) {
return nil, err
}
@ -755,8 +748,65 @@ func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Break
return nil, err
}
createdBp := api.ConvertBreakpoints(bps)
return createdBp[0], nil // we created a single logical breakpoint, the slice here will always have len == 1
return d.convertBreakpoint(bps[0].Logical), nil
}
func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error {
addrs, err := proc.FindFileLocation(d.target, lbp.File, lbp.Line)
if err != nil {
return err
}
bps := make([]*proc.Breakpoint, len(addrs))
for i := range addrs {
bps[i], err = d.target.SetBreakpoint(lbp.LogicalID, addrs[i], proc.UserBreakpoint, nil)
if err != nil {
break
}
}
if err != nil {
if isBreakpointExistsErr(err) {
return err
}
for _, bp := range bps {
if bp == nil {
continue
}
if err1 := d.target.ClearBreakpoint(bp.Addr); err1 != nil {
return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
}
}
return err
}
return nil
}
func (d *Debugger) clearPhysicalBreakpoints(id int) error {
var errs []error
n := 0
for _, bp := range d.target.Breakpoints().M {
if bp.LogicalID() == id {
n++
err := d.target.ClearBreakpoint(bp.Addr)
if err != nil {
errs = append(errs, err)
}
}
}
if len(errs) > 0 {
buf := new(bytes.Buffer)
for i, err := range errs {
fmt.Fprintf(buf, "%s", err)
if i != len(errs)-1 {
fmt.Fprintf(buf, ", ")
}
}
if len(errs) == n {
return fmt.Errorf("unable to clear breakpoint %d: %v", id, buf.String())
}
return fmt.Errorf("unable to clear breakpoint %d (partial): %s", id, buf.String())
}
return nil
}
func isBreakpointExistsErr(err error) bool {
@ -775,47 +825,32 @@ func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
// It also enables or disables the breakpoint.
// We can consume this function to avoid locking a goroutine.
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error {
originals := d.findBreakpoint(amend.ID)
if len(originals) > 0 && originals[0].WatchExpr != "" && amend.Disabled {
return errors.New("can not disable watchpoints")
}
_, disabled := d.disabledBreakpoints[amend.ID]
if originals == nil && !disabled {
original := d.target.Breakpoints().Logical[amend.ID]
if original == nil {
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
}
if !amend.Disabled && disabled { // enable the breakpoint
bp, err := d.target.SetBreakpoint(amend.ID, amend.Addr, proc.UserBreakpoint, nil)
if amend.Disabled && original.Enabled {
original.Enabled = false
err := copyLogicalBreakpointInfo(original, amend)
if err != nil {
return err
}
copyBreakpointInfo(bp, amend)
if breaklet := bp.UserBreaklet(); breaklet != nil {
breaklet.TotalHitCount = amend.TotalHitCount
breaklet.HitCount = map[int]uint64{}
for idx := range amend.HitCount {
i, err := strconv.Atoi(idx)
if err != nil {
return fmt.Errorf("can't convert goroutine ID: %w", err)
}
breaklet.HitCount[i] = amend.HitCount[idx]
}
}
delete(d.disabledBreakpoints, amend.ID)
}
if amend.Disabled && !disabled { // disable the breakpoint
if _, err := d.clearBreakpoint(amend); err != nil {
return err
}
d.disabledBreakpoints[amend.ID] = amend
}
for _, original := range originals {
if err := copyBreakpointInfo(original, amend); err != nil {
return err
}
return d.clearPhysicalBreakpoints(amend.ID)
}
if !amend.Disabled && !original.Enabled {
original.Enabled = true
copyLogicalBreakpointInfo(original, amend)
return d.createPhysicalBreakpoints(original)
}
err := copyLogicalBreakpointInfo(original, amend)
if err != nil {
return err
}
for _, bp := range d.findBreakpoint(amend.ID) {
bp.UserBreaklet().Cond = original.Cond
}
return nil
}
@ -836,37 +871,38 @@ func (d *Debugger) CancelNext() error {
return d.target.ClearSteppingBreakpoints()
}
func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err error) {
bp.Name = requested.Name
bp.Tracepoint = requested.Tracepoint
bp.TraceReturn = requested.TraceReturn
bp.Goroutine = requested.Goroutine
bp.Stacktrace = requested.Stacktrace
bp.Variables = requested.Variables
bp.UserData = requested.UserData
bp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs)
bp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals)
breaklet := bp.UserBreaklet()
if breaklet != nil {
breaklet.Cond = nil
if requested.Cond != "" {
breaklet.Cond, err = parser.ParseExpr(requested.Cond)
}
breaklet.HitCond = nil
if requested.HitCond != "" {
opTok, val, parseErr := parseHitCondition(requested.HitCond)
if err == nil {
err = parseErr
}
if parseErr == nil {
breaklet.HitCond = &struct {
Op token.Token
Val int
}{opTok, val}
}
func copyLogicalBreakpointInfo(lbp *proc.LogicalBreakpoint, requested *api.Breakpoint) error {
lbp.Name = requested.Name
lbp.Tracepoint = requested.Tracepoint
lbp.TraceReturn = requested.TraceReturn
lbp.Goroutine = requested.Goroutine
lbp.Stacktrace = requested.Stacktrace
lbp.Variables = requested.Variables
lbp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs)
lbp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals)
lbp.UserData = requested.UserData
lbp.Cond = nil
if requested.Cond != "" {
var err error
lbp.Cond, err = parser.ParseExpr(requested.Cond)
if err != nil {
return err
}
}
return err
lbp.HitCond = nil
if requested.HitCond != "" {
opTok, val, err := parseHitCondition(requested.HitCond)
if err != nil {
return err
}
lbp.HitCond = &struct {
Op token.Token
Val int
}{opTok, val}
}
return nil
}
func parseHitCondition(hitCond string) (token.Token, int, error) {
@ -919,57 +955,19 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
// clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
if bp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
delete(d.disabledBreakpoints, bp.ID)
return bp, nil
if requestedBp.ID <= 0 {
bp := d.target.Breakpoints().M[requestedBp.Addr]
requestedBp.ID = bp.LogicalID()
}
var clearBps []*proc.Breakpoint
lbp := d.target.Breakpoints().Logical[requestedBp.ID]
clearedBp := d.convertBreakpoint(lbp)
toclear := func(addr uint64) {
bp := d.target.Breakpoints().M[addr]
if bp != nil {
clearBps = append(clearBps, bp)
}
}
delete(d.target.Breakpoints().Logical, requestedBp.ID)
clearAddr := true
for _, addr := range requestedBp.Addrs {
if addr == requestedBp.Addr {
clearAddr = false
}
toclear(addr)
}
if clearAddr {
toclear(requestedBp.Addr)
}
// Breakpoints need to be converted before clearing them or they won't have
// an ID anymore.
sort.Sort(breakpointsByLogicalID(clearBps))
clearedBp := api.ConvertBreakpoints(clearBps)[0]
var errs []error
for _, bp := range clearBps {
err := d.target.ClearBreakpoint(bp.Addr)
if err != nil {
errs = append(errs, fmt.Errorf("address %#x: %v", bp.Addr, err))
}
}
if len(errs) > 0 {
buf := new(bytes.Buffer)
for i, err := range errs {
fmt.Fprintf(buf, "%s", err)
if i != len(errs)-1 {
fmt.Fprintf(buf, ", ")
}
}
if len(errs) == len(clearBps) {
return nil, fmt.Errorf("unable to clear breakpoint %d: %v", requestedBp.ID, buf.String())
}
return nil, fmt.Errorf("unable to clear breakpoint %d (partial): %s", requestedBp.ID, buf.String())
err := d.clearPhysicalBreakpoints(requestedBp.ID)
if err != nil {
return nil, err
}
d.log.Infof("cleared breakpoint: %#v", clearedBp)
@ -1008,46 +1006,36 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
var bps []*api.Breakpoint
if !all {
bps = api.ConvertBreakpoints(d.breakpoints())
} else {
abps := []*api.Breakpoint{}
if all {
for _, bp := range d.target.Breakpoints().M {
abp := api.ConvertBreakpoint(bp)
var abp *api.Breakpoint
if bp.Logical != nil {
abp = api.ConvertLogicalBreakpoint(bp.Logical)
} else {
abp = &api.Breakpoint{}
}
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
abp.VerboseDescr = bp.VerboseDescr()
bps = append(bps, abp)
abps = append(abps, abp)
}
} else {
for _, lbp := range d.target.Breakpoints().Logical {
abps = append(abps, d.convertBreakpoint(lbp))
}
}
for _, bp := range d.disabledBreakpoints {
bps = append(bps, bp)
}
return bps
}
func (d *Debugger) breakpoints() []*proc.Breakpoint {
bps := []*proc.Breakpoint{}
for _, bp := range d.target.Breakpoints().M {
if bp.IsUser() {
bps = append(bps, bp)
}
}
sort.Sort(breakpointsByLogicalID(bps))
return bps
return abps
}
// FindBreakpoint returns the breakpoint specified by 'id'.
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
bps := api.ConvertBreakpoints(d.findBreakpoint(id))
bps = append(bps, d.findDisabledBreakpoint(id)...)
if len(bps) <= 0 {
lbp := d.target.Breakpoints().Logical[id]
if lbp == nil {
return nil
}
return bps[0]
return d.convertBreakpoint(lbp)
}
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
@ -1060,47 +1048,17 @@ func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
return bps
}
func (d *Debugger) findDisabledBreakpoint(id int) []*api.Breakpoint {
var bps []*api.Breakpoint
for _, dbp := range d.disabledBreakpoints {
if dbp.ID == id {
bps = append(bps, dbp)
}
}
return bps
}
// FindBreakpointByName returns the breakpoint specified by 'name'
func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
bp := d.findBreakpointByName(name)
if bp == nil {
bp = d.findDisabledBreakpointByName(name)
}
return bp
return d.findBreakpointByName(name)
}
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
var bps []*proc.Breakpoint
for _, bp := range d.breakpoints() {
if bp.Name == name {
bps = append(bps, bp)
}
}
if len(bps) == 0 {
return nil
}
sort.Sort(breakpointsByLogicalID(bps))
r := api.ConvertBreakpoints(bps)
return r[0] // there can only be one logical breakpoint with the same name
}
func (d *Debugger) findDisabledBreakpointByName(name string) *api.Breakpoint {
for _, dbp := range d.disabledBreakpoints {
if dbp.Name == name {
return dbp
for _, lbp := range d.target.Breakpoints().Logical {
if lbp.Name == name {
return d.convertBreakpoint(lbp)
}
}
return nil
@ -1118,9 +1076,9 @@ func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string,
return nil, err
}
if d.findBreakpointByName(expr) == nil {
bp.Name = expr
bp.Logical.Name = expr
}
return api.ConvertBreakpoint(bp), nil
return d.convertBreakpoint(bp.Logical), nil
}
// Threads returns the threads of the target process.
@ -2254,18 +2212,3 @@ func noDebugErrorWarning(err error) error {
}
return err
}
type breakpointsByLogicalID []*proc.Breakpoint
func (v breakpointsByLogicalID) Len() int { return len(v) }
func (v breakpointsByLogicalID) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
func (v breakpointsByLogicalID) Less(i, j int) bool {
if v[i].LogicalID() == v[j].LogicalID() {
if v[i].WatchType != v[j].WatchType {
return v[i].WatchType > v[j].WatchType // if a logical breakpoint contains a watchpoint let the watchpoint sort first
}
return v[i].Addr < v[j].Addr
}
return v[i].LogicalID() < v[j].LogicalID()
}

@ -155,7 +155,7 @@ func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) (err er
}
s.debugger.LockTarget()
defer s.debugger.UnlockTarget()
*threads = api.ConvertThreads(pthreads)
*threads = api.ConvertThreads(pthreads, s.debugger.ConvertThreadBreakpoint)
return nil
}
@ -169,7 +169,7 @@ func (s *RPCServer) GetThread(id int, thread *api.Thread) error {
}
s.debugger.LockTarget()
defer s.debugger.UnlockTarget()
*thread = *api.ConvertThread(t)
*thread = *api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
return nil
}

@ -387,7 +387,7 @@ func (s *RPCServer) ListThreads(arg ListThreadsIn, out *ListThreadsOut) (err err
}
s.debugger.LockTarget()
defer s.debugger.UnlockTarget()
out.Threads = api.ConvertThreads(threads)
out.Threads = api.ConvertThreads(threads, s.debugger.ConvertThreadBreakpoint)
return nil
}
@ -410,7 +410,7 @@ func (s *RPCServer) GetThread(arg GetThreadIn, out *GetThreadOut) error {
}
s.debugger.LockTarget()
defer s.debugger.UnlockTarget()
out.Thread = api.ConvertThread(t)
out.Thread = api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
return nil
}