Skip to content

Commit

Permalink
pkg/runtest: use queue.Request and queue.Result
Browse files Browse the repository at this point in the history
There's no need to duplicate the execution mechanisms.
  • Loading branch information
a-nogikh committed May 7, 2024
1 parent 2d4d4d3 commit ea47191
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 96 deletions.
11 changes: 10 additions & 1 deletion pkg/fuzzer/queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ type Request struct {
NeedSignal SignalType
NeedCover bool
NeedHints bool
ExecOpts *ipc.ExecOpts
// TODO: passing ExecOpts directly is actually quite hacky -- this is a high-level interface
// and it should specify what is needed in high-level terms.
ExecOpts *ipc.ExecOpts

// If specified, the resulting signal for call SignalFilterCall
// will include subset of it even if it's not new.
Expand All @@ -29,6 +31,11 @@ type Request struct {
// This stat will be incremented on request completion.
Stat *stats.Val

// Options needed by runtest.
BinaryFile string // If set, it's executed instead of Prog.
Repeat int // Repeats in addition to the first run.
ReturnOutput bool

// The callback will be called on request completion in the LIFO order.
// If it returns false, all further processing will be stopped.
// It allows wrappers to intercept Done() requests.
Expand Down Expand Up @@ -91,7 +98,9 @@ const (

type Result struct {
Info *ipc.ProgInfo
Output []byte
Status Status
Err error // More details in case of ExecFailure.
}

func (r *Result) Stop() bool {
Expand Down
123 changes: 60 additions & 63 deletions pkg/runtest/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,30 @@ import (

"github.com/google/syzkaller/pkg/csource"
"github.com/google/syzkaller/pkg/flatrpc"
"github.com/google/syzkaller/pkg/fuzzer/queue"
"github.com/google/syzkaller/pkg/ipc"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys/targets"
)

type RunRequest struct {
Bin string
P *prog.Prog
Opts ipc.ExecOpts
Repeat int

Done chan struct{}
Output []byte
Info ipc.ProgInfo
Err error
type runRequest struct {
*queue.Request

err error
finished chan struct{}
results *ipc.ProgInfo
name string
broken string
skip string
result *queue.Result
results *ipc.ProgInfo // the expected results

name string
broken string
skip string
}

type Context struct {
Dir string
Target *prog.Target
Features flatrpc.Feature
EnabledCalls map[string]map[*prog.Syscall]bool
Requests chan *RunRequest
LogFunc func(text string)
Retries int // max number of test retries to deal with flaky tests
Verbose bool
Expand All @@ -64,18 +59,17 @@ func (ctx *Context) log(msg string, args ...interface{}) {
ctx.LogFunc(fmt.Sprintf(msg, args...))
}

func (ctx *Context) Run() error {
defer close(ctx.Requests)
func (ctx *Context) Run(execute func(*queue.Request) *queue.Result) error {
if ctx.Retries%2 == 0 {
ctx.Retries++
}
progs := make(chan *RunRequest, 1000+2*cap(ctx.Requests))
progs := make(chan *runRequest, 1000)
errc := make(chan error, 1)
go func() {
defer close(progs)
errc <- ctx.generatePrograms(progs)
}()
var requests []*RunRequest
var requests []*runRequest
for req := range progs {
req := req
requests = append(requests, req)
Expand All @@ -94,12 +88,9 @@ func (ctx *Context) Run() error {
// In the best case this allows to get off with just 1 test run.
var resultErr error
for try, failed := 0, 0; try < ctx.Retries; try++ {
req.Output = nil
req.Info = ipc.ProgInfo{}
req.Done = make(chan struct{})
ctx.Requests <- req
<-req.Done
if req.Err != nil {
req.result = execute(req.Request)
if req.result.Err != nil {
resultErr = req.result.Err
break
}
err := checkResult(req)
Expand All @@ -112,9 +103,7 @@ func (ctx *Context) Run() error {
break
}
}
if req.Err == nil {
req.Err = resultErr
}
req.err = resultErr
close(req.finished)
}()
}
Expand All @@ -132,13 +121,14 @@ func (ctx *Context) Run() error {
verbose = true
} else {
<-req.finished
if req.Err != nil {
if req.err != nil {
fail++
result = fmt.Sprintf("FAIL: %v",
strings.Replace(req.Err.Error(), "\n", "\n\t", -1))
if len(req.Output) != 0 {
strings.Replace(req.err.Error(), "\n", "\n\t", -1))
res := req.result
if len(res.Output) != 0 {
result += fmt.Sprintf("\n\t%s",
strings.Replace(string(req.Output), "\n", "\n\t", -1))
strings.Replace(string(res.Output), "\n", "\n\t", -1))
}
} else {
ok++
Expand All @@ -148,8 +138,8 @@ func (ctx *Context) Run() error {
if !verbose || ctx.Verbose {
ctx.log("%-38v: %v", req.name, result)
}
if req.Bin != "" {
os.Remove(req.Bin)
if req.Request != nil && req.Request.BinaryFile != "" {
os.Remove(req.BinaryFile)
}
}
if err := <-errc; err != nil {
Expand All @@ -162,7 +152,7 @@ func (ctx *Context) Run() error {
return nil
}

func (ctx *Context) generatePrograms(progs chan *RunRequest) error {
func (ctx *Context) generatePrograms(progs chan *runRequest) error {
cover := []bool{false}
if ctx.Features&flatrpc.FeatureCoverage != 0 {
cover = append(cover, true)
Expand Down Expand Up @@ -201,7 +191,7 @@ func progFileList(dir, filter string) ([]string, error) {
return res, nil
}

func (ctx *Context) generateFile(progs chan *RunRequest, sandboxes []string, cover []bool, filename string) error {
func (ctx *Context) generateFile(progs chan *runRequest, sandboxes []string, cover []bool, filename string) error {
p, requires, results, err := parseProg(ctx.Target, ctx.Dir, filename)
if err != nil {
return err
Expand All @@ -215,7 +205,7 @@ nextSandbox:
name := fmt.Sprintf("%v %v", filename, sandbox)
for _, call := range p.Calls {
if !ctx.EnabledCalls[sandbox][call.Meta] {
progs <- &RunRequest{
progs <- &runRequest{
name: name,
skip: fmt.Sprintf("unsupported call %v", call.Meta.Name),
}
Expand Down Expand Up @@ -267,7 +257,7 @@ nextSandbox:
name += " C"
if !sysTarget.ExecutorUsesForkServer && times > 1 {
// Non-fork loop implementation does not support repetition.
progs <- &RunRequest{
progs <- &runRequest{
name: name,
broken: "non-forking loop",
}
Expand Down Expand Up @@ -378,7 +368,7 @@ func checkArch(requires map[string]bool, arch string) bool {
return true
}

func (ctx *Context) produceTest(progs chan *RunRequest, req *RunRequest, name string,
func (ctx *Context) produceTest(progs chan *runRequest, req *runRequest, name string,
properties, requires map[string]bool, results *ipc.ProgInfo) {
req.name = name
req.results = results
Expand Down Expand Up @@ -409,7 +399,7 @@ func match(props, requires map[string]bool) bool {
return true
}

func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bool, times int) (*RunRequest, error) {
func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bool, times int) (*runRequest, error) {
var opts ipc.ExecOpts
sandboxFlags, err := ipc.SandboxToFlags(sandbox)
if err != nil {
Expand All @@ -428,15 +418,17 @@ func (ctx *Context) createSyzTest(p *prog.Prog, sandbox string, threaded, cov bo
if ctx.Debug {
opts.EnvFlags |= ipc.FlagDebug
}
req := &RunRequest{
P: p,
Opts: opts,
Repeat: times,
req := &runRequest{
Request: &queue.Request{
Prog: p,
ExecOpts: &opts,
Repeat: times,
},
}
return req, nil
}

func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, times int) (*RunRequest, error) {
func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, times int) (*runRequest, error) {
opts := csource.Options{
Threaded: threaded,
Repeat: times > 1,
Expand Down Expand Up @@ -479,29 +471,34 @@ func (ctx *Context) createCTest(p *prog.Prog, sandbox string, threaded bool, tim
if threaded {
ipcFlags |= ipc.FlagThreaded
}
req := &RunRequest{
P: p,
Bin: bin,
Opts: ipc.ExecOpts{
ExecFlags: ipcFlags,
req := &runRequest{
Request: &queue.Request{
Prog: p,
BinaryFile: bin,
ExecOpts: &ipc.ExecOpts{
ExecFlags: ipcFlags,
},
Repeat: times,
},
Repeat: times,
}
return req, nil
}

func checkResult(req *RunRequest) error {
func checkResult(req *runRequest) error {
if req.result.Status != queue.Success {
return fmt.Errorf("non-successful result status (%v)", req.result.Status)
}
var infos []ipc.ProgInfo
isC := req.Bin != ""
isC := req.BinaryFile != ""
if isC {
var err error
if infos, err = parseBinOutput(req); err != nil {
return err
}
} else {
raw := req.Info
raw := req.result.Info
for len(raw.Calls) != 0 {
ncalls := min(len(raw.Calls), len(req.P.Calls))
ncalls := min(len(raw.Calls), len(req.Prog.Calls))
infos = append(infos, ipc.ProgInfo{
Extra: raw.Extra,
Calls: raw.Calls[:ncalls],
Expand All @@ -511,7 +508,7 @@ func checkResult(req *RunRequest) error {
}
if req.Repeat != len(infos) {
return fmt.Errorf("should repeat %v times, but repeated %v, prog calls %v, info calls %v\n%s",
req.Repeat, len(infos), req.P.Calls, len(req.Info.Calls), req.Output)
req.Repeat, len(infos), req.Prog.Calls, len(req.result.Info.Calls), req.result.Output)
}
calls := make(map[string]bool)
for run, info := range infos {
Expand All @@ -524,7 +521,7 @@ func checkResult(req *RunRequest) error {
return nil
}

func checkCallResult(req *RunRequest, isC bool, run, call int, info ipc.ProgInfo, calls map[string]bool) error {
func checkCallResult(req *runRequest, isC bool, run, call int, info ipc.ProgInfo, calls map[string]bool) error {
inf := info.Calls[call]
want := req.results.Calls[call]
for flag, what := range map[ipc.CallFlags]string{
Expand All @@ -537,7 +534,7 @@ func checkCallResult(req *RunRequest, isC bool, run, call int, info ipc.ProgInfo
// C code does not detect blocked/non-finished calls.
continue
}
if req.Opts.ExecFlags&ipc.FlagThreaded == 0 {
if req.ExecOpts.ExecFlags&ipc.FlagThreaded == 0 {
// In non-threaded mode blocked syscalls will block main thread
// and we won't detect blocked/unfinished syscalls.
continue
Expand All @@ -563,12 +560,12 @@ func checkCallResult(req *RunRequest, isC bool, run, call int, info ipc.ProgInfo
if isC || inf.Flags&ipc.CallExecuted == 0 {
return nil
}
if req.Opts.EnvFlags&ipc.FlagSignal != 0 {
if req.ExecOpts.EnvFlags&ipc.FlagSignal != 0 {
// Signal is always deduplicated, so we may not get any signal
// on a second invocation of the same syscall.
// For calls that are not meant to collect synchronous coverage we
// allow the signal to be empty as long as the extra signal is not.
callName := req.P.Calls[call].Meta.CallName
callName := req.Prog.Calls[call].Meta.CallName
if len(inf.Signal) < 2 && !calls[callName] && len(info.Extra.Signal) == 0 {
return fmt.Errorf("run %v: call %v: no signal", run, call)
}
Expand All @@ -586,13 +583,13 @@ func checkCallResult(req *RunRequest, isC bool, run, call int, info ipc.ProgInfo
return nil
}

func parseBinOutput(req *RunRequest) ([]ipc.ProgInfo, error) {
func parseBinOutput(req *runRequest) ([]ipc.ProgInfo, error) {
var infos []ipc.ProgInfo
s := bufio.NewScanner(bytes.NewReader(req.Output))
s := bufio.NewScanner(bytes.NewReader(req.result.Output))
re := regexp.MustCompile("^### call=([0-9]+) errno=([0-9]+)$")
for s.Scan() {
if s.Text() == "### start" {
infos = append(infos, ipc.ProgInfo{Calls: make([]ipc.CallInfo, len(req.P.Calls))})
infos = append(infos, ipc.ProgInfo{Calls: make([]ipc.CallInfo, len(req.Prog.Calls))})
}
match := re.FindSubmatch(s.Bytes())
if match == nil {
Expand Down

0 comments on commit ea47191

Please sign in to comment.