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:
parent
5bc8460328
commit
78471b3a5a
@ -47,7 +47,6 @@ type Breakpoint struct {
|
||||
|
||||
Addr uint64 // Address breakpoint is set for.
|
||||
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
|
||||
Name string // User defined name of the breakpoint
|
||||
|
||||
WatchExpr string
|
||||
WatchType WatchType
|
||||
@ -59,14 +58,7 @@ type Breakpoint struct {
|
||||
Breaklets []*Breaklet
|
||||
|
||||
// Breakpoint information
|
||||
Tracepoint bool // Tracepoint flag
|
||||
TraceReturn bool
|
||||
Goroutine bool // Retrieve goroutine information
|
||||
Stacktrace int // Number of stack frames to retrieve
|
||||
Variables []string // Variables to evaluate
|
||||
LoadArgs *LoadConfig
|
||||
LoadLocals *LoadConfig
|
||||
UserData interface{} // Any additional information about the breakpoint
|
||||
Logical *LogicalBreakpoint
|
||||
|
||||
// ReturnInfo describes how to collect return variables when this
|
||||
// breakpoint is hit as a return breakpoint.
|
||||
@ -85,9 +77,6 @@ type Breaklet struct {
|
||||
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
|
||||
Cond ast.Expr
|
||||
|
||||
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
|
||||
TotalHitCount uint64 // Number of times a breakpoint has been reached
|
||||
|
||||
// DeferReturns: when kind == NextDeferBreakpoint this breakpoint
|
||||
// will also check if the caller is runtime.gopanic or if the return
|
||||
// address is in the DeferReturns array.
|
||||
@ -99,13 +88,6 @@ type Breaklet struct {
|
||||
// the function, not when the function is called directly
|
||||
DeferReturns []uint64
|
||||
|
||||
// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
|
||||
// true with the TotalHitCount.
|
||||
HitCond *struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}
|
||||
|
||||
// checkPanicCall checks that the breakpoint happened while the function was
|
||||
// called by a panic. It is only checked for WatchOutOfScopeBreakpoint Kind.
|
||||
checkPanicCall bool
|
||||
@ -206,10 +188,12 @@ func (bp *Breakpoint) VerboseDescr() []string {
|
||||
r = append(r, fmt.Sprintf("HWBreakIndex=%#x watchStackOff=%#x", bp.HWBreakIndex, bp.watchStackOff))
|
||||
}
|
||||
|
||||
lbp := bp.Logical
|
||||
|
||||
for _, breaklet := range bp.Breaklets {
|
||||
switch breaklet.Kind {
|
||||
case UserBreakpoint:
|
||||
r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", exprToString(breaklet.Cond), breaklet.HitCond))
|
||||
r = append(r, fmt.Sprintf("User Cond=%q HitCond=%v", exprToString(breaklet.Cond), lbp.HitCond))
|
||||
case NextBreakpoint:
|
||||
r = append(r, fmt.Sprintf("Next Cond=%q", exprToString(breaklet.Cond)))
|
||||
case NextDeferBreakpoint:
|
||||
@ -280,11 +264,14 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
|
||||
|
||||
switch breaklet.Kind {
|
||||
case UserBreakpoint:
|
||||
if g, err := GetG(thread); err == nil {
|
||||
breaklet.HitCount[g.ID]++
|
||||
lbp := bpstate.Breakpoint.Logical
|
||||
if lbp != nil {
|
||||
if g, err := GetG(thread); err == nil {
|
||||
lbp.HitCount[g.ID]++
|
||||
}
|
||||
lbp.TotalHitCount++
|
||||
}
|
||||
breaklet.TotalHitCount++
|
||||
active = checkHitCond(breaklet)
|
||||
active = checkHitCond(lbp)
|
||||
|
||||
case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint:
|
||||
nextDeferOk := true
|
||||
@ -331,26 +318,26 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
|
||||
}
|
||||
|
||||
// checkHitCond evaluates bp's hit condition on thread.
|
||||
func checkHitCond(breaklet *Breaklet) bool {
|
||||
if breaklet.HitCond == nil {
|
||||
func checkHitCond(lbp *LogicalBreakpoint) bool {
|
||||
if lbp == nil || lbp.HitCond == nil {
|
||||
return true
|
||||
}
|
||||
// Evaluate the breakpoint condition.
|
||||
switch breaklet.HitCond.Op {
|
||||
switch lbp.HitCond.Op {
|
||||
case token.EQL:
|
||||
return int(breaklet.TotalHitCount) == breaklet.HitCond.Val
|
||||
return int(lbp.TotalHitCount) == lbp.HitCond.Val
|
||||
case token.NEQ:
|
||||
return int(breaklet.TotalHitCount) != breaklet.HitCond.Val
|
||||
return int(lbp.TotalHitCount) != lbp.HitCond.Val
|
||||
case token.GTR:
|
||||
return int(breaklet.TotalHitCount) > breaklet.HitCond.Val
|
||||
return int(lbp.TotalHitCount) > lbp.HitCond.Val
|
||||
case token.LSS:
|
||||
return int(breaklet.TotalHitCount) < breaklet.HitCond.Val
|
||||
return int(lbp.TotalHitCount) < lbp.HitCond.Val
|
||||
case token.GEQ:
|
||||
return int(breaklet.TotalHitCount) >= breaklet.HitCond.Val
|
||||
return int(lbp.TotalHitCount) >= lbp.HitCond.Val
|
||||
case token.LEQ:
|
||||
return int(breaklet.TotalHitCount) <= breaklet.HitCond.Val
|
||||
return int(lbp.TotalHitCount) <= lbp.HitCond.Val
|
||||
case token.REM:
|
||||
return int(breaklet.TotalHitCount)%breaklet.HitCond.Val == 0
|
||||
return int(lbp.TotalHitCount)%lbp.HitCond.Val == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -468,6 +455,9 @@ func (nbp NoBreakpointError) Error() string {
|
||||
type BreakpointMap struct {
|
||||
M map[uint64]*Breakpoint
|
||||
|
||||
// Logical is a map of logical breakpoints.
|
||||
Logical map[int]*LogicalBreakpoint
|
||||
|
||||
// WatchOutOfScope is the list of watchpoints that went out of scope during
|
||||
// the last resume operation
|
||||
WatchOutOfScope []*Breakpoint
|
||||
@ -651,23 +641,42 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi
|
||||
bpmap := t.Breakpoints()
|
||||
newBreaklet := &Breaklet{Kind: kind, Cond: cond}
|
||||
if kind == UserBreakpoint {
|
||||
newBreaklet.HitCount = map[int]uint64{}
|
||||
newBreaklet.LogicalID = logicalID
|
||||
}
|
||||
|
||||
setLogicalBreakpoint := func(bp *Breakpoint) {
|
||||
if kind != UserBreakpoint || bp.Logical != nil {
|
||||
return
|
||||
}
|
||||
if bpmap.Logical == nil {
|
||||
bpmap.Logical = make(map[int]*LogicalBreakpoint)
|
||||
}
|
||||
lbp := bpmap.Logical[logicalID]
|
||||
if lbp == nil {
|
||||
lbp = &LogicalBreakpoint{LogicalID: logicalID}
|
||||
lbp.HitCount = make(map[int]uint64)
|
||||
lbp.Enabled = true
|
||||
bpmap.Logical[logicalID] = lbp
|
||||
}
|
||||
bp.Logical = lbp
|
||||
breaklet := bp.UserBreaklet()
|
||||
if breaklet != nil && breaklet.Cond == nil {
|
||||
breaklet.Cond = lbp.Cond
|
||||
}
|
||||
lbp.File = bp.File
|
||||
lbp.Line = bp.Line
|
||||
fn := t.BinInfo().PCToFunc(bp.Addr)
|
||||
if fn != nil {
|
||||
lbp.FunctionName = fn.NameWithoutTypeParams()
|
||||
}
|
||||
}
|
||||
|
||||
if bp, ok := bpmap.M[addr]; ok {
|
||||
if !bp.canOverlap(kind) {
|
||||
return bp, BreakpointExistsError{bp.File, bp.Line, bp.Addr}
|
||||
}
|
||||
if kind == UserBreakpoint {
|
||||
bp.Tracepoint = false
|
||||
bp.TraceReturn = false
|
||||
bp.Goroutine = false
|
||||
bp.Stacktrace = 0
|
||||
bp.Variables = nil
|
||||
bp.LoadArgs = nil
|
||||
bp.LoadLocals = nil
|
||||
}
|
||||
bp.Breaklets = append(bp.Breaklets, newBreaklet)
|
||||
setLogicalBreakpoint(bp)
|
||||
return bp, nil
|
||||
}
|
||||
|
||||
@ -708,6 +717,7 @@ func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind Breakpoi
|
||||
}
|
||||
|
||||
newBreakpoint.Breaklets = append(newBreakpoint.Breaklets, newBreaklet)
|
||||
setLogicalBreakpoint(newBreakpoint)
|
||||
|
||||
bpmap.M[addr] = newBreakpoint
|
||||
|
||||
@ -741,6 +751,9 @@ func (t *Target) ClearBreakpoint(addr uint64) error {
|
||||
for i := range bp.Breaklets {
|
||||
if bp.Breaklets[i].Kind == UserBreakpoint {
|
||||
bp.Breaklets[i] = nil
|
||||
if bp.WatchExpr == "" {
|
||||
bp.Logical = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -805,6 +818,9 @@ func (t *Target) finishClearBreakpoint(bp *Breakpoint) (bool, error) {
|
||||
}
|
||||
|
||||
delete(t.Breakpoints().M, bp.Addr)
|
||||
if bp.WatchExpr != "" && bp.Logical != nil {
|
||||
delete(t.Breakpoints().Logical, bp.Logical.LogicalID)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@ -925,3 +941,46 @@ func returnInfoError(descr string, err error, mem MemoryReadWriter) []*Variable
|
||||
v.Name = "return value read error"
|
||||
return []*Variable{v}
|
||||
}
|
||||
|
||||
// LogicalBreakpoint represents a breakpoint set by a user.
|
||||
// A logical breakpoint can be associated with zero or many physical
|
||||
// breakpoints.
|
||||
// Where a physical breakpoint is associated with a specific instruction
|
||||
// address a logical breakpoint is associated with a source code location.
|
||||
// Therefore a logical breakpoint can be associated with zero or many
|
||||
// physical breakpoints.
|
||||
// It will have one or more physical breakpoints when source code has been
|
||||
// inlined (or in the case of type parametric code).
|
||||
// It will have zero physical breakpoints when it represents a deferred
|
||||
// breakpoint for code that will be loaded in the future.
|
||||
type LogicalBreakpoint struct {
|
||||
LogicalID int
|
||||
Name string
|
||||
FunctionName string
|
||||
File string
|
||||
Line int
|
||||
Enabled bool
|
||||
|
||||
Tracepoint bool // Tracepoint flag
|
||||
TraceReturn bool
|
||||
Goroutine bool // Retrieve goroutine information
|
||||
Stacktrace int // Number of stack frames to retrieve
|
||||
Variables []string // Variables to evaluate
|
||||
LoadArgs *LoadConfig
|
||||
LoadLocals *LoadConfig
|
||||
|
||||
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
|
||||
TotalHitCount uint64 // Number of times a breakpoint has been reached
|
||||
|
||||
// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
|
||||
// true with the TotalHitCount.
|
||||
HitCond *struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}
|
||||
|
||||
// Cond: if not nil the breakpoint will be triggered only if evaluating Cond returns true
|
||||
Cond ast.Expr
|
||||
|
||||
UserData interface{} // Any additional information about the breakpoint
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ func setFileBreakpoint(p *proc.Target, t *testing.T, fixture protest.Fixture, li
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many results %v", f, l, fixture.Source, lineno, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(0, addrs[0], proc.UserBreakpoint, nil)
|
||||
bp, err := p.SetBreakpoint(int(addrs[0]), addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err)
|
||||
}
|
||||
@ -164,18 +164,18 @@ func TestReverseBreakpointCounts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
|
||||
if bp.UserBreaklet().TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
|
||||
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
|
||||
if bp.Logical.TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
|
||||
}
|
||||
|
||||
if len(bp.UserBreaklet().HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
|
||||
if len(bp.Logical.HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
|
||||
}
|
||||
|
||||
for _, v := range bp.UserBreaklet().HitCount {
|
||||
for _, v := range bp.Logical.HitCount {
|
||||
if v != 100 {
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -319,8 +319,8 @@ func TestBreakpoint(t *testing.T) {
|
||||
assertNoError(err, t, "Registers")
|
||||
pc := regs.PC()
|
||||
|
||||
if bp.UserBreaklet().TotalHitCount != 1 {
|
||||
t.Fatalf("Breakpoint should be hit once, got %d\n", bp.UserBreaklet().TotalHitCount)
|
||||
if bp.Logical.TotalHitCount != 1 {
|
||||
t.Fatalf("Breakpoint should be hit once, got %d\n", bp.Logical.TotalHitCount)
|
||||
}
|
||||
|
||||
if pc-1 != bp.Addr && pc != bp.Addr {
|
||||
@ -1363,7 +1363,7 @@ func TestThreadFrameEvaluation(t *testing.T) {
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
|
||||
bp := p.CurrentThread().Breakpoint()
|
||||
if bp.Breakpoint == nil || bp.Name != deadlockBp {
|
||||
if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp {
|
||||
t.Fatalf("did not stop at deadlock breakpoint %v", bp)
|
||||
}
|
||||
|
||||
@ -1471,18 +1471,18 @@ func TestBreakpointCounts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
|
||||
if bp.UserBreaklet().TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
|
||||
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
|
||||
if bp.Logical.TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
|
||||
}
|
||||
|
||||
if len(bp.UserBreaklet().HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
|
||||
if len(bp.Logical.HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
|
||||
}
|
||||
|
||||
for _, v := range bp.UserBreaklet().HitCount {
|
||||
for _, v := range bp.Logical.HitCount {
|
||||
if v != 100 {
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1505,8 +1505,8 @@ func TestHardcodedBreakpointCounts(t *testing.T) {
|
||||
if bp == nil {
|
||||
continue
|
||||
}
|
||||
if bp.Name != proc.HardcodedBreakpoint {
|
||||
t.Fatalf("wrong breakpoint name %s", bp.Name)
|
||||
if bp.Logical.Name != proc.HardcodedBreakpoint {
|
||||
t.Fatalf("wrong breakpoint name %s", bp.Logical.Name)
|
||||
}
|
||||
g, err := proc.GetG(th)
|
||||
assertNoError(err, t, "GetG")
|
||||
@ -1576,23 +1576,23 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
|
||||
total += m[i] + 1
|
||||
}
|
||||
|
||||
if uint64(total) != bp.UserBreaklet().TotalHitCount {
|
||||
t.Fatalf("Mismatched total count %d %d\n", total, bp.UserBreaklet().TotalHitCount)
|
||||
if uint64(total) != bp.Logical.TotalHitCount {
|
||||
t.Fatalf("Mismatched total count %d %d\n", total, bp.Logical.TotalHitCount)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
|
||||
if bp.UserBreaklet().TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
|
||||
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
|
||||
if bp.Logical.TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
|
||||
}
|
||||
|
||||
if len(bp.UserBreaklet().HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
|
||||
if len(bp.Logical.HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
|
||||
}
|
||||
|
||||
for _, v := range bp.UserBreaklet().HitCount {
|
||||
for _, v := range bp.Logical.HitCount {
|
||||
if v != 100 {
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1767,7 +1767,7 @@ func TestCondBreakpointError(t *testing.T) {
|
||||
func TestHitCondBreakpointEQ(t *testing.T) {
|
||||
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
bp := setFileBreakpoint(p, t, fixture.Source, 7)
|
||||
bp.UserBreaklet().HitCond = &struct {
|
||||
bp.Logical.HitCond = &struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}{token.EQL, 3}
|
||||
@ -1791,7 +1791,7 @@ func TestHitCondBreakpointGEQ(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
bp := setFileBreakpoint(p, t, fixture.Source, 7)
|
||||
bp.UserBreaklet().HitCond = &struct {
|
||||
bp.Logical.HitCond = &struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}{token.GEQ, 3}
|
||||
@ -1814,7 +1814,7 @@ func TestHitCondBreakpointREM(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
bp := setFileBreakpoint(p, t, fixture.Source, 7)
|
||||
bp.UserBreaklet().HitCond = &struct {
|
||||
bp.Logical.HitCond = &struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}{token.REM, 2}
|
||||
@ -2015,7 +2015,7 @@ func TestPanicBreakpoint(t *testing.T) {
|
||||
withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
bp := p.CurrentThread().Breakpoint()
|
||||
if bp.Breakpoint == nil || bp.Name != proc.UnrecoveredPanic {
|
||||
if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic {
|
||||
t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
|
||||
}
|
||||
})
|
||||
@ -2025,7 +2025,7 @@ func TestCmdLineArgs(t *testing.T) {
|
||||
expectSuccess := func(p *proc.Target, fixture protest.Fixture) {
|
||||
err := p.Continue()
|
||||
bp := p.CurrentThread().Breakpoint()
|
||||
if bp.Breakpoint != nil && bp.Name == proc.UnrecoveredPanic {
|
||||
if bp.Breakpoint != nil && bp.Logical.Name == proc.UnrecoveredPanic {
|
||||
t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp)
|
||||
}
|
||||
exit, exited := err.(proc.ErrProcessExited)
|
||||
@ -2041,7 +2041,7 @@ func TestCmdLineArgs(t *testing.T) {
|
||||
expectPanic := func(p *proc.Target, fixture protest.Fixture) {
|
||||
p.Continue()
|
||||
bp := p.CurrentThread().Breakpoint()
|
||||
if bp.Breakpoint == nil || bp.Name != proc.UnrecoveredPanic {
|
||||
if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic {
|
||||
t.Fatalf("not on unrecovered-panic breakpoint: %v", bp)
|
||||
}
|
||||
}
|
||||
@ -2475,7 +2475,7 @@ func TestStepConcurrentDirect(t *testing.T) {
|
||||
assertNoError(err, t, "ClearBreakpoint()")
|
||||
|
||||
for _, b := range p.Breakpoints().M {
|
||||
if b.Name == proc.UnrecoveredPanic {
|
||||
if b.Logical.Name == proc.UnrecoveredPanic {
|
||||
err := p.ClearBreakpoint(b.Addr)
|
||||
assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
|
||||
break
|
||||
@ -2535,7 +2535,7 @@ func TestStepConcurrentPtr(t *testing.T) {
|
||||
setFileBreakpoint(p, t, fixture.Source, 24)
|
||||
|
||||
for _, b := range p.Breakpoints().M {
|
||||
if b.Name == proc.UnrecoveredPanic {
|
||||
if b.Logical.Name == proc.UnrecoveredPanic {
|
||||
err := p.ClearBreakpoint(b.Addr)
|
||||
assertNoError(err, t, "ClearBreakpoint(unrecovered-panic)")
|
||||
break
|
||||
@ -2645,7 +2645,7 @@ func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
|
||||
|
||||
// Next should be interrupted by a tracepoint on the same goroutine.
|
||||
bp = setFileBreakpoint(p, t, fixture.Source, 14)
|
||||
bp.Tracepoint = true
|
||||
bp.Logical.Tracepoint = true
|
||||
assertNoError(p.Next(), t, "Next()")
|
||||
assertLineNumber(p, t, 14, "wrong line number")
|
||||
if !p.Breakpoints().HasSteppingBreakpoints() {
|
||||
@ -4448,7 +4448,7 @@ func TestDeadlockBreakpoint(t *testing.T) {
|
||||
assertNoError(p.Continue(), t, "Continue()")
|
||||
|
||||
bp := p.CurrentThread().Breakpoint()
|
||||
if bp.Breakpoint == nil || bp.Name != deadlockBp {
|
||||
if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp {
|
||||
t.Fatalf("did not stop at deadlock breakpoint %v", bp)
|
||||
}
|
||||
})
|
||||
@ -5526,18 +5526,18 @@ func TestWatchpointCounts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("TotalHitCount: %d", bp.UserBreaklet().TotalHitCount)
|
||||
if bp.UserBreaklet().TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.UserBreaklet().TotalHitCount)
|
||||
t.Logf("TotalHitCount: %d", bp.Logical.TotalHitCount)
|
||||
if bp.Logical.TotalHitCount != 200 {
|
||||
t.Fatalf("Wrong TotalHitCount for the breakpoint (%d)", bp.Logical.TotalHitCount)
|
||||
}
|
||||
|
||||
if len(bp.UserBreaklet().HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.UserBreaklet().HitCount))
|
||||
if len(bp.Logical.HitCount) != 2 {
|
||||
t.Fatalf("Wrong number of goroutines for breakpoint (%d)", len(bp.Logical.HitCount))
|
||||
}
|
||||
|
||||
for _, v := range bp.UserBreaklet().HitCount {
|
||||
for _, v := range bp.Logical.HitCount {
|
||||
if v != 100 {
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.UserBreaklet().HitCount)
|
||||
t.Fatalf("Wrong HitCount for breakpoint (%v)", bp.Logical.HitCount)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -5836,7 +5836,7 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) {
|
||||
if bp != nil {
|
||||
t.Logf("%#v\n", bp.Breakpoint)
|
||||
}
|
||||
if bp == nil || (bp.Name != proc.UnrecoveredPanic) {
|
||||
if bp == nil || (bp.Logical.Name != proc.UnrecoveredPanic) {
|
||||
t.Fatalf("no breakpoint hit or wrong breakpoint hit: %#v", bp)
|
||||
}
|
||||
})
|
||||
|
@ -164,6 +164,7 @@ func watchpointOutOfScope(t *Target, watchpoint *Breakpoint) {
|
||||
log := logflags.DebuggerLogger()
|
||||
log.Errorf("could not clear out-of-scope watchpoint: %v", err)
|
||||
}
|
||||
delete(t.Breakpoints().Logical, watchpoint.LogicalID())
|
||||
}
|
||||
|
||||
// adjustStackWatchpoint is called when the goroutine of watchpoint resizes
|
||||
|
@ -398,8 +398,8 @@ func (t *Target) createUnrecoveredPanicBreakpoint() {
|
||||
if err == nil {
|
||||
bp, err := t.SetBreakpoint(unrecoveredPanicID, panicpcs[0], UserBreakpoint, nil)
|
||||
if err == nil {
|
||||
bp.Name = UnrecoveredPanic
|
||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
||||
bp.Logical.Name = UnrecoveredPanic
|
||||
bp.Logical.Variables = []string{"runtime.curg._panic.arg"}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -410,14 +410,14 @@ func (t *Target) createFatalThrowBreakpoint() {
|
||||
if err == nil {
|
||||
bp, err := t.SetBreakpoint(fatalThrowID, fatalpcs[0], UserBreakpoint, nil)
|
||||
if err == nil {
|
||||
bp.Name = FatalThrow
|
||||
bp.Logical.Name = FatalThrow
|
||||
}
|
||||
}
|
||||
fatalpcs, err = FindFunctionLocation(t.Process, "runtime.fatal", 0)
|
||||
if err == nil {
|
||||
bp, err := t.SetBreakpoint(fatalThrowID, fatalpcs[0], UserBreakpoint, nil)
|
||||
if err == nil {
|
||||
bp.Name = FatalThrow
|
||||
bp.Logical.Name = FatalThrow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,13 +171,13 @@ func (dbp *Target) Continue() error {
|
||||
return err
|
||||
}
|
||||
if onNextGoroutine &&
|
||||
((!curbp.Tracepoint && !curbp.TraceReturn) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
|
||||
(!isTraceOrTraceReturn(curbp.Breakpoint) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
|
||||
err := dbp.ClearSteppingBreakpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if curbp.Name == UnrecoveredPanic {
|
||||
if curbp.LogicalID() == unrecoveredPanicID {
|
||||
dbp.ClearSteppingBreakpoints()
|
||||
}
|
||||
if curbp.LogicalID() != hardcodedBreakpointID {
|
||||
@ -199,6 +199,13 @@ func (dbp *Target) Continue() error {
|
||||
}
|
||||
}
|
||||
|
||||
func isTraceOrTraceReturn(bp *Breakpoint) bool {
|
||||
if bp.Logical == nil {
|
||||
return false
|
||||
}
|
||||
return bp.Logical.Tracepoint || bp.Logical.TraceReturn
|
||||
}
|
||||
|
||||
func conditionErrors(threads []Thread) error {
|
||||
var condErr error
|
||||
for _, th := range threads {
|
||||
@ -1119,7 +1126,8 @@ func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Threa
|
||||
hcbp.File = loc.File
|
||||
hcbp.Line = loc.Line
|
||||
hcbp.Addr = loc.PC
|
||||
hcbp.Name = HardcodedBreakpoint
|
||||
hcbp.Logical = &LogicalBreakpoint{}
|
||||
hcbp.Logical.Name = HardcodedBreakpoint
|
||||
hcbp.Breaklets = []*Breaklet{&Breaklet{Kind: UserBreakpoint, LogicalID: hardcodedBreakpointID}}
|
||||
tgt.StopReason = StopHardcodedBreakpoint
|
||||
}
|
||||
|
@ -16,80 +16,69 @@ import (
|
||||
"github.com/go-delve/delve/pkg/proc"
|
||||
)
|
||||
|
||||
// ConvertBreakpoint converts from a proc.Breakpoint to
|
||||
// an api.Breakpoint.
|
||||
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
// ConvertLogicalBreakpoint converts a proc.LogicalBreakpoint into an API breakpoint.
|
||||
func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint {
|
||||
b := &Breakpoint{
|
||||
Name: bp.Name,
|
||||
ID: bp.LogicalID(),
|
||||
FunctionName: bp.FunctionName,
|
||||
File: bp.File,
|
||||
Line: bp.Line,
|
||||
Addr: bp.Addr,
|
||||
Tracepoint: bp.Tracepoint,
|
||||
TraceReturn: bp.TraceReturn,
|
||||
Stacktrace: bp.Stacktrace,
|
||||
Goroutine: bp.Goroutine,
|
||||
Variables: bp.Variables,
|
||||
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
|
||||
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
|
||||
WatchExpr: bp.WatchExpr,
|
||||
WatchType: WatchType(bp.WatchType),
|
||||
Addrs: []uint64{bp.Addr},
|
||||
UserData: bp.UserData,
|
||||
ID: lbp.LogicalID,
|
||||
FunctionName: lbp.FunctionName,
|
||||
File: lbp.File,
|
||||
Line: lbp.Line,
|
||||
Name: lbp.Name,
|
||||
Tracepoint: lbp.Tracepoint,
|
||||
TraceReturn: lbp.TraceReturn,
|
||||
Stacktrace: lbp.Stacktrace,
|
||||
Goroutine: lbp.Goroutine,
|
||||
Variables: lbp.Variables,
|
||||
LoadArgs: LoadConfigFromProc(lbp.LoadArgs),
|
||||
LoadLocals: LoadConfigFromProc(lbp.LoadLocals),
|
||||
TotalHitCount: lbp.TotalHitCount,
|
||||
Disabled: !lbp.Enabled,
|
||||
UserData: lbp.UserData,
|
||||
}
|
||||
|
||||
breaklet := bp.UserBreaklet()
|
||||
if breaklet != nil {
|
||||
b.TotalHitCount = breaklet.TotalHitCount
|
||||
b.HitCount = map[string]uint64{}
|
||||
for idx := range breaklet.HitCount {
|
||||
b.HitCount[strconv.Itoa(idx)] = breaklet.HitCount[idx]
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, token.NewFileSet(), breaklet.Cond)
|
||||
b.Cond = buf.String()
|
||||
if breaklet.HitCond != nil {
|
||||
b.HitCond = fmt.Sprintf("%s %d", breaklet.HitCond.Op.String(), breaklet.HitCond.Val)
|
||||
}
|
||||
b.HitCount = map[string]uint64{}
|
||||
for idx := range lbp.HitCount {
|
||||
b.HitCount[strconv.Itoa(idx)] = lbp.HitCount[idx]
|
||||
}
|
||||
|
||||
if lbp.HitCond != nil {
|
||||
b.HitCond = fmt.Sprintf("%s %d", lbp.HitCond.Op.String(), lbp.HitCond.Val)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, token.NewFileSet(), lbp.Cond)
|
||||
b.Cond = buf.String()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// ConvertBreakpoints converts a slice of physical breakpoints into a slice
|
||||
// of logical breakpoints.
|
||||
// The input must be sorted by increasing LogicalID
|
||||
func ConvertBreakpoints(bps []*proc.Breakpoint) []*Breakpoint {
|
||||
if len(bps) <= 0 {
|
||||
return nil
|
||||
// ConvertPhysicalBreakpoints adds informations from physical breakpoints to an API breakpoint.
|
||||
func ConvertPhysicalBreakpoints(b *Breakpoint, bps []*proc.Breakpoint) {
|
||||
if len(bps) == 0 {
|
||||
return
|
||||
}
|
||||
r := make([]*Breakpoint, 0, len(bps))
|
||||
|
||||
b.WatchExpr = bps[0].WatchExpr
|
||||
b.WatchType = WatchType(bps[0].WatchType)
|
||||
|
||||
lg := false
|
||||
for _, bp := range bps {
|
||||
if len(r) > 0 {
|
||||
if r[len(r)-1].ID == bp.LogicalID() {
|
||||
r[len(r)-1].Addrs = append(r[len(r)-1].Addrs, bp.Addr)
|
||||
if r[len(r)-1].FunctionName != bp.FunctionName && r[len(r)-1].FunctionName != "" {
|
||||
if !lg {
|
||||
r[len(r)-1].FunctionName = removeTypeParams(r[len(r)-1].FunctionName)
|
||||
lg = true
|
||||
}
|
||||
fn := removeTypeParams(bp.FunctionName)
|
||||
if r[len(r)-1].FunctionName != fn {
|
||||
r[len(r)-1].FunctionName = "(multiple functions)"
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else if r[len(r)-1].ID > bp.LogicalID() {
|
||||
panic("input not sorted")
|
||||
b.Addrs = append(b.Addrs, bp.Addr)
|
||||
if b.FunctionName != bp.FunctionName && b.FunctionName != "" {
|
||||
if !lg {
|
||||
b.FunctionName = removeTypeParams(b.FunctionName)
|
||||
lg = true
|
||||
}
|
||||
fn := removeTypeParams(bp.FunctionName)
|
||||
if b.FunctionName != fn {
|
||||
b.FunctionName = "(multiple functions)"
|
||||
}
|
||||
}
|
||||
r = append(r, ConvertBreakpoint(bp))
|
||||
lg = false
|
||||
}
|
||||
return r
|
||||
|
||||
if len(b.Addrs) > 0 {
|
||||
b.Addr = b.Addrs[0]
|
||||
}
|
||||
}
|
||||
|
||||
func removeTypeParams(name string) string {
|
||||
@ -99,7 +88,7 @@ func removeTypeParams(name string) string {
|
||||
|
||||
// ConvertThread converts a proc.Thread into an
|
||||
// api thread.
|
||||
func ConvertThread(th proc.Thread) *Thread {
|
||||
func ConvertThread(th proc.Thread, bp *Breakpoint) *Thread {
|
||||
var (
|
||||
function *Function
|
||||
file string
|
||||
@ -116,12 +105,6 @@ func ConvertThread(th proc.Thread) *Thread {
|
||||
function = ConvertFunction(loc.Fn)
|
||||
}
|
||||
|
||||
var bp *Breakpoint
|
||||
|
||||
if b := th.Breakpoint(); b.Active {
|
||||
bp = ConvertBreakpoint(b.Breakpoint)
|
||||
}
|
||||
|
||||
if g, _ := proc.GetG(th); g != nil {
|
||||
gid = g.ID
|
||||
}
|
||||
@ -138,10 +121,10 @@ func ConvertThread(th proc.Thread) *Thread {
|
||||
}
|
||||
|
||||
// ConvertThreads converts a slice of proc.Thread into a slice of api.Thread.
|
||||
func ConvertThreads(threads []proc.Thread) []*Thread {
|
||||
func ConvertThreads(threads []proc.Thread, convertBreakpoint func(proc.Thread) *Breakpoint) []*Thread {
|
||||
r := make([]*Thread, len(threads))
|
||||
for i := range threads {
|
||||
r[i] = ConvertThread(threads[i])
|
||||
r[i] = ConvertThread(threads[i], convertBreakpoint(threads[i]))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -1857,10 +1857,12 @@ func (s *Session) stoppedOnBreakpointGoroutineID(state *api.DebuggerState) (int,
|
||||
return goid, nil
|
||||
}
|
||||
bp := g.Thread.Breakpoint()
|
||||
if bp == nil || bp.Breakpoint == nil {
|
||||
if bp == nil || bp.Breakpoint == nil || bp.Breakpoint.Logical == nil {
|
||||
return goid, nil
|
||||
}
|
||||
return goid, api.ConvertBreakpoint(bp.Breakpoint)
|
||||
abp := api.ConvertLogicalBreakpoint(bp.Breakpoint.Logical)
|
||||
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp.Breakpoint})
|
||||
return goid, abp
|
||||
}
|
||||
|
||||
// stepUntilStopAndNotify is a wrapper around runUntilStopAndNotify that
|
||||
@ -3188,8 +3190,8 @@ func (s *Session) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
||||
}
|
||||
// Check if this goroutine ID is stopped at a breakpoint.
|
||||
includeStackTrace := true
|
||||
if bpState != nil && bpState.Breakpoint != nil && (bpState.Breakpoint.Name == proc.FatalThrow || bpState.Breakpoint.Name == proc.UnrecoveredPanic) {
|
||||
switch bpState.Breakpoint.Name {
|
||||
if bpState != nil && bpState.Breakpoint != nil && bpState.Breakpoint.Logical != nil && (bpState.Breakpoint.Logical.Name == proc.FatalThrow || bpState.Breakpoint.Logical.Name == proc.UnrecoveredPanic) {
|
||||
switch bpState.Breakpoint.Logical.Name {
|
||||
case proc.FatalThrow:
|
||||
body.ExceptionId = "fatal error"
|
||||
body.Description, err = s.throwReason(goroutineID)
|
||||
|
@ -72,10 +72,6 @@ type Debugger struct {
|
||||
recordMutex sync.Mutex
|
||||
|
||||
dumpState proc.DumpState
|
||||
// Debugger keeps a map of disabled breakpoints
|
||||
// so lower layers like proc doesn't need to deal
|
||||
// with them
|
||||
disabledBreakpoints map[int]*api.Breakpoint
|
||||
|
||||
breakpointIDCounter int
|
||||
}
|
||||
@ -209,8 +205,6 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
||||
}
|
||||
}
|
||||
|
||||
d.disabledBreakpoints = make(map[int]*api.Breakpoint)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@ -520,46 +514,24 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
||||
}
|
||||
|
||||
discarded := []api.DiscardedBreakpoint{}
|
||||
breakpoints := api.ConvertBreakpoints(d.breakpoints())
|
||||
p.Breakpoints().Logical = d.target.Breakpoints().Logical
|
||||
d.target = p
|
||||
maxID := 0
|
||||
for _, oldBp := range breakpoints {
|
||||
if oldBp.ID < 0 {
|
||||
for _, oldBp := range d.target.Breakpoints().Logical {
|
||||
if oldBp.LogicalID < 0 || !oldBp.Enabled {
|
||||
continue
|
||||
}
|
||||
if oldBp.ID > maxID {
|
||||
maxID = oldBp.ID
|
||||
}
|
||||
if oldBp.WatchExpr != "" {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate watchpoints on restart"})
|
||||
} else if len(oldBp.File) > 0 {
|
||||
addrs, err := proc.FindFileLocation(p, oldBp.File, oldBp.Line)
|
||||
if len(oldBp.File) > 0 {
|
||||
oldBp.TotalHitCount = 0
|
||||
oldBp.HitCount = make(map[int]uint64)
|
||||
err := d.createPhysicalBreakpoints(oldBp)
|
||||
if err != nil {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: err.Error()})
|
||||
continue
|
||||
}
|
||||
createLogicalBreakpoint(d, addrs, oldBp, oldBp.ID)
|
||||
} else {
|
||||
// Avoid setting a breakpoint based on address when rebuilding
|
||||
if rebuild {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate address breakpoints on restart"})
|
||||
continue
|
||||
}
|
||||
newBp, err := p.SetBreakpoint(oldBp.ID, oldBp.Addr, proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: "can not recreate watchpoint on restart"})
|
||||
}
|
||||
}
|
||||
for _, bp := range d.disabledBreakpoints {
|
||||
if bp.ID > maxID {
|
||||
maxID = bp.ID
|
||||
}
|
||||
}
|
||||
d.breakpointIDCounter = maxID
|
||||
return discarded, nil
|
||||
}
|
||||
|
||||
@ -609,7 +581,7 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
|
||||
}
|
||||
|
||||
for _, thread := range d.target.ThreadList() {
|
||||
th := api.ConvertThread(thread)
|
||||
th := api.ConvertThread(thread, d.ConvertThreadBreakpoint(thread))
|
||||
|
||||
th.CallReturn = thread.Common().CallReturn
|
||||
if retLoadCfg != nil {
|
||||
@ -630,7 +602,9 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
|
||||
|
||||
state.WatchOutOfScope = make([]*api.Breakpoint, 0, len(d.target.Breakpoints().WatchOutOfScope))
|
||||
for _, bp := range d.target.Breakpoints().WatchOutOfScope {
|
||||
state.WatchOutOfScope = append(state.WatchOutOfScope, api.ConvertBreakpoint(bp))
|
||||
abp := api.ConvertLogicalBreakpoint(bp.Logical)
|
||||
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
|
||||
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
@ -673,7 +647,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
)
|
||||
|
||||
if requestedBp.Name != "" {
|
||||
if (d.findBreakpointByName(requestedBp.Name) != nil) || (d.findDisabledBreakpointByName(requestedBp.Name) != nil) {
|
||||
if d.findBreakpointByName(requestedBp.Name) != nil {
|
||||
return nil, errors.New("breakpoint name already exists")
|
||||
}
|
||||
}
|
||||
@ -714,32 +688,51 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
return createdBp, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) convertBreakpoint(lbp *proc.LogicalBreakpoint) *api.Breakpoint {
|
||||
abp := api.ConvertLogicalBreakpoint(lbp)
|
||||
api.ConvertPhysicalBreakpoints(abp, d.findBreakpoint(lbp.LogicalID))
|
||||
return abp
|
||||
}
|
||||
|
||||
func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint {
|
||||
if b := thread.Breakpoint(); b.Active && b.Breakpoint.Logical != nil {
|
||||
return d.convertBreakpoint(b.Breakpoint.Logical)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createLogicalBreakpoint creates one physical breakpoint for each address
|
||||
// in addrs and associates all of them with the same logical breakpoint.
|
||||
func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
|
||||
p := d.target
|
||||
|
||||
if dbp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
|
||||
return dbp, proc.BreakpointExistsError{File: dbp.File, Line: dbp.Line, Addr: dbp.Addr}
|
||||
if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
|
||||
abp := d.convertBreakpoint(lbp)
|
||||
return abp, proc.BreakpointExistsError{File: lbp.File, Line: lbp.Line}
|
||||
}
|
||||
|
||||
var lbp *proc.LogicalBreakpoint
|
||||
if id <= 0 {
|
||||
d.breakpointIDCounter++
|
||||
id = d.breakpointIDCounter
|
||||
lbp = &proc.LogicalBreakpoint{LogicalID: id, HitCount: make(map[int]uint64), Enabled: true}
|
||||
p.Breakpoints().Logical[id] = lbp
|
||||
}
|
||||
|
||||
err := copyLogicalBreakpointInfo(lbp, requestedBp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bps := make([]*proc.Breakpoint, len(addrs))
|
||||
var err error
|
||||
for i := range addrs {
|
||||
if id <= 0 {
|
||||
d.breakpointIDCounter++
|
||||
id = d.breakpointIDCounter
|
||||
}
|
||||
bps[i], err = p.SetBreakpoint(id, addrs[i], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = copyBreakpointInfo(bps[i], requestedBp)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
delete(p.Breakpoints().Logical, id)
|
||||
if isBreakpointExistsErr(err) {
|
||||
return nil, err
|
||||
}
|
||||
@ -755,8 +748,65 @@ func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Break
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdBp := api.ConvertBreakpoints(bps)
|
||||
return createdBp[0], nil // we created a single logical breakpoint, the slice here will always have len == 1
|
||||
return d.convertBreakpoint(bps[0].Logical), nil
|
||||
}
|
||||
|
||||
func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error {
|
||||
addrs, err := proc.FindFileLocation(d.target, lbp.File, lbp.Line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bps := make([]*proc.Breakpoint, len(addrs))
|
||||
for i := range addrs {
|
||||
bps[i], err = d.target.SetBreakpoint(lbp.LogicalID, addrs[i], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if isBreakpointExistsErr(err) {
|
||||
return err
|
||||
}
|
||||
for _, bp := range bps {
|
||||
if bp == nil {
|
||||
continue
|
||||
}
|
||||
if err1 := d.target.ClearBreakpoint(bp.Addr); err1 != nil {
|
||||
return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Debugger) clearPhysicalBreakpoints(id int) error {
|
||||
var errs []error
|
||||
n := 0
|
||||
for _, bp := range d.target.Breakpoints().M {
|
||||
if bp.LogicalID() == id {
|
||||
n++
|
||||
err := d.target.ClearBreakpoint(bp.Addr)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
buf := new(bytes.Buffer)
|
||||
for i, err := range errs {
|
||||
fmt.Fprintf(buf, "%s", err)
|
||||
if i != len(errs)-1 {
|
||||
fmt.Fprintf(buf, ", ")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) == n {
|
||||
return fmt.Errorf("unable to clear breakpoint %d: %v", id, buf.String())
|
||||
}
|
||||
return fmt.Errorf("unable to clear breakpoint %d (partial): %s", id, buf.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isBreakpointExistsErr(err error) bool {
|
||||
@ -775,47 +825,32 @@ func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
|
||||
// It also enables or disables the breakpoint.
|
||||
// We can consume this function to avoid locking a goroutine.
|
||||
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error {
|
||||
originals := d.findBreakpoint(amend.ID)
|
||||
|
||||
if len(originals) > 0 && originals[0].WatchExpr != "" && amend.Disabled {
|
||||
return errors.New("can not disable watchpoints")
|
||||
}
|
||||
|
||||
_, disabled := d.disabledBreakpoints[amend.ID]
|
||||
if originals == nil && !disabled {
|
||||
original := d.target.Breakpoints().Logical[amend.ID]
|
||||
if original == nil {
|
||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||
}
|
||||
if !amend.Disabled && disabled { // enable the breakpoint
|
||||
bp, err := d.target.SetBreakpoint(amend.ID, amend.Addr, proc.UserBreakpoint, nil)
|
||||
if amend.Disabled && original.Enabled {
|
||||
original.Enabled = false
|
||||
err := copyLogicalBreakpointInfo(original, amend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copyBreakpointInfo(bp, amend)
|
||||
if breaklet := bp.UserBreaklet(); breaklet != nil {
|
||||
breaklet.TotalHitCount = amend.TotalHitCount
|
||||
breaklet.HitCount = map[int]uint64{}
|
||||
for idx := range amend.HitCount {
|
||||
i, err := strconv.Atoi(idx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't convert goroutine ID: %w", err)
|
||||
}
|
||||
breaklet.HitCount[i] = amend.HitCount[idx]
|
||||
}
|
||||
}
|
||||
delete(d.disabledBreakpoints, amend.ID)
|
||||
}
|
||||
if amend.Disabled && !disabled { // disable the breakpoint
|
||||
if _, err := d.clearBreakpoint(amend); err != nil {
|
||||
return err
|
||||
}
|
||||
d.disabledBreakpoints[amend.ID] = amend
|
||||
}
|
||||
for _, original := range originals {
|
||||
if err := copyBreakpointInfo(original, amend); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.clearPhysicalBreakpoints(amend.ID)
|
||||
}
|
||||
|
||||
if !amend.Disabled && !original.Enabled {
|
||||
original.Enabled = true
|
||||
copyLogicalBreakpointInfo(original, amend)
|
||||
return d.createPhysicalBreakpoints(original)
|
||||
}
|
||||
|
||||
err := copyLogicalBreakpointInfo(original, amend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, bp := range d.findBreakpoint(amend.ID) {
|
||||
bp.UserBreaklet().Cond = original.Cond
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -836,37 +871,38 @@ func (d *Debugger) CancelNext() error {
|
||||
return d.target.ClearSteppingBreakpoints()
|
||||
}
|
||||
|
||||
func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err error) {
|
||||
bp.Name = requested.Name
|
||||
bp.Tracepoint = requested.Tracepoint
|
||||
bp.TraceReturn = requested.TraceReturn
|
||||
bp.Goroutine = requested.Goroutine
|
||||
bp.Stacktrace = requested.Stacktrace
|
||||
bp.Variables = requested.Variables
|
||||
bp.UserData = requested.UserData
|
||||
bp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs)
|
||||
bp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals)
|
||||
breaklet := bp.UserBreaklet()
|
||||
if breaklet != nil {
|
||||
breaklet.Cond = nil
|
||||
if requested.Cond != "" {
|
||||
breaklet.Cond, err = parser.ParseExpr(requested.Cond)
|
||||
}
|
||||
breaklet.HitCond = nil
|
||||
if requested.HitCond != "" {
|
||||
opTok, val, parseErr := parseHitCondition(requested.HitCond)
|
||||
if err == nil {
|
||||
err = parseErr
|
||||
}
|
||||
if parseErr == nil {
|
||||
breaklet.HitCond = &struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}{opTok, val}
|
||||
}
|
||||
func copyLogicalBreakpointInfo(lbp *proc.LogicalBreakpoint, requested *api.Breakpoint) error {
|
||||
lbp.Name = requested.Name
|
||||
lbp.Tracepoint = requested.Tracepoint
|
||||
lbp.TraceReturn = requested.TraceReturn
|
||||
lbp.Goroutine = requested.Goroutine
|
||||
lbp.Stacktrace = requested.Stacktrace
|
||||
lbp.Variables = requested.Variables
|
||||
lbp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs)
|
||||
lbp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals)
|
||||
lbp.UserData = requested.UserData
|
||||
lbp.Cond = nil
|
||||
if requested.Cond != "" {
|
||||
var err error
|
||||
lbp.Cond, err = parser.ParseExpr(requested.Cond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
lbp.HitCond = nil
|
||||
if requested.HitCond != "" {
|
||||
opTok, val, err := parseHitCondition(requested.HitCond)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lbp.HitCond = &struct {
|
||||
Op token.Token
|
||||
Val int
|
||||
}{opTok, val}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseHitCondition(hitCond string) (token.Token, int, error) {
|
||||
@ -919,57 +955,19 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
|
||||
|
||||
// clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
|
||||
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
if bp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
|
||||
delete(d.disabledBreakpoints, bp.ID)
|
||||
return bp, nil
|
||||
if requestedBp.ID <= 0 {
|
||||
bp := d.target.Breakpoints().M[requestedBp.Addr]
|
||||
requestedBp.ID = bp.LogicalID()
|
||||
}
|
||||
|
||||
var clearBps []*proc.Breakpoint
|
||||
lbp := d.target.Breakpoints().Logical[requestedBp.ID]
|
||||
clearedBp := d.convertBreakpoint(lbp)
|
||||
|
||||
toclear := func(addr uint64) {
|
||||
bp := d.target.Breakpoints().M[addr]
|
||||
if bp != nil {
|
||||
clearBps = append(clearBps, bp)
|
||||
}
|
||||
}
|
||||
delete(d.target.Breakpoints().Logical, requestedBp.ID)
|
||||
|
||||
clearAddr := true
|
||||
for _, addr := range requestedBp.Addrs {
|
||||
if addr == requestedBp.Addr {
|
||||
clearAddr = false
|
||||
}
|
||||
toclear(addr)
|
||||
}
|
||||
if clearAddr {
|
||||
toclear(requestedBp.Addr)
|
||||
}
|
||||
|
||||
// Breakpoints need to be converted before clearing them or they won't have
|
||||
// an ID anymore.
|
||||
sort.Sort(breakpointsByLogicalID(clearBps))
|
||||
clearedBp := api.ConvertBreakpoints(clearBps)[0]
|
||||
|
||||
var errs []error
|
||||
for _, bp := range clearBps {
|
||||
err := d.target.ClearBreakpoint(bp.Addr)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("address %#x: %v", bp.Addr, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
buf := new(bytes.Buffer)
|
||||
for i, err := range errs {
|
||||
fmt.Fprintf(buf, "%s", err)
|
||||
if i != len(errs)-1 {
|
||||
fmt.Fprintf(buf, ", ")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) == len(clearBps) {
|
||||
return nil, fmt.Errorf("unable to clear breakpoint %d: %v", requestedBp.ID, buf.String())
|
||||
}
|
||||
return nil, fmt.Errorf("unable to clear breakpoint %d (partial): %s", requestedBp.ID, buf.String())
|
||||
err := d.clearPhysicalBreakpoints(requestedBp.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.log.Infof("cleared breakpoint: %#v", clearedBp)
|
||||
@ -1008,46 +1006,36 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
|
||||
var bps []*api.Breakpoint
|
||||
|
||||
if !all {
|
||||
bps = api.ConvertBreakpoints(d.breakpoints())
|
||||
} else {
|
||||
abps := []*api.Breakpoint{}
|
||||
if all {
|
||||
for _, bp := range d.target.Breakpoints().M {
|
||||
abp := api.ConvertBreakpoint(bp)
|
||||
var abp *api.Breakpoint
|
||||
if bp.Logical != nil {
|
||||
abp = api.ConvertLogicalBreakpoint(bp.Logical)
|
||||
} else {
|
||||
abp = &api.Breakpoint{}
|
||||
}
|
||||
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
|
||||
abp.VerboseDescr = bp.VerboseDescr()
|
||||
bps = append(bps, abp)
|
||||
abps = append(abps, abp)
|
||||
}
|
||||
} else {
|
||||
for _, lbp := range d.target.Breakpoints().Logical {
|
||||
abps = append(abps, d.convertBreakpoint(lbp))
|
||||
}
|
||||
}
|
||||
|
||||
for _, bp := range d.disabledBreakpoints {
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
|
||||
return bps
|
||||
}
|
||||
|
||||
func (d *Debugger) breakpoints() []*proc.Breakpoint {
|
||||
bps := []*proc.Breakpoint{}
|
||||
for _, bp := range d.target.Breakpoints().M {
|
||||
if bp.IsUser() {
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
sort.Sort(breakpointsByLogicalID(bps))
|
||||
return bps
|
||||
return abps
|
||||
}
|
||||
|
||||
// FindBreakpoint returns the breakpoint specified by 'id'.
|
||||
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
bps := api.ConvertBreakpoints(d.findBreakpoint(id))
|
||||
bps = append(bps, d.findDisabledBreakpoint(id)...)
|
||||
if len(bps) <= 0 {
|
||||
lbp := d.target.Breakpoints().Logical[id]
|
||||
if lbp == nil {
|
||||
return nil
|
||||
}
|
||||
return bps[0]
|
||||
return d.convertBreakpoint(lbp)
|
||||
}
|
||||
|
||||
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
|
||||
@ -1060,47 +1048,17 @@ func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
|
||||
return bps
|
||||
}
|
||||
|
||||
func (d *Debugger) findDisabledBreakpoint(id int) []*api.Breakpoint {
|
||||
var bps []*api.Breakpoint
|
||||
for _, dbp := range d.disabledBreakpoints {
|
||||
if dbp.ID == id {
|
||||
bps = append(bps, dbp)
|
||||
}
|
||||
}
|
||||
return bps
|
||||
}
|
||||
|
||||
// FindBreakpointByName returns the breakpoint specified by 'name'
|
||||
func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
|
||||
bp := d.findBreakpointByName(name)
|
||||
if bp == nil {
|
||||
bp = d.findDisabledBreakpointByName(name)
|
||||
}
|
||||
return bp
|
||||
return d.findBreakpointByName(name)
|
||||
}
|
||||
|
||||
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
||||
var bps []*proc.Breakpoint
|
||||
for _, bp := range d.breakpoints() {
|
||||
if bp.Name == name {
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
if len(bps) == 0 {
|
||||
return nil
|
||||
}
|
||||
sort.Sort(breakpointsByLogicalID(bps))
|
||||
r := api.ConvertBreakpoints(bps)
|
||||
return r[0] // there can only be one logical breakpoint with the same name
|
||||
}
|
||||
|
||||
func (d *Debugger) findDisabledBreakpointByName(name string) *api.Breakpoint {
|
||||
for _, dbp := range d.disabledBreakpoints {
|
||||
if dbp.Name == name {
|
||||
return dbp
|
||||
for _, lbp := range d.target.Breakpoints().Logical {
|
||||
if lbp.Name == name {
|
||||
return d.convertBreakpoint(lbp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -1118,9 +1076,9 @@ func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string,
|
||||
return nil, err
|
||||
}
|
||||
if d.findBreakpointByName(expr) == nil {
|
||||
bp.Name = expr
|
||||
bp.Logical.Name = expr
|
||||
}
|
||||
return api.ConvertBreakpoint(bp), nil
|
||||
return d.convertBreakpoint(bp.Logical), nil
|
||||
}
|
||||
|
||||
// Threads returns the threads of the target process.
|
||||
@ -2254,18 +2212,3 @@ func noDebugErrorWarning(err error) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type breakpointsByLogicalID []*proc.Breakpoint
|
||||
|
||||
func (v breakpointsByLogicalID) Len() int { return len(v) }
|
||||
func (v breakpointsByLogicalID) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
|
||||
func (v breakpointsByLogicalID) Less(i, j int) bool {
|
||||
if v[i].LogicalID() == v[j].LogicalID() {
|
||||
if v[i].WatchType != v[j].WatchType {
|
||||
return v[i].WatchType > v[j].WatchType // if a logical breakpoint contains a watchpoint let the watchpoint sort first
|
||||
}
|
||||
return v[i].Addr < v[j].Addr
|
||||
}
|
||||
return v[i].LogicalID() < v[j].LogicalID()
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) (err er
|
||||
}
|
||||
s.debugger.LockTarget()
|
||||
defer s.debugger.UnlockTarget()
|
||||
*threads = api.ConvertThreads(pthreads)
|
||||
*threads = api.ConvertThreads(pthreads, s.debugger.ConvertThreadBreakpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ func (s *RPCServer) GetThread(id int, thread *api.Thread) error {
|
||||
}
|
||||
s.debugger.LockTarget()
|
||||
defer s.debugger.UnlockTarget()
|
||||
*thread = *api.ConvertThread(t)
|
||||
*thread = *api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ func (s *RPCServer) ListThreads(arg ListThreadsIn, out *ListThreadsOut) (err err
|
||||
}
|
||||
s.debugger.LockTarget()
|
||||
defer s.debugger.UnlockTarget()
|
||||
out.Threads = api.ConvertThreads(threads)
|
||||
out.Threads = api.ConvertThreads(threads, s.debugger.ConvertThreadBreakpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -410,7 +410,7 @@ func (s *RPCServer) GetThread(arg GetThreadIn, out *GetThreadOut) error {
|
||||
}
|
||||
s.debugger.LockTarget()
|
||||
defer s.debugger.UnlockTarget()
|
||||
out.Thread = api.ConvertThread(t)
|
||||
out.Thread = api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user