diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index 653c3311..50581cd9 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -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 +} diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index 682d32ed..7b4a53f6 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -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) } } }) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index f156a6f1..e0fa81c3 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -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) } }) diff --git a/pkg/proc/stackwatch.go b/pkg/proc/stackwatch.go index 83881edf..3ab57274 100644 --- a/pkg/proc/stackwatch.go +++ b/pkg/proc/stackwatch.go @@ -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 diff --git a/pkg/proc/target.go b/pkg/proc/target.go index ac5930b6..dfa7b4de 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -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 } } } diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index 32bd558e..9f3b1e6b 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -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 } diff --git a/service/api/conversions.go b/service/api/conversions.go index c2f16df7..5248f95d 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -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 } diff --git a/service/dap/server.go b/service/dap/server.go index 0045dff0..f091fc2f 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -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) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 52c72fee..d96cbb0e 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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() -} diff --git a/service/rpc1/server.go b/service/rpc1/server.go index 7ed452b2..b4294309 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -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 } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 5a01f5b4..ae642fbe 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -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 }