aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-08-22 17:49:28 +0200
committerAleksandr Nogikh <nogikh@google.com>2024-08-27 13:41:43 +0000
commit36293874a6c15ab8f79533163a6200aa6277757d (patch)
tree1538fdce9b238de37e4ce61bd7a56e103b1590a8
parentdee56964d94d28f3f609bc4f62042dd6d1e153ae (diff)
pkg/repro: consider the executor info from the crash report
1) If we know the tentative reproducer, try only it before the bisection. It's the best single candidate program. 2) During bisection, never drop the program.
-rw-r--r--pkg/repro/repro.go95
1 files changed, 63 insertions, 32 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go
index 2091d3c88..5f0947541 100644
--- a/pkg/repro/repro.go
+++ b/pkg/repro/repro.go
@@ -45,18 +45,19 @@ type Stats struct {
}
type reproContext struct {
- exec execInterface
- logf func(string, ...interface{})
- target *targets.Target
- crashTitle string
- crashType crash.Type
- crashStart int
- entries []*prog.LogEntry
- testTimeouts []time.Duration
- startOpts csource.Options
- stats *Stats
- report *report.Report
- timeouts targets.Timeouts
+ exec execInterface
+ logf func(string, ...interface{})
+ target *targets.Target
+ crashTitle string
+ crashType crash.Type
+ crashStart int
+ crashExecutor *report.ExecutorInfo
+ entries []*prog.LogEntry
+ testTimeouts []time.Duration
+ startOpts csource.Options
+ stats *Stats
+ report *report.Report
+ timeouts targets.Timeouts
}
// execInterface describes the interfaces needed by pkg/repro.
@@ -91,10 +92,12 @@ func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature
}
crashStart := len(crashLog)
crashTitle, crashType := "", crash.UnknownType
+ var crashExecutor *report.ExecutorInfo
if rep := reporter.Parse(crashLog); rep != nil {
crashStart = rep.StartPos
crashTitle = rep.Title
crashType = rep.Type
+ crashExecutor = rep.Executor
}
testTimeouts := []time.Duration{
3 * cfg.Timeouts.Program, // to catch simpler crashes (i.e. no races and no hangs)
@@ -116,11 +119,13 @@ func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature
testTimeouts = testTimeouts[2:]
}
ctx := &reproContext{
- exec: exec,
- target: cfg.SysTarget,
- crashTitle: crashTitle,
- crashType: crashType,
- crashStart: crashStart,
+ exec: exec,
+ target: cfg.SysTarget,
+ crashTitle: crashTitle,
+ crashType: crashType,
+ crashStart: crashStart,
+ crashExecutor: crashExecutor,
+
entries: entries,
testTimeouts: testTimeouts,
startOpts: createStartOptions(cfg, features, crashType),
@@ -252,24 +257,26 @@ func (ctx *reproContext) extractProg(entries []*prog.LogEntry) (*Result, error)
ctx.stats.ExtractProgTime = time.Since(start)
}()
- // Extract last program on every proc.
- procs := make(map[int]int)
- for i, ent := range entries {
- procs[ent.Proc] = i
- }
- var indices []int
- for _, idx := range procs {
- indices = append(indices, idx)
+ var toTest []*prog.LogEntry
+ if ctx.crashExecutor != nil {
+ for _, entry := range entries {
+ if entry.ID == ctx.crashExecutor.ExecID {
+ toTest = append(toTest, entry)
+ ctx.reproLogf(3, "first checking the prog from the crash report")
+ break
+ }
+ }
}
- sort.Ints(indices)
- var lastEntries []*prog.LogEntry
- for i := len(indices) - 1; i >= 0; i-- {
- lastEntries = append(lastEntries, entries[indices[i]])
+
+ if len(toTest) == 0 {
+ ctx.reproLogf(3, "testing a last program of every proc")
+ toTest = lastEntries(entries)
}
+
for _, timeout := range ctx.testTimeouts {
// Execute each program separately to detect simple crashes caused by a single program.
// Programs are executed in reverse order, usually the last program is the guilty one.
- res, err := ctx.extractProgSingle(lastEntries, timeout)
+ res, err := ctx.extractProgSingle(toTest, timeout)
if err != nil {
return nil, err
}
@@ -298,6 +305,24 @@ func (ctx *reproContext) extractProg(entries []*prog.LogEntry) (*Result, error)
return nil, nil
}
+// Extract last program on every proc.
+func lastEntries(entries []*prog.LogEntry) []*prog.LogEntry {
+ procs := make(map[int]int)
+ for i, ent := range entries {
+ procs[ent.Proc] = i
+ }
+ var indices []int
+ for _, idx := range procs {
+ indices = append(indices, idx)
+ }
+ sort.Ints(indices)
+ var lastEntries []*prog.LogEntry
+ for i := len(indices) - 1; i >= 0; i-- {
+ lastEntries = append(lastEntries, entries[indices[i]])
+ }
+ return lastEntries
+}
+
func (ctx *reproContext) extractProgSingle(entries []*prog.LogEntry, duration time.Duration) (*Result, error) {
ctx.reproLogf(3, "single: executing %d programs separately with timeout %s", len(entries), duration)
@@ -651,7 +676,7 @@ func (ctx *reproContext) bisectProgs(progs []*prog.LogEntry, pred func([]*prog.L
}
return pred(progs)
}
- ret, err := minimize.Slice(minimize.Config[*prog.LogEntry]{
+ ret, err := minimize.SliceWithFixed(minimize.Config[*prog.LogEntry]{
Pred: minimizePred,
// For flaky crashes we usually end up with too many chunks.
// Continuing bisection would just take a lot of time and likely produce no result.
@@ -659,7 +684,13 @@ func (ctx *reproContext) bisectProgs(progs []*prog.LogEntry, pred func([]*prog.L
Logf: func(msg string, args ...interface{}) {
ctx.reproLogf(3, "bisect: "+msg, args...)
},
- }, progs)
+ }, progs, func(elem *prog.LogEntry) bool {
+ if ctx.crashExecutor == nil {
+ return false
+ }
+ // If the program was mentioned in the crash report, always keep it during bisection.
+ return elem.ID == ctx.crashExecutor.ExecID
+ })
if err == minimize.ErrTooManyChunks {
ctx.reproLogf(3, "bisect: too many guilty chunks, aborting")
return nil, nil