diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 62649c13..100d189a 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -180,6 +180,7 @@ Set breakpoint condition. condition . condition -hitcount . + condition -per-g-hitcount . condition -clear . Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true. @@ -196,6 +197,8 @@ With the -hitcount option a condition on the breakpoint hit count can be set, th condition -hitcount bp != n condition -hitcount bp % n +The -per-g-hitcount option works like -hitcount, but use per goroutine hitcount to compare with n. + With the -clear option a condtion on the breakpoint can removed. The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n. diff --git a/_fixtures/condperghitcount.go b/_fixtures/condperghitcount.go new file mode 100644 index 00000000..fafa416a --- /dev/null +++ b/_fixtures/condperghitcount.go @@ -0,0 +1,25 @@ +package main + +import ( + "sync" + "time" +) + +func main() { + var wg sync.WaitGroup + wg.Add(2) + for i := 0; i < 2; i++ { + go func() { + j := 0 + for { + j++ + if j > 10 { + break + } + } + wg.Done() + }() + time.Sleep(time.Second) + } + wg.Wait() +} diff --git a/pkg/proc/breakpoints.go b/pkg/proc/breakpoints.go index 50581cd9..9e5d27b9 100644 --- a/pkg/proc/breakpoints.go +++ b/pkg/proc/breakpoints.go @@ -264,14 +264,16 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa switch breaklet.Kind { case UserBreakpoint: + var goroutineID int lbp := bpstate.Breakpoint.Logical if lbp != nil { if g, err := GetG(thread); err == nil { - lbp.HitCount[g.ID]++ + goroutineID = g.ID + lbp.HitCount[goroutineID]++ } lbp.TotalHitCount++ } - active = checkHitCond(lbp) + active = checkHitCond(lbp, goroutineID) case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint: nextDeferOk := true @@ -318,26 +320,30 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa } // checkHitCond evaluates bp's hit condition on thread. -func checkHitCond(lbp *LogicalBreakpoint) bool { +func checkHitCond(lbp *LogicalBreakpoint, goroutineID int) bool { if lbp == nil || lbp.HitCond == nil { return true } + hitCount := int(lbp.TotalHitCount) + if lbp.HitCondPerG && goroutineID > 0 { + hitCount = int(lbp.HitCount[goroutineID]) + } // Evaluate the breakpoint condition. switch lbp.HitCond.Op { case token.EQL: - return int(lbp.TotalHitCount) == lbp.HitCond.Val + return hitCount == lbp.HitCond.Val case token.NEQ: - return int(lbp.TotalHitCount) != lbp.HitCond.Val + return hitCount != lbp.HitCond.Val case token.GTR: - return int(lbp.TotalHitCount) > lbp.HitCond.Val + return hitCount > lbp.HitCond.Val case token.LSS: - return int(lbp.TotalHitCount) < lbp.HitCond.Val + return hitCount < lbp.HitCond.Val case token.GEQ: - return int(lbp.TotalHitCount) >= lbp.HitCond.Val + return hitCount >= lbp.HitCond.Val case token.LEQ: - return int(lbp.TotalHitCount) <= lbp.HitCond.Val + return hitCount <= lbp.HitCond.Val case token.REM: - return int(lbp.TotalHitCount)%lbp.HitCond.Val == 0 + return hitCount%lbp.HitCond.Val == 0 } return false } @@ -971,6 +977,7 @@ type LogicalBreakpoint struct { 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 + HitCondPerG bool // Use per goroutine hitcount as HitCond operand, instead of total hitcount // HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns // true with the TotalHitCount. diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 42611981..64469877 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -451,6 +451,7 @@ The command 'on x -edit' can be used to edit the list of commands executed when condition . condition -hitcount . + condition -per-g-hitcount . condition -clear . Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true. @@ -467,6 +468,8 @@ With the -hitcount option a condition on the breakpoint hit count can be set, th condition -hitcount bp != n condition -hitcount bp % n +The -per-g-hitcount option works like -hitcount, but use per goroutine hitcount to compare with n. + With the -clear option a condtion on the breakpoint can removed. The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n. @@ -1654,7 +1657,11 @@ func formatBreakpointAttrs(prefix string, bp *api.Breakpoint, includeTrace bool) attrs = append(attrs, fmt.Sprintf("%scond %s", prefix, bp.Cond)) } if bp.HitCond != "" { - attrs = append(attrs, fmt.Sprintf("%scond -hitcount %s", prefix, bp.HitCond)) + if bp.HitCondPerG { + attrs = append(attrs, fmt.Sprintf("%scond -per-g-hitcount %s", prefix, bp.HitCond)) + } else { + attrs = append(attrs, fmt.Sprintf("%scond -hitcount %s", prefix, bp.HitCond)) + } } if bp.Stacktrace > 0 { attrs = append(attrs, fmt.Sprintf("%sstack %d", prefix, bp.Stacktrace)) @@ -2814,11 +2821,13 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error { return fmt.Errorf("not enough arguments") } - if args[0] == "-hitcount" { + hitCondPerG := args[0] == "-per-g-hitcount" + if args[0] == "-hitcount" || hitCondPerG { // hitcount breakpoint if ctx.Prefix == onPrefix { ctx.Breakpoint.HitCond = args[1] + ctx.Breakpoint.HitCondPerG = hitCondPerG return nil } @@ -2833,6 +2842,7 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error { } bp.HitCond = args[1] + bp.HitCondPerG = hitCondPerG return t.client.AmendBreakpoint(bp) } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 39d8ff52..acc2dbf8 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -1158,6 +1158,26 @@ func TestHitCondBreakpoint(t *testing.T) { t.Fatalf("wrong value of i") } }) + + withTestTerminal("condperghitcount", t, func(term *FakeTerminal) { + term.MustExec("break bp1 main.main:8") + term.MustExec("condition -per-g-hitcount bp1 == 2") + listIsAt(t, term, "continue", 16, -1, -1) + // first g hit + out := term.MustExec("print j") + t.Logf("%q", out) + if !strings.Contains(out, "2\n") { + t.Fatalf("wrong value of j") + } + term.MustExec("toggle bp1") + listIsAt(t, term, "continue", 16, -1, -1) + // second g hit + out = term.MustExec("print j") + t.Logf("%q", out) + if !strings.Contains(out, "2\n") { + t.Fatalf("wrong value of j") + } + }) } func TestClearCondBreakpoint(t *testing.T) { diff --git a/service/api/conversions.go b/service/api/conversions.go index 5248f95d..f16a2645 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -43,6 +43,7 @@ func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint { if lbp.HitCond != nil { b.HitCond = fmt.Sprintf("%s %d", lbp.HitCond.Op.String(), lbp.HitCond.Val) + b.HitCondPerG = lbp.HitCondPerG } var buf bytes.Buffer diff --git a/service/api/types.go b/service/api/types.go index 5187ebdd..258e82ca 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -92,6 +92,8 @@ type Breakpoint struct { // Breakpoint hit count condition. // Supported hit count conditions are "NUMBER" and "OP NUMBER". HitCond string + // HitCondPerG use per goroutine hitcount as HitCond operand, instead of total hitcount + HitCondPerG bool // Tracepoint flag, signifying this is a tracepoint. Tracepoint bool `json:"continue"` diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index d96cbb0e..886bdbd0 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -900,6 +900,7 @@ func copyLogicalBreakpointInfo(lbp *proc.LogicalBreakpoint, requested *api.Break Op token.Token Val int }{opTok, val} + lbp.HitCondPerG = requested.HitCondPerG } return nil