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. Addr uint64 // Address breakpoint is set for.
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction. OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
Name string // User defined name of the breakpoint
WatchExpr string WatchExpr string
WatchType WatchType WatchType WatchType
@ -59,14 +58,7 @@ type Breakpoint struct {
Breaklets []*Breaklet Breaklets []*Breaklet
// Breakpoint information // Breakpoint information
Tracepoint bool // Tracepoint flag Logical *LogicalBreakpoint
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
// ReturnInfo describes how to collect return variables when this // ReturnInfo describes how to collect return variables when this
// breakpoint is hit as a return breakpoint. // 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: if not nil the breakpoint will be triggered only if evaluating Cond returns true
Cond ast.Expr 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 // DeferReturns: when kind == NextDeferBreakpoint this breakpoint
// will also check if the caller is runtime.gopanic or if the return // will also check if the caller is runtime.gopanic or if the return
// address is in the DeferReturns array. // address is in the DeferReturns array.
@ -99,13 +88,6 @@ type Breaklet struct {
// the function, not when the function is called directly // the function, not when the function is called directly
DeferReturns []uint64 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 // checkPanicCall checks that the breakpoint happened while the function was
// called by a panic. It is only checked for WatchOutOfScopeBreakpoint Kind. // called by a panic. It is only checked for WatchOutOfScopeBreakpoint Kind.
checkPanicCall bool checkPanicCall bool
@ -206,10 +188,12 @@ func (bp *Breakpoint) VerboseDescr() []string {
r = append(r, fmt.Sprintf("HWBreakIndex=%#x watchStackOff=%#x", bp.HWBreakIndex, bp.watchStackOff)) r = append(r, fmt.Sprintf("HWBreakIndex=%#x watchStackOff=%#x", bp.HWBreakIndex, bp.watchStackOff))
} }
lbp := bp.Logical
for _, breaklet := range bp.Breaklets { for _, breaklet := range bp.Breaklets {
switch breaklet.Kind { switch breaklet.Kind {
case UserBreakpoint: 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: case NextBreakpoint:
r = append(r, fmt.Sprintf("Next Cond=%q", exprToString(breaklet.Cond))) r = append(r, fmt.Sprintf("Next Cond=%q", exprToString(breaklet.Cond)))
case NextDeferBreakpoint: case NextDeferBreakpoint:
@ -280,11 +264,14 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
switch breaklet.Kind { switch breaklet.Kind {
case UserBreakpoint: case UserBreakpoint:
if g, err := GetG(thread); err == nil { lbp := bpstate.Breakpoint.Logical
breaklet.HitCount[g.ID]++ if lbp != nil {
if g, err := GetG(thread); err == nil {
lbp.HitCount[g.ID]++
}
lbp.TotalHitCount++
} }
breaklet.TotalHitCount++ active = checkHitCond(lbp)
active = checkHitCond(breaklet)
case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint: case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint:
nextDeferOk := true nextDeferOk := true
@ -331,26 +318,26 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
} }
// checkHitCond evaluates bp's hit condition on thread. // checkHitCond evaluates bp's hit condition on thread.
func checkHitCond(breaklet *Breaklet) bool { func checkHitCond(lbp *LogicalBreakpoint) bool {
if breaklet.HitCond == nil { if lbp == nil || lbp.HitCond == nil {
return true return true
} }
// Evaluate the breakpoint condition. // Evaluate the breakpoint condition.
switch breaklet.HitCond.Op { switch lbp.HitCond.Op {
case token.EQL: case token.EQL:
return int(breaklet.TotalHitCount) == breaklet.HitCond.Val return int(lbp.TotalHitCount) == lbp.HitCond.Val
case token.NEQ: case token.NEQ:
return int(breaklet.TotalHitCount) != breaklet.HitCond.Val return int(lbp.TotalHitCount) != lbp.HitCond.Val
case token.GTR: case token.GTR:
return int(breaklet.TotalHitCount) > breaklet.HitCond.Val return int(lbp.TotalHitCount) > lbp.HitCond.Val
case token.LSS: case token.LSS:
return int(breaklet.TotalHitCount) < breaklet.HitCond.Val return int(lbp.TotalHitCount) < lbp.HitCond.Val
case token.GEQ: case token.GEQ:
return int(breaklet.TotalHitCount) >= breaklet.HitCond.Val return int(lbp.TotalHitCount) >= lbp.HitCond.Val
case token.LEQ: case token.LEQ:
return int(breaklet.TotalHitCount) <= breaklet.HitCond.Val return int(lbp.TotalHitCount) <= lbp.HitCond.Val
case token.REM: case token.REM:
return int(breaklet.TotalHitCount)%breaklet.HitCond.Val == 0 return int(lbp.TotalHitCount)%lbp.HitCond.Val == 0
} }
return false return false
} }
@ -468,6 +455,9 @@ func (nbp NoBreakpointError) Error() string {
type BreakpointMap struct { type BreakpointMap struct {
M map[uint64]*Breakpoint 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 // WatchOutOfScope is the list of watchpoints that went out of scope during
// the last resume operation // the last resume operation
WatchOutOfScope []*Breakpoint WatchOutOfScope []*Breakpoint
@ -651,23 +641,42 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi
bpmap := t.Breakpoints() bpmap := t.Breakpoints()
newBreaklet := &Breaklet{Kind: kind, Cond: cond} newBreaklet := &Breaklet{Kind: kind, Cond: cond}
if kind == UserBreakpoint { if kind == UserBreakpoint {
newBreaklet.HitCount = map[int]uint64{}
newBreaklet.LogicalID = logicalID 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, ok := bpmap.M[addr]; ok {
if !bp.canOverlap(kind) { if !bp.canOverlap(kind) {
return bp, BreakpointExistsError{bp.File, bp.Line, bp.Addr} 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) bp.Breaklets = append(bp.Breaklets, newBreaklet)
setLogicalBreakpoint(bp)
return bp, nil return bp, nil
} }
@ -708,6 +717,7 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi
} }
newBreakpoint.Breaklets = append(newBreakpoint.Breaklets, newBreaklet) newBreakpoint.Breaklets = append(newBreakpoint.Breaklets, newBreaklet)
setLogicalBreakpoint(newBreakpoint)
bpmap.M[addr] = newBreakpoint bpmap.M[addr] = newBreakpoint
@ -741,6 +751,9 @@ func (t *Target) ClearBreakpoint(addr uint64) error {
for i := range bp.Breaklets { for i := range bp.Breaklets {
if bp.Breaklets[i].Kind == UserBreakpoint { if bp.Breaklets[i].Kind == UserBreakpoint {
bp.Breaklets[i] = nil 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) delete(t.Breakpoints().M, bp.Addr)
if bp.WatchExpr != "" && bp.Logical != nil {
delete(t.Breakpoints().Logical, bp.Logical.LogicalID)
}
return true, nil return true, nil
} }
@ -925,3 +941,46 @@ func returnInfoError(descr string, err error, mem MemoryReadWriter) []*Variable
v.Name = "return value read error" v.Name = "return value read error"
return []*Variable{v} 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 { if len(addrs) != 1 {
t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many results %v", f, l, fixture.Source, lineno, addrs) 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 { if err != nil {
t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err) 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) t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 { if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount) t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
} }
if len(bp.UserBreaklet().HitCount) != 2 { if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount)) 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 { 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") assertNoError(err, t, "Registers")
pc := regs.PC() pc := regs.PC()
if bp.UserBreaklet().TotalHitCount != 1 { if bp.Logical.TotalHitCount != 1 {
t.Fatalf("Breakpoint should be hit once, got %d\n", bp.UserBreaklet().TotalHitCount) t.Fatalf("Breakpoint should be hit once, got %d\n", bp.Logical.TotalHitCount)
} }
if pc-1 != bp.Addr && pc != bp.Addr { if pc-1 != bp.Addr && pc != bp.Addr {
@ -1363,7 +1363,7 @@ func TestThreadFrameEvaluation(t *testing.T) {
assertNoError(p.Continue(), t, "Continue()") assertNoError(p.Continue(), t, "Continue()")
bp := p.CurrentThread().Breakpoint() 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) 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) t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 { if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount) t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
} }
if len(bp.UserBreaklet().HitCount) != 2 { if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount)) 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 { 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 { if bp == nil {
continue continue
} }
if bp.Name != proc.HardcodedBreakpoint { if bp.Logical.Name != proc.HardcodedBreakpoint {
t.Fatalf("wrong breakpoint name %s", bp.Name) t.Fatalf("wrong breakpoint name %s", bp.Logical.Name)
} }
g, err := proc.GetG(th) g, err := proc.GetG(th)
assertNoError(err, t, "GetG") assertNoError(err, t, "GetG")
@ -1576,23 +1576,23 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
total += m[i] + 1 total += m[i] + 1
} }
if uint64(total) != bp.UserBreaklet().TotalHitCount { if uint64(total) != bp.Logical.TotalHitCount {
t.Fatalf("Mismatched total count %d %d\n", total, bp.UserBreaklet().TotalHitCount) t.Fatalf("Mismatched total count %d %d\n", total, bp.Logical.TotalHitCount)
} }
} }
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount) t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 { if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount) t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
} }
if len(bp.UserBreaklet().HitCount) != 2 { if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount)) 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 { 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) { func TestHitCondBreakpointEQ(t *testing.T) {
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7) bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().HitCond = &struct { bp.Logical.HitCond = &struct {
Op token.Token Op token.Token
Val int Val int
}{token.EQL, 3} }{token.EQL, 3}
@ -1791,7 +1791,7 @@ func TestHitCondBreakpointGEQ(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7) bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().HitCond = &struct { bp.Logical.HitCond = &struct {
Op token.Token Op token.Token
Val int Val int
}{token.GEQ, 3} }{token.GEQ, 3}
@ -1814,7 +1814,7 @@ func TestHitCondBreakpointREM(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7) bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().HitCond = &struct { bp.Logical.HitCond = &struct {
Op token.Token Op token.Token
Val int Val int
}{token.REM, 2} }{token.REM, 2}
@ -2015,7 +2015,7 @@ func TestPanicBreakpoint(t *testing.T) {
withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()") assertNoError(p.Continue(), t, "Continue()")
bp := p.CurrentThread().Breakpoint() 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) 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) { expectSuccess := func(p *proc.Target, fixture protest.Fixture) {
err := p.Continue() err := p.Continue()
bp := p.CurrentThread().Breakpoint() 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) t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp)
} }
exit, exited := err.(proc.ErrProcessExited) exit, exited := err.(proc.ErrProcessExited)
@ -2041,7 +2041,7 @@ func TestCmdLineArgs(t *testing.T) {
expectPanic := func(p *proc.Target, fixture protest.Fixture) { expectPanic := func(p *proc.Target, fixture protest.Fixture) {
p.Continue() p.Continue()
bp := p.CurrentThread().Breakpoint() 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) t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
} }
} }
@ -2475,7 +2475,7 @@ func TestStepConcurrentDirect(t *testing.T) {
assertNoError(err, t, "ClearBreakpoint()") assertNoError(err, t, "ClearBreakpoint()")
for _, b := range p.Breakpoints().M { for _, b := range p.Breakpoints().M {
if b.Name == proc.UnrecoveredPanic { if b.Logical.Name == proc.UnrecoveredPanic {
err := p.ClearBreakpoint(b.Addr) err := p.ClearBreakpoint(b.Addr)
assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)") assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
break break
@ -2535,7 +2535,7 @@ func TestStepConcurrentPtr(t *testing.T) {
setFileBreakpoint(p, t, fixture.Source, 24) setFileBreakpoint(p, t, fixture.Source, 24)
for _, b := range p.Breakpoints().M { for _, b := range p.Breakpoints().M {
if b.Name == proc.UnrecoveredPanic { if b.Logical.Name == proc.UnrecoveredPanic {
err := p.ClearBreakpoint(b.Addr) err := p.ClearBreakpoint(b.Addr)
assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)") assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
break break
@ -2645,7 +2645,7 @@ func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
// Next should be interrupted by a tracepoint on the same goroutine. // Next should be interrupted by a tracepoint on the same goroutine.
bp = setFileBreakpoint(p, t, fixture.Source, 14) bp = setFileBreakpoint(p, t, fixture.Source, 14)
bp.Tracepoint = true bp.Logical.Tracepoint = true
assertNoError(p.Next(), t, "Next()") assertNoError(p.Next(), t, "Next()")
assertLineNumber(p, t, 14, "wrong line number") assertLineNumber(p, t, 14, "wrong line number")
if !p.Breakpoints().HasSteppingBreakpoints() { if !p.Breakpoints().HasSteppingBreakpoints() {
@ -4448,7 +4448,7 @@ func TestDeadlockBreakpoint(t *testing.T) {
assertNoError(p.Continue(), t, "Continue()") assertNoError(p.Continue(), t, "Continue()")
bp := p.CurrentThread().Breakpoint() 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) 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) t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
if bp.UserBreaklet().TotalHitCount != 200 { if bp.Logical.TotalHitCount != 200 {
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount) t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
} }
if len(bp.UserBreaklet().HitCount) != 2 { if len(bp.Logical.HitCount) != 2 {
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount)) 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 { 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 { if bp != nil {
t.Logf("%#v\n", bp.Breakpoint) 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) 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 := logflags.DebuggerLogger()
log.Errorf("could not clear out-of-scope watchpoint: %v", err) 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 // adjustStackWatchpoint is called when the goroutine of watchpoint resizes

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

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

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

@ -1857,10 +1857,12 @@ func (s *Session) stoppedOnBreakpointGoroutineID(state *api.DebuggerState) (int,
return goid, nil return goid, nil
} }
bp := g.Thread.Breakpoint() 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, 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 // 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. // Check if this goroutine ID is stopped at a breakpoint.
includeStackTrace := true includeStackTrace := true
if bpState != nil && bpState.Breakpoint != nil && (bpState.Breakpoint.Name == proc.FatalThrow || bpState.Breakpoint.Name == proc.UnrecoveredPanic) { 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.Name { switch bpState.Breakpoint.Logical.Name {
case proc.FatalThrow: case proc.FatalThrow:
body.ExceptionId = "fatal error" body.ExceptionId = "fatal error"
body.Description, err = s.throwReason(goroutineID) body.Description, err = s.throwReason(goroutineID)

@ -72,10 +72,6 @@ type Debugger struct {
recordMutex sync.Mutex recordMutex sync.Mutex
dumpState proc.DumpState 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 breakpointIDCounter int
} }
@ -209,8 +205,6 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
} }
} }
d.disabledBreakpoints = make(map[int]*api.Breakpoint)
return d, nil return d, nil
} }
@ -520,46 +514,24 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
} }
discarded := []api.DiscardedBreakpoint{} discarded := []api.DiscardedBreakpoint{}
breakpoints := api.ConvertBreakpoints(d.breakpoints()) p.Breakpoints().Logical = d.target.Breakpoints().Logical
d.target = p d.target = p
maxID := 0 for _, oldBp := range d.target.Breakpoints().Logical {
for _, oldBp := range breakpoints { if oldBp.LogicalID < 0 || !oldBp.Enabled {
if oldBp.ID < 0 {
continue continue
} }
if oldBp.ID > maxID { if len(oldBp.File) > 0 {
maxID = oldBp.ID oldBp.TotalHitCount = 0
} oldBp.HitCount = make(map[int]uint64)
if oldBp.WatchExpr != "" { err := d.createPhysicalBreakpoints(oldBp)
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 err != nil { 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 continue
} }
createLogicalBreakpoint(d, addrs, oldBp, oldBp.ID)
} else { } else {
// Avoid setting a breakpoint based on address when rebuilding discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: "can not recreate watchpoint on restart"})
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
}
} }
} }
for _, bp := range d.disabledBreakpoints {
if bp.ID > maxID {
maxID = bp.ID
}
}
d.breakpointIDCounter = maxID
return discarded, nil return discarded, nil
} }
@ -609,7 +581,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
} }
for _, thread := range d.target.ThreadList() { for _, thread := range d.target.ThreadList() {
th := api.ConvertThread(thread) th := api.ConvertThread(thread, d.ConvertThreadBreakpoint(thread))
th.CallReturn = thread.Common().CallReturn th.CallReturn = thread.Common().CallReturn
if retLoadCfg != nil { 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)) state.WatchOutOfScope = make([]*api.Breakpoint, 0, len(d.target.Breakpoints().WatchOutOfScope))
for _, bp := range 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 return state, nil
@ -673,7 +647,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
) )
if requestedBp.Name != "" { 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") return nil, errors.New("breakpoint name already exists")
} }
} }
@ -714,32 +688,51 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
return createdBp, nil 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 // createLogicalBreakpoint creates one physical breakpoint for each address
// in addrs and associates all of them with the same logical breakpoint. // 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) { func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
p := d.target p := d.target
if dbp, ok := d.disabledBreakpoints[requestedBp.ID]; ok { if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
return dbp, proc.BreakpointExistsError{File: dbp.File, Line: dbp.Line, Addr: dbp.Addr} 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)) bps := make([]*proc.Breakpoint, len(addrs))
var err error
for i := range addrs { for i := range addrs {
if id <= 0 {
d.breakpointIDCounter++
id = d.breakpointIDCounter
}
bps[i], err = p.SetBreakpoint(id, addrs[i], proc.UserBreakpoint, nil) bps[i], err = p.SetBreakpoint(id, addrs[i], proc.UserBreakpoint, nil)
if err != nil { if err != nil {
break break
} }
err = copyBreakpointInfo(bps[i], requestedBp)
if err != nil {
break
}
} }
if err != nil { if err != nil {
delete(p.Breakpoints().Logical, id)
if isBreakpointExistsErr(err) { if isBreakpointExistsErr(err) {
return nil, err return nil, err
} }
@ -755,8 +748,65 @@ func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Break
return nil, err return nil, err
} }
createdBp := api.ConvertBreakpoints(bps) return d.convertBreakpoint(bps[0].Logical), nil
return createdBp[0], nil // we created a single logical breakpoint, the slice here will always have len == 1 }
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 { func isBreakpointExistsErr(err error) bool {
@ -775,47 +825,32 @@ func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
// It also enables or disables the breakpoint. // It also enables or disables the breakpoint.
// We can consume this function to avoid locking a goroutine. // We can consume this function to avoid locking a goroutine.
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error { func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error {
originals := d.findBreakpoint(amend.ID) original := d.target.Breakpoints().Logical[amend.ID]
if original == nil {
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 {
return fmt.Errorf("no breakpoint with ID %d", amend.ID) return fmt.Errorf("no breakpoint with ID %d", amend.ID)
} }
if !amend.Disabled && disabled { // enable the breakpoint if amend.Disabled && original.Enabled {
bp, err := d.target.SetBreakpoint(amend.ID, amend.Addr, proc.UserBreakpoint, nil) original.Enabled = false
err := copyLogicalBreakpointInfo(original, amend)
if err != nil { if err != nil {
return err return err
} }
copyBreakpointInfo(bp, amend) return d.clearPhysicalBreakpoints(amend.ID)
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
}
} }
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 return nil
} }
@ -836,37 +871,38 @@ func (d *Debugger) CancelNext() error {
return d.target.ClearSteppingBreakpoints() return d.target.ClearSteppingBreakpoints()
} }
func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err error) { func copyLogicalBreakpointInfo(lbp *proc.LogicalBreakpoint, requested *api.Breakpoint) error {
bp.Name = requested.Name lbp.Name = requested.Name
bp.Tracepoint = requested.Tracepoint lbp.Tracepoint = requested.Tracepoint
bp.TraceReturn = requested.TraceReturn lbp.TraceReturn = requested.TraceReturn
bp.Goroutine = requested.Goroutine lbp.Goroutine = requested.Goroutine
bp.Stacktrace = requested.Stacktrace lbp.Stacktrace = requested.Stacktrace
bp.Variables = requested.Variables lbp.Variables = requested.Variables
bp.UserData = requested.UserData lbp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs)
bp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs) lbp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals)
bp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals) lbp.UserData = requested.UserData
breaklet := bp.UserBreaklet() lbp.Cond = nil
if breaklet != nil { if requested.Cond != "" {
breaklet.Cond = nil var err error
if requested.Cond != "" { lbp.Cond, err = parser.ParseExpr(requested.Cond)
breaklet.Cond, err = parser.ParseExpr(requested.Cond) if err != nil {
} return err
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}
}
} }
} }
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) { 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 // clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) { func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
if bp, ok := d.disabledBreakpoints[requestedBp.ID]; ok { if requestedBp.ID <= 0 {
delete(d.disabledBreakpoints, bp.ID) bp := d.target.Breakpoints().M[requestedBp.Addr]
return bp, nil requestedBp.ID = bp.LogicalID()
} }
var clearBps []*proc.Breakpoint lbp := d.target.Breakpoints().Logical[requestedBp.ID]
clearedBp := d.convertBreakpoint(lbp)
toclear := func(addr uint64) { delete(d.target.Breakpoints().Logical, requestedBp.ID)
bp := d.target.Breakpoints().M[addr]
if bp != nil {
clearBps = append(clearBps, bp)
}
}
clearAddr := true err := d.clearPhysicalBreakpoints(requestedBp.ID)
for _, addr := range requestedBp.Addrs { if err != nil {
if addr == requestedBp.Addr { return nil, err
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())
} }
d.log.Infof("cleared breakpoint: %#v", clearedBp) d.log.Infof("cleared breakpoint: %#v", clearedBp)
@ -1008,46 +1006,36 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
var bps []*api.Breakpoint abps := []*api.Breakpoint{}
if all {
if !all {
bps = api.ConvertBreakpoints(d.breakpoints())
} else {
for _, bp := range d.target.Breakpoints().M { 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() 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))
} }
} }
return abps
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
} }
// FindBreakpoint returns the breakpoint specified by 'id'. // FindBreakpoint returns the breakpoint specified by 'id'.
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint { func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
bps := api.ConvertBreakpoints(d.findBreakpoint(id)) lbp := d.target.Breakpoints().Logical[id]
bps = append(bps, d.findDisabledBreakpoint(id)...) if lbp == nil {
if len(bps) <= 0 {
return nil return nil
} }
return bps[0] return d.convertBreakpoint(lbp)
} }
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint { func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
@ -1060,47 +1048,17 @@ func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
return bps 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' // FindBreakpointByName returns the breakpoint specified by 'name'
func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint { func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
return d.findBreakpointByName(name)
bp := d.findBreakpointByName(name)
if bp == nil {
bp = d.findDisabledBreakpointByName(name)
}
return bp
} }
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint { func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
var bps []*proc.Breakpoint for _, lbp := range d.target.Breakpoints().Logical {
for _, bp := range d.breakpoints() { if lbp.Name == name {
if bp.Name == name { return d.convertBreakpoint(lbp)
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
} }
} }
return nil return nil
@ -1118,9 +1076,9 @@ func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string,
return nil, err return nil, err
} }
if d.findBreakpointByName(expr) == nil { 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. // Threads returns the threads of the target process.
@ -2254,18 +2212,3 @@ func noDebugErrorWarning(err error) error {
} }
return err 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() s.debugger.LockTarget()
defer s.debugger.UnlockTarget() defer s.debugger.UnlockTarget()
*threads = api.ConvertThreads(pthreads) *threads = api.ConvertThreads(pthreads, s.debugger.ConvertThreadBreakpoint)
return nil return nil
} }
@ -169,7 +169,7 @@ func (s *RPCServer) GetThread(id int, thread *api.Thread) error {
} }
s.debugger.LockTarget() s.debugger.LockTarget()
defer s.debugger.UnlockTarget() defer s.debugger.UnlockTarget()
*thread = *api.ConvertThread(t) *thread = *api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
return nil return nil
} }

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