diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2024-11-05 17:33:59 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2024-11-13 10:32:10 +0000 |
| commit | 5ad1a162959d6df7712dc93cffe128322b11d7af (patch) | |
| tree | c9da9e418df1605fff5eb07d6f90a74a9bee4e6b /pkg/repro | |
| parent | 8acebd3f596375b3c8c34ee8f9000a344064295a (diff) | |
pkg/repro: accept a cancellable context
Refactor pkg/repro to accept a context.Context object. This will make it
look more similar to other package interfaces and will eventually let us
abort currently running repro jobs without having to shut down the whole
application.
Simplify the code by factoring out the parameters common both to RunSyzRepro()
and RunCRepro().
Diffstat (limited to 'pkg/repro')
| -rw-r--r-- | pkg/repro/repro.go | 115 | ||||
| -rw-r--r-- | pkg/repro/repro_test.go | 37 | ||||
| -rw-r--r-- | pkg/repro/strace.go | 11 |
3 files changed, 80 insertions, 83 deletions
diff --git a/pkg/repro/repro.go b/pkg/repro/repro.go index dae7a3cd4..9edacb3be 100644 --- a/pkg/repro/repro.go +++ b/pkg/repro/repro.go @@ -46,6 +46,7 @@ type Stats struct { } type reproContext struct { + ctx context.Context exec execInterface logf func(string, ...interface{}) target *targets.Target @@ -65,42 +66,35 @@ type reproContext struct { // execInterface describes the interfaces needed by pkg/repro. type execInterface interface { - RunCProg(p *prog.Prog, duration time.Duration, opts csource.Options) (*instance.RunResult, error) - RunSyzProg(syzProg []byte, duration time.Duration, opts csource.Options, exitCondition vm.ExitCondition) ( - *instance.RunResult, error) + // Run() will either run a C repro or a syz repro depending on params. + Run(ctx context.Context, params instance.ExecParams, logf instance.ExecutorLogger) (*instance.RunResult, error) } -// The Fast repro mode restricts the repro log bisection, skips multiple simpifications and C repro generation. -var Fast = &struct{}{} +type Environment struct { + Config *mgrconfig.Config + Features flatrpc.Feature + Reporter *report.Reporter + Pool *vm.Dispatcher + // The Fast repro mode restricts the repro log bisection, + // it skips multiple simpifications and C repro generation. + Fast bool +} -func Run(crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature, reporter *report.Reporter, - pool *vm.Dispatcher, opts ...any) (*Result, *Stats, error) { - exec := &poolWrapper{ - cfg: cfg, - reporter: reporter, - pool: pool, - } - ctx, err := prepareCtx(crashLog, cfg, features, reporter, exec) - if err != nil { - return nil, nil, err - } - for _, opt := range opts { - if opt == Fast { - ctx.fast = true - ctx.testTimeouts = []time.Duration{30 * time.Second, 5 * time.Minute} - } - } - exec.logf = ctx.reproLogf - return ctx.run() +func Run(ctx context.Context, log []byte, env Environment) (*Result, *Stats, error) { + return runInner(ctx, log, env.Config, env.Features, env.Reporter, env.Fast, &poolWrapper{ + cfg: env.Config, + reporter: env.Reporter, + pool: env.Pool, + }) } var ErrEmptyCrashLog = errors.New("no programs") -func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature, reporter *report.Reporter, - exec execInterface) (*reproContext, error) { +func runInner(ctx context.Context, crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature, + reporter *report.Reporter, fast bool, exec execInterface) (*Result, *Stats, error) { entries := cfg.Target.ParseLog(crashLog) if len(entries) == 0 { - return nil, fmt.Errorf("log (%d bytes) parse failed: %w", len(crashLog), ErrEmptyCrashLog) + return nil, nil, fmt.Errorf("log (%d bytes) parse failed: %w", len(crashLog), ErrEmptyCrashLog) } crashStart := len(crashLog) crashTitle, crashType := "", crash.UnknownType @@ -130,7 +124,11 @@ func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature case crashType == crash.Hang: testTimeouts = testTimeouts[2:] } - ctx := &reproContext{ + if fast { + testTimeouts = []time.Duration{30 * time.Second, 5 * time.Minute} + } + reproCtx := &reproContext{ + ctx: ctx, exec: exec, target: cfg.SysTarget, crashTitle: crashTitle, @@ -144,8 +142,9 @@ func prepareCtx(crashLog []byte, cfg *mgrconfig.Config, features flatrpc.Feature stats: new(Stats), timeouts: cfg.Timeouts, observedTitles: map[string]bool{}, + fast: fast, } - return ctx, nil + return reproCtx.run() } func (ctx *reproContext) run() (*Result, *Stats, error) { @@ -687,14 +686,22 @@ func (ctx *reproContext) testProgs(entries []*prog.LogEntry, duration time.Durat ctx.reproLogf(2, "testing program (duration=%v, %+v): %s", duration, opts, program) ctx.reproLogf(3, "detailed listing:\n%s", pstr) return ctx.getVerdict(func() (*instance.RunResult, error) { - return ctx.exec.RunSyzProg(pstr, duration, opts, instance.SyzExitConditions) + return ctx.exec.Run(ctx.ctx, instance.ExecParams{ + SyzProg: pstr, + Opts: opts, + Duration: duration, + }, ctx.reproLogf) }, strict) } -func (ctx *reproContext) testCProg(p *prog.Prog, duration time.Duration, - opts csource.Options, strict bool) (ret verdict, err error) { +func (ctx *reproContext) testCProg(p *prog.Prog, duration time.Duration, opts csource.Options, + strict bool) (ret verdict, err error) { return ctx.getVerdict(func() (*instance.RunResult, error) { - return ctx.exec.RunCProg(p, duration, opts) + return ctx.exec.Run(ctx.ctx, instance.ExecParams{ + CProg: p, + Opts: opts, + Duration: duration, + }, ctx.reproLogf) }, strict) } @@ -748,43 +755,37 @@ type poolWrapper struct { cfg *mgrconfig.Config reporter *report.Reporter pool *vm.Dispatcher - logf func(level int, format string, args ...interface{}) } -func (pw *poolWrapper) RunCProg(p *prog.Prog, duration time.Duration, - opts csource.Options) (*instance.RunResult, error) { - var result *instance.RunResult - var err error - pw.pool.Run(func(ctx context.Context, inst *vm.Instance, updInfo dispatcher.UpdateInfo) { - updInfo(func(info *dispatcher.Info) { - info.Status = fmt.Sprintf("reproducing (C, %.1f min)", duration.Minutes()) - }) - var ret *instance.ExecProgInstance - ret, err = instance.SetupExecProg(inst, pw.cfg, pw.reporter, - &instance.OptionalConfig{Logf: pw.logf}) - if err != nil { - return - } - result, err = ret.RunCProg(p, duration, opts) - }) - return result, err -} +func (pw *poolWrapper) Run(ctx context.Context, params instance.ExecParams, + logf instance.ExecutorLogger) (*instance.RunResult, error) { + if err := ctx.Err(); err != nil { + // Note that we could also propagate ctx down to SetupExecProg() and RunCProg() operations, + // but so far it does not seem to be worth the effort. + return nil, err + } -func (pw *poolWrapper) RunSyzProg(syzProg []byte, duration time.Duration, - opts csource.Options, exitCondition vm.ExitCondition) (*instance.RunResult, error) { var result *instance.RunResult var err error pw.pool.Run(func(ctx context.Context, inst *vm.Instance, updInfo dispatcher.UpdateInfo) { updInfo(func(info *dispatcher.Info) { - info.Status = fmt.Sprintf("reproducing (syz, %.1f min)", duration.Minutes()) + typ := "syz" + if params.CProg != nil { + typ = "C" + } + info.Status = fmt.Sprintf("reproducing (%s, %.1f min)", typ, params.Duration.Minutes()) }) var ret *instance.ExecProgInstance ret, err = instance.SetupExecProg(inst, pw.cfg, pw.reporter, - &instance.OptionalConfig{Logf: pw.logf}) + &instance.OptionalConfig{Logf: logf}) if err != nil { return } - result, err = ret.RunSyzProg(syzProg, duration, opts, exitCondition) + if params.CProg != nil { + result, err = ret.RunCProg(params) + } else { + result, err = ret.RunSyzProg(params) + } }) return result, err } diff --git a/pkg/repro/repro_test.go b/pkg/repro/repro_test.go index 5a378b610..dce8845ff 100644 --- a/pkg/repro/repro_test.go +++ b/pkg/repro/repro_test.go @@ -4,11 +4,11 @@ package repro import ( + "context" "fmt" "math/rand" "regexp" "testing" - "time" "github.com/google/go-cmp/cmp" "github.com/google/syzkaller/pkg/csource" @@ -19,7 +19,6 @@ import ( "github.com/google/syzkaller/pkg/testutil" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" - "github.com/google/syzkaller/vm" ) func initTest(t *testing.T) (*rand.Rand, int) { @@ -113,17 +112,16 @@ type testExecInterface struct { run func([]byte) (*instance.RunResult, error) } -func (tei *testExecInterface) RunCProg(p *prog.Prog, duration time.Duration, - opts csource.Options) (*instance.RunResult, error) { - return tei.RunSyzProg(p.Serialize(), duration, opts, instance.SyzExitConditions) -} - -func (tei *testExecInterface) RunSyzProg(syzProg []byte, duration time.Duration, - opts csource.Options, exitCondition vm.ExitCondition) (*instance.RunResult, error) { +func (tei *testExecInterface) Run(_ context.Context, params instance.ExecParams, + _ instance.ExecutorLogger) (*instance.RunResult, error) { + syzProg := params.SyzProg + if params.CProg != nil { + syzProg = params.CProg.Serialize() + } return tei.run(syzProg) } -func prepareTestCtx(t *testing.T, log string, exec execInterface) *reproContext { +func runTestRepro(t *testing.T, log string, exec execInterface) (*Result, *Stats, error) { mgrConfig := &mgrconfig.Config{ Derived: mgrconfig.Derived{ TargetOS: targets.Linux, @@ -140,11 +138,8 @@ func prepareTestCtx(t *testing.T, log string, exec execInterface) *reproContext if err != nil { t.Fatal(err) } - ctx, err := prepareCtx([]byte(log), mgrConfig, flatrpc.AllFeatures, reporter, exec) - if err != nil { - t.Fatal(err) - } - return ctx + return runInner(context.Background(), []byte(log), mgrConfig, + flatrpc.AllFeatures, reporter, false, exec) } const testReproLog = ` @@ -180,10 +175,9 @@ func testExecRunner(log []byte) (*instance.RunResult, error) { // Just a pkg/repro smoke test: check that we can extract a two-call reproducer. // No focus on error handling and minor corner cases. func TestPlainRepro(t *testing.T) { - ctx := prepareTestCtx(t, testReproLog, &testExecInterface{ + result, _, err := runTestRepro(t, testReproLog, &testExecInterface{ run: testExecRunner, }) - result, _, err := ctx.run() if err != nil { t.Fatal(err) } @@ -198,7 +192,7 @@ alarm(0xa) // Ensure that the code just retries. func TestVMErrorResilience(t *testing.T) { fail := false - ctx := prepareTestCtx(t, testReproLog, &testExecInterface{ + result, _, err := runTestRepro(t, testReproLog, &testExecInterface{ run: func(log []byte) (*instance.RunResult, error) { fail = !fail if fail { @@ -207,7 +201,6 @@ func TestVMErrorResilience(t *testing.T) { return testExecRunner(log) }, }) - result, _, err := ctx.run() if err != nil { t.Fatal(err) } @@ -220,7 +213,7 @@ alarm(0xa) func TestTooManyErrors(t *testing.T) { counter := 0 - ctx := prepareTestCtx(t, testReproLog, &testExecInterface{ + _, _, err := runTestRepro(t, testReproLog, &testExecInterface{ run: func(log []byte) (*instance.RunResult, error) { counter++ if counter%4 != 0 { @@ -229,7 +222,6 @@ func TestTooManyErrors(t *testing.T) { return testExecRunner(log) }, }) - _, _, err := ctx.run() if err == nil { t.Fatalf("expected an error") } @@ -254,10 +246,9 @@ func TestProgConcatenation(t *testing.T) { execLog += "getpid()\n" } } - ctx := prepareTestCtx(t, execLog, &testExecInterface{ + result, _, err := runTestRepro(t, execLog, &testExecInterface{ run: testExecRunner, }) - result, _, err := ctx.run() if err != nil { t.Fatal(err) } diff --git a/pkg/repro/strace.go b/pkg/repro/strace.go index 9aaeb6498..e101945cf 100644 --- a/pkg/repro/strace.go +++ b/pkg/repro/strace.go @@ -44,13 +44,18 @@ func RunStrace(result *Result, cfg *mgrconfig.Config, reporter *report.Reporter, err = fmt.Errorf("failed to set up instance: %w", setupErr) return } + params := instance.ExecParams{ + Opts: result.Opts, + Duration: result.Duration, + } if result.CRepro { log.Logf(1, "running C repro under strace") - runRes, err = ret.RunCProg(result.Prog, result.Duration, result.Opts) + params.CProg = result.Prog + runRes, err = ret.RunCProg(params) } else { log.Logf(1, "running syz repro under strace") - runRes, err = ret.RunSyzProg(result.Prog.Serialize(), result.Duration, - result.Opts, instance.SyzExitConditions) + params.SyzProg = result.Prog.Serialize() + runRes, err = ret.RunSyzProg(params) } }) if err != nil { |
