diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2024-05-27 14:30:34 +0200 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2024-05-27 13:05:20 +0000 |
| commit | 57e89ea959ef691f69b83c8c33bc4c8b1f65c68f (patch) | |
| tree | fe33e83d059ceaef99c381cf0ad79cf28a5ba503 /pkg/repro | |
| parent | 8bbf94ce31b652c168de6ea784942b54ea09e80c (diff) | |
pkg/repro: avoid hitting the prog.MaxCalls limit
When we combine the progs found during prog bisection, there's a chance
that we may exceed the prog.MaxCalls limit. In that case, we get a
SYZFATAL error and proceed as if it were the target crash. That's
absolutely wrong.
Let's first minimize each single program before concatenating them, that
should work for almost all cases.
Diffstat (limited to 'pkg/repro')
| -rw-r--r-- | pkg/repro/repro.go | 74 | ||||
| -rw-r--r-- | pkg/repro/repro_test.go | 35 |
2 files changed, 94 insertions, 15 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go index 67fac3811..08ed610ec 100644 --- a/pkg/repro/repro.go +++ b/pkg/repro/repro.go @@ -379,29 +379,70 @@ func (ctx *context) extractProgBisect(entries []*prog.LogEntry, baseDuration tim ctx.reproLogf(3, "bisect: trying to concatenate") // Concatenate all programs into one. - prog := &prog.Prog{ + dur := duration(len(entries)) * 3 / 2 + return ctx.concatenateProgs(entries, dur) +} + +// The bisected progs may exceed the prog.MaxCalls limit. +// So let's first try to drop unneeded calls. +func (ctx *context) concatenateProgs(entries []*prog.LogEntry, dur time.Duration) (*Result, error) { + ctx.reproLogf(3, "bisect: concatenate %d entries", len(entries)) + if len(entries) > 1 { + // There's a risk of exceeding prog.MaxCalls, so let's first minimize + // all entries separately. + for i := 0; i < len(entries); i++ { + ctx.reproLogf(1, "minimizing program #%d before concatenation", i) + callsBefore := len(entries[i].P.Calls) + entries[i].P, _ = prog.Minimize(entries[i].P, -1, prog.MinimizeParams{ + RemoveCallsOnly: true, + }, + func(p1 *prog.Prog, _ int) bool { + var newEntries []*prog.LogEntry + if i > 0 { + newEntries = append(newEntries, entries[:i]...) + } + newEntries = append(newEntries, &prog.LogEntry{ + P: p1, + }) + if i+1 < len(entries) { + newEntries = append(newEntries, entries[i+1:]...) + } + crashed, err := ctx.testProgs(newEntries, dur, ctx.startOpts) + if err != nil { + ctx.reproLogf(0, "concatenation step failed with %v", err) + return false + } + return crashed + }) + ctx.reproLogf(1, "minimized %d calls -> %d calls", callsBefore, len(entries[i].P.Calls)) + } + } + p := &prog.Prog{ Target: entries[0].P.Target, } for _, entry := range entries { - prog.Calls = append(prog.Calls, entry.P.Calls...) + p.Calls = append(p.Calls, entry.P.Calls...) } - dur := duration(len(entries)) * 3 / 2 - crashed, err := ctx.testProg(prog, dur, opts) + if len(p.Calls) > prog.MaxCalls { + ctx.reproLogf(2, "bisect: concatenated prog still exceeds %d calls", prog.MaxCalls) + return nil, nil + } + crashed, err := ctx.testProg(p, dur, ctx.startOpts) if err != nil { + ctx.reproLogf(3, "bisect: error during concatenation testing: %v", err) return nil, err } - if crashed { - res := &Result{ - Prog: prog, - Duration: dur, - Opts: opts, - } - ctx.reproLogf(3, "bisect: concatenation succeeded") - return res, nil + if !crashed { + ctx.reproLogf(3, "bisect: concatenated prog does not crash") + return nil, nil } - - ctx.reproLogf(3, "bisect: concatenation failed") - return nil, nil + res := &Result{ + Prog: p, + Duration: dur, + Opts: ctx.startOpts, + } + ctx.reproLogf(3, "bisect: concatenation succeeded") + return res, nil } // Minimize calls and arguments. @@ -580,6 +621,9 @@ func (ctx *context) runOnInstance(callback func(execInterface) (rep *instance.Ru func encodeEntries(entries []*prog.LogEntry) []byte { buf := new(bytes.Buffer) for _, ent := range entries { + if len(ent.P.Calls) > prog.MaxCalls { + panic("prog.MaxCalls is exceeded") + } fmt.Fprintf(buf, "executing program %v:\n%v", ent.Proc, string(ent.P.Serialize())) } return buf.Bytes() diff --git a/pkg/repro/repro_test.go b/pkg/repro/repro_test.go index 3ceacae5f..3945eae0f 100644 --- a/pkg/repro/repro_test.go +++ b/pkg/repro/repro_test.go @@ -259,3 +259,38 @@ func TestTooManyErrors(t *testing.T) { t.Fatalf("expected an error") } } + +func TestProgConcatenation(t *testing.T) { + // Since the crash condition is alarm() after pause(), the code + // would have to work around the prog.MaxCall limitation. + execLog := "2015/12/21 12:18:05 executing program 1:\n" + for i := 0; i < prog.MaxCalls; i++ { + if i == 10 { + execLog += "pause()\n" + } else { + execLog += "getpid()\n" + } + } + execLog += "2015/12/21 12:18:10 executing program 2:\n" + for i := 0; i < prog.MaxCalls; i++ { + if i == 10 { + execLog += "alarm(0xa)\n" + } else { + execLog += "getpid()\n" + } + } + ctx := prepareTestCtx(t, execLog) + go generateTestInstances(ctx, 3, &testExecInterface{ + t: t, + run: testExecRunner, + }) + result, _, err := ctx.run() + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(`pause() +alarm(0xa) +`, string(result.Prog.Serialize())); diff != "" { + t.Fatal(diff) + } +} |
