blob: cad34fa06257b4a4d62fef478f2f1fd8bf26972d [file] [log] [blame]
// Copyright 2015 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// execprog executes a single program or a set of programs
// and optinally prints information about execution.
package main
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"io/ioutil"
"os"
"runtime"
"sync"
"time"
"github.com/google/syzkaller/pkg/cover"
"github.com/google/syzkaller/pkg/ipc"
. "github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/prog"
_ "github.com/google/syzkaller/sys"
)
var (
flagOS = flag.String("os", runtime.GOOS, "target os")
flagArch = flag.String("arch", runtime.GOARCH, "target arch")
flagExecutor = flag.String("executor", "./syz-executor", "path to executor binary")
flagCoverFile = flag.String("coverfile", "", "write coverage to the file")
flagRepeat = flag.Int("repeat", 1, "repeat execution that many times (0 for infinite loop)")
flagProcs = flag.Int("procs", 1, "number of parallel processes to execute programs")
flagOutput = flag.String("output", "none", "write programs to none/stdout")
flagFaultCall = flag.Int("fault_call", -1, "inject fault into this call (0-based)")
flagFaultNth = flag.Int("fault_nth", 0, "inject fault on n-th operation (0-based)")
flagHints = flag.Bool("hints", false, "do a hints-generation run")
)
func main() {
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Fprintf(os.Stderr, "usage: execprog [flags] file-with-programs+\n")
flag.PrintDefaults()
os.Exit(1)
}
target, err := prog.GetTarget(*flagOS, *flagArch)
if err != nil {
Fatalf("%v", err)
}
var entries []*prog.LogEntry
for _, fn := range flag.Args() {
data, err := ioutil.ReadFile(fn)
if err != nil {
Fatalf("failed to read log file: %v", err)
}
entries = target.ParseLog(data)
}
Logf(0, "parsed %v programs", len(entries))
if len(entries) == 0 {
return
}
execOpts := &ipc.ExecOpts{}
config, err := ipc.DefaultConfig()
if err != nil {
Fatalf("%v", err)
}
if config.Flags&ipc.FlagSignal != 0 {
execOpts.Flags |= ipc.FlagCollectCover
}
execOpts.Flags |= ipc.FlagDedupCover
if *flagCoverFile != "" {
config.Flags |= ipc.FlagSignal
execOpts.Flags |= ipc.FlagCollectCover
execOpts.Flags &^= ipc.FlagDedupCover
}
if *flagHints {
if execOpts.Flags&ipc.FlagCollectCover != 0 {
execOpts.Flags ^= ipc.FlagCollectCover
}
execOpts.Flags |= ipc.FlagCollectComps
}
if *flagFaultCall >= 0 {
config.Flags |= ipc.FlagEnableFault
execOpts.Flags |= ipc.FlagInjectFault
execOpts.FaultCall = *flagFaultCall
execOpts.FaultNth = *flagFaultNth
}
handled := make(map[string]bool)
for _, entry := range entries {
for _, call := range entry.P.Calls {
handled[call.Meta.CallName] = true
}
}
if handled["syz_emit_ethernet"] || handled["syz_extract_tcp_res"] {
config.Flags |= ipc.FlagEnableTun
}
var wg sync.WaitGroup
wg.Add(*flagProcs)
var posMu, logMu sync.Mutex
gate := ipc.NewGate(2**flagProcs, nil)
var pos int
var lastPrint time.Time
shutdown := make(chan struct{})
for p := 0; p < *flagProcs; p++ {
pid := p
go func() {
defer wg.Done()
env, err := ipc.MakeEnv(*flagExecutor, pid, config)
if err != nil {
Fatalf("failed to create ipc env: %v", err)
}
defer env.Close()
for {
if !func() bool {
// Limit concurrency window.
ticket := gate.Enter()
defer gate.Leave(ticket)
posMu.Lock()
idx := pos
pos++
if idx%len(entries) == 0 && time.Since(lastPrint) > 5*time.Second {
Logf(0, "executed programs: %v", idx)
lastPrint = time.Now()
}
posMu.Unlock()
if *flagRepeat > 0 && idx >= len(entries)**flagRepeat {
return false
}
entry := entries[idx%len(entries)]
callOpts := execOpts
if *flagFaultCall == -1 && entry.Fault {
newOpts := *execOpts
newOpts.Flags |= ipc.FlagInjectFault
newOpts.FaultCall = entry.FaultCall
newOpts.FaultNth = entry.FaultNth
callOpts = &newOpts
}
switch *flagOutput {
case "stdout":
strOpts := ""
if callOpts.Flags&ipc.FlagInjectFault != 0 {
strOpts = fmt.Sprintf(" (fault-call:%v fault-nth:%v)", callOpts.FaultCall, callOpts.FaultNth)
}
data := entry.P.Serialize()
logMu.Lock()
Logf(0, "executing program %v%v:\n%s", pid, strOpts, data)
logMu.Unlock()
}
output, info, failed, hanged, err := env.Exec(callOpts, entry.P)
select {
case <-shutdown:
return false
default:
}
if failed {
fmt.Printf("BUG: executor-detected bug:\n%s", output)
}
if config.Flags&ipc.FlagDebug != 0 || err != nil {
fmt.Printf("result: failed=%v hanged=%v err=%v\n\n%s", failed, hanged, err, output)
}
if *flagCoverFile != "" {
// Coverage is dumped in sanitizer format.
// github.com/google/sanitizers/tools/sancov command can be used to dump PCs,
// then they can be piped via addr2line to symbolize.
for i, inf := range info {
fmt.Printf("call #%v: signal %v, coverage %v\n", i, len(inf.Signal), len(inf.Cover))
if len(inf.Cover) == 0 {
continue
}
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, uint64(0xC0BFFFFFFFFFFF64))
for _, pc := range inf.Cover {
binary.Write(buf, binary.LittleEndian, cover.RestorePC(pc, 0xffffffff))
}
err := osutil.WriteFile(fmt.Sprintf("%v.%v", *flagCoverFile, i), buf.Bytes())
if err != nil {
Fatalf("failed to write coverage file: %v", err)
}
}
}
if *flagHints {
compMaps := ipc.GetCompMaps(info)
ncomps, ncandidates := 0, 0
for i := range entry.P.Calls {
if *flagOutput == "stdout" {
fmt.Printf("call %v:\n", i)
}
comps := compMaps[i]
for v, args := range comps {
ncomps += len(args)
if *flagOutput == "stdout" {
fmt.Printf("comp 0x%x:", v)
for arg := range args {
fmt.Printf(" 0x%x", arg)
}
fmt.Printf("\n")
}
}
entry.P.MutateWithHints(i, comps, func(p *prog.Prog) {
ncandidates++
if *flagOutput == "stdout" {
fmt.Printf("PROGRAM:\n%s\n", p.Serialize())
}
})
}
fmt.Printf("ncomps=%v ncandidates=%v\n", ncomps, ncandidates)
}
return true
}() {
return
}
}
}()
}
osutil.HandleInterrupts(shutdown)
wg.Wait()
}