Automatically disable breakpoints with hitcount conditions that will never be satisfied again (#2833)

* service/debugger: disable breakpoints with hitcond not satisfiable

To avoid slowing down the debugged process unnecessarily, we disable
breakpoints with a hit condition that can no longer be hit again.

* test: add integration tests for hit conditions no more satisfiable

* proc/test: fix typo in breakpoints related tests

* test: use the new API for hitcond integration tests
This commit is contained in:
Fabio Falzoi
2022-01-06 18:00:46 +01:00
committed by GitHub
parent 7afe640773
commit 79d5db24a5
4 changed files with 175 additions and 10 deletions

View File

@ -770,12 +770,10 @@ func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
return d.target.SetEBPFTracepoint(fnName)
}
// AmendBreakpoint will update the breakpoint with the matching ID.
// amendBreakpoint will update the breakpoint with the matching ID.
// It also enables or disables the breakpoint.
func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
// 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 {
@ -792,6 +790,17 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
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
@ -809,6 +818,15 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
return nil
}
// AmendBreakpoint will update the breakpoint with the matching ID.
// It also enables or disables the breakpoint.
func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.amendBreakpoint(amend)
}
// CancelNext will clear internal breakpoints, thus cancelling the 'next',
// 'step' or 'stepout' operation.
func (d *Debugger) CancelNext() error {
@ -957,6 +975,33 @@ func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
return clearedBp, nil
}
// isBpHitCondNotSatisfiable returns true if the breakpoint bp has a hit
// condition that is no more satisfiable.
// The hit condition is considered no more satisfiable if it can no longer be
// hit again, for example with {Op: "==", Val: 1} and TotalHitCount == 1.
func isBpHitCondNotSatisfiable(bp *api.Breakpoint) bool {
if bp.HitCond == "" {
return false
}
tok, val, err := parseHitCondition(bp.HitCond)
if err != nil {
return false
}
switch tok {
case token.EQL, token.LEQ:
if int(bp.TotalHitCount) >= val {
return true
}
case token.LSS:
if int(bp.TotalHitCount) >= val-1 {
return true
}
}
return false
}
// Breakpoints returns the list of current breakpoints.
func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
d.targetMutex.Lock()
@ -1284,6 +1329,10 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
}
}
}
if bp := state.CurrentThread.Breakpoint; bp != nil && isBpHitCondNotSatisfiable(bp) {
bp.Disabled = true
d.amendBreakpoint(bp)
}
return state, err
}