diff options
| author | Andrey Konovalov <andreyknvl@gmail.com> | 2017-06-21 19:39:57 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-06-21 19:39:57 +0200 |
| commit | 268bf907e11c005b8ccc4e2e19bd1819f49b82e4 (patch) | |
| tree | b56d727cb784efce90141ea202c49be2296dc20f /pkg | |
| parent | f1a56fe3b65b27dda453100efec7528146f12121 (diff) | |
| parent | 81990cb63316391a226e92ecda9e09dbe3bc8e1e (diff) | |
Merge pull request #239 from xairy/up-better-repro
Minor reproducing improvements
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/repro/repro.go | 150 |
1 files changed, 102 insertions, 48 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go index 93ecc24ef..111ae42ed 100644 --- a/pkg/repro/repro.go +++ b/pkg/repro/repro.go @@ -21,9 +21,10 @@ import ( ) type Result struct { - Prog *prog.Prog - Opts csource.Options - CRepro bool + Prog *prog.Prog + Duration time.Duration + Opts csource.Options + CRepro bool } type context struct { @@ -40,6 +41,14 @@ type instance struct { executorBin string } +func reverseEntries(entries []*prog.LogEntry) []*prog.LogEntry { + last := len(entries) - 1 + for i := 0; i < len(entries)/2; i++ { + entries[i], entries[last-i] = entries[last-i], entries[i] + } + return entries +} + func Run(crashLog []byte, cfg *mgrconfig.Config, vmPool *vm.Pool, vmIndexes []int) (*Result, error) { if len(vmIndexes) == 0 { return nil, fmt.Errorf("no VMs provided") @@ -127,14 +136,9 @@ func Run(crashLog []byte, cfg *mgrconfig.Config, vmPool *vm.Pool, vmIndexes []in return res, err } -func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, error) { - // Cut programs that were executed after crash. - for i, ent := range entries { - if ent.Start > crashStart { - entries = entries[:i] - break - } - } +func (ctx *context) reproExtractProg(entries []*prog.LogEntry) (*Result, error) { + Logf(2, "reproducing crash '%v': suspecting %v programs", ctx.crashDesc, len(entries)) + // Extract last program on every proc. procs := make(map[int]int) for i, ent := range entries { @@ -145,11 +149,11 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er indices = append(indices, idx) } sort.Ints(indices) - var suspected []*prog.LogEntry + var lastEntries []*prog.LogEntry for i := len(indices) - 1; i >= 0; i-- { - suspected = append(suspected, entries[indices[i]]) + lastEntries = append(lastEntries, entries[indices[i]]) } - Logf(2, "reproducing crash '%v': suspecting %v programs", ctx.crashDesc, len(suspected)) + opts := csource.Options{ Threaded: true, Collide: true, @@ -163,15 +167,18 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er Debug: true, Repro: true, } + // Execute the suspected programs. // We first try to execute each program for 10 seconds, that should detect simple crashes // (i.e. no races and no hangs). Then we execute each program for 5 minutes // to catch races and hangs. Note that the max duration must be larger than // hang/no output detection duration in vm.MonitorExecution, which is currently set to 3 mins. + // Programs are executed in reverse order, usually the last program is the guilty one. + durations := []time.Duration{10 * time.Second, 5 * time.Minute} + suspected := [][]*prog.LogEntry{reverseEntries(entries), reverseEntries(lastEntries)} var res *Result - var duration time.Duration - for _, dur := range []time.Duration{10 * time.Second, 5 * time.Minute} { - for _, ent := range suspected { + for i, dur := range durations { + for _, ent := range suspected[i] { opts.Fault = ent.Fault opts.FaultCall = ent.FaultCall opts.FaultNth = ent.FaultNth @@ -184,10 +191,10 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } if crashed { res = &Result{ - Prog: ent.P, - Opts: opts, + Prog: ent.P, + Duration: dur * 3 / 2, + Opts: opts, } - duration = dur * 3 / 2 break } } @@ -199,17 +206,20 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er Logf(0, "reproducing crash '%v': no program crashed", ctx.crashDesc) return nil, nil } - defer func() { - res.Opts.Repro = false - }() + return res, nil +} + +func (ctx *context) reproMinimizeProg(res *Result) (*Result, error) { Logf(2, "reproducing crash '%v': minimizing guilty program", ctx.crashDesc) + + // Minimize calls and arguments. call := -1 if res.Opts.Fault { call = res.Opts.FaultCall } res.Prog, res.Opts.FaultCall = prog.Minimize(res.Prog, call, func(p1 *prog.Prog, callIndex int) bool { - crashed, err := ctx.testProg(p1, duration, res.Opts) + crashed, err := ctx.testProg(p1, res.Duration, res.Opts) if err != nil { Logf(1, "reproducing crash '%v': minimization failed with %v", ctx.crashDesc, err) return false @@ -217,17 +227,17 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er return crashed }, true) - // Try to "minimize" threaded/collide/sandbox/etc to find simpler reproducer. - opts = res.Opts + // Minimize repro options (threaded, collide, sandbox, etc). + opts := res.Opts opts.Collide = false - crashed, err := ctx.testProg(res.Prog, duration, opts) + crashed, err := ctx.testProg(res.Prog, res.Duration, opts) if err != nil { return res, err } if crashed { res.Opts = opts opts.Threaded = false - crashed, err := ctx.testProg(res.Prog, duration, opts) + crashed, err := ctx.testProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -238,7 +248,7 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er if res.Opts.Sandbox == "namespace" { opts = res.Opts opts.Sandbox = "none" - crashed, err := ctx.testProg(res.Prog, duration, opts) + crashed, err := ctx.testProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -249,7 +259,7 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er if res.Opts.Procs > 1 { opts = res.Opts opts.Procs = 1 - crashed, err := ctx.testProg(res.Prog, duration, opts) + crashed, err := ctx.testProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -260,7 +270,7 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er if res.Opts.Repeat { opts = res.Opts opts.Repeat = false - crashed, err := ctx.testProg(res.Prog, duration, opts) + crashed, err := ctx.testProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -269,21 +279,29 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } } + return res, nil +} + +func (ctx *context) reproExtractC(res *Result) (*Result, error) { + Logf(2, "reproducing crash '%v': extracting C reproducer", ctx.crashDesc) + // Try triggering crash with a C reproducer. - crashed, err = ctx.testCProg(res.Prog, duration, res.Opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, res.Opts) if err != nil { return res, err } res.CRepro = crashed - if !crashed { - return res, nil - } + return res, nil +} + +func (ctx *context) reproMinimizeC(res *Result) (*Result, error) { + Logf(2, "reproducing crash '%v': minimizing C reproducer", ctx.crashDesc) // Try to simplify the C reproducer. if res.Opts.EnableTun { - opts = res.Opts + opts := res.Opts opts.EnableTun = false - crashed, err := ctx.testCProg(res.Prog, duration, opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -292,9 +310,9 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } } if res.Opts.Sandbox != "" { - opts = res.Opts + opts := res.Opts opts.Sandbox = "" - crashed, err := ctx.testCProg(res.Prog, duration, opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -303,9 +321,9 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } } if res.Opts.UseTmpDir { - opts = res.Opts + opts := res.Opts opts.UseTmpDir = false - crashed, err := ctx.testCProg(res.Prog, duration, opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -314,9 +332,9 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } } if res.Opts.HandleSegv { - opts = res.Opts + opts := res.Opts opts.HandleSegv = false - crashed, err := ctx.testCProg(res.Prog, duration, opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -325,9 +343,9 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } } if res.Opts.WaitRepeat { - opts = res.Opts + opts := res.Opts opts.WaitRepeat = false - crashed, err := ctx.testCProg(res.Prog, duration, opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -336,9 +354,9 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er } } if res.Opts.Debug { - opts = res.Opts + opts := res.Opts opts.Debug = false - crashed, err := ctx.testCProg(res.Prog, duration, opts) + crashed, err := ctx.testCProg(res.Prog, res.Duration, opts) if err != nil { return res, err } @@ -350,6 +368,43 @@ func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, er return res, nil } +func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, error) { + // Cut programs that were executed after crash. + for i, ent := range entries { + if ent.Start > crashStart { + entries = entries[:i] + break + } + } + + res, err := ctx.reproExtractProg(entries) + if err != nil { + return res, err + } + + res, err = ctx.reproMinimizeProg(res) + if err != nil { + return res, err + } + + res, err = ctx.reproExtractC(res) + if err != nil { + return res, err + } + if !res.CRepro { + res.Opts.Repro = false + return res, nil + } + + res, err = ctx.reproMinimizeC(res) + if err != nil { + return res, err + } + + res.Opts.Repro = false + return res, nil +} + func (ctx *context) testProg(p *prog.Prog, duration time.Duration, opts csource.Options) (crashed bool, err error) { inst := <-ctx.instances if inst == nil { @@ -416,7 +471,6 @@ func (ctx *context) testBin(bin string, duration time.Duration) (crashed bool, e if err != nil { return false, fmt.Errorf("failed to copy to VM: %v", err) } - Logf(2, "reproducing crash '%v': testing compiled C program", ctx.crashDesc) return ctx.testImpl(inst.Instance, bin, duration) } |
