// Copyright 2016 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.
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"github.com/google/syzkaller/pkg/ast"
"github.com/google/syzkaller/pkg/compiler"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/sys/targets"
)
var (
flagOS = flag.String("os", "", "target OS")
flagBuild = flag.Bool("build", false, "regenerate arch-specific kernel headers")
flagSourceDir = flag.String("sourcedir", "", "path to kernel source checkout dir")
flagBuildDir = flag.String("builddir", "", "path to kernel build dir")
flagArch = flag.String("arch", "", "comma-separated list of arches to generate (all by default)")
)
type Arch struct {
target *targets.Target
sourceDir string
buildDir string
build bool
files []*File
err error
done chan bool
}
type File struct {
arch *Arch
name string
consts map[string]uint64
undeclared map[string]bool
info *compiler.ConstInfo
err error
done chan bool
}
type Extractor interface {
prepare(sourcedir string, build bool, arches []string) error
prepareArch(arch *Arch) error
processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error)
}
var extractors = map[string]Extractor{
"akaros": new(akaros),
"linux": new(linux),
"freebsd": new(freebsd),
"netbsd": new(netbsd),
"android": new(linux),
"fuchsia": new(fuchsia),
"windows": new(windows),
}
func main() {
failf := func(msg string, args ...interface{}) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}
flag.Parse()
if *flagBuild && *flagBuildDir != "" {
failf("-build and -builddir is an invalid combination")
}
OS, archArray, files, err := archFileList(*flagOS, *flagArch, flag.Args())
if err != nil {
failf("%v", err)
}
extractor := extractors[OS]
if extractor == nil {
failf("unknown os: %v", OS)
}
if err := extractor.prepare(*flagSourceDir, *flagBuild, archArray); err != nil {
failf("%v", err)
}
arches, err := createArches(OS, archArray, files)
if err != nil {
failf("%v", err)
}
jobC := make(chan interface{}, len(archArray)*len(files))
for _, arch := range arches {
jobC <- arch
}
for p := 0; p < runtime.GOMAXPROCS(0); p++ {
go func() {
for job := range jobC {
switch j := job.(type) {
case *Arch:
infos, err := processArch(extractor, j)
j.err = err
close(j.done)
if j.err == nil {
for _, f := range j.files {
f.info = infos[f.name]
jobC <- f
}
}
case *File:
j.consts, j.undeclared, j.err = processFile(extractor, j.arch, j)
close(j.done)
}
}
}()
}
failed := false
for _, arch := range arches {
fmt.Printf("generating %v/%v...\n", arch.target.OS, arch.target.Arch)
<-arch.done
if arch.err != nil {
failed = true
fmt.Printf(" %v\n", arch.err)
continue
}
for _, f := range arch.files {
fmt.Printf("extracting from %v\n", f.name)
<-f.done
if f.err != nil {
failed = true
fmt.Printf(" %v\n", f.err)
continue
}
}
fmt.Printf("\n")
}
if !failed {
failed = checkUnsupportedCalls(arches)
}
for _, arch := range arches {
if arch.build {
os.RemoveAll(arch.buildDir)
}
}
if failed {
os.Exit(1)
}
}
func createArches(OS string, archArray, files []string) ([]*Arch, error) {
var arches []*Arch
for _, archStr := range archArray {
buildDir := ""
if *flagBuild {
dir, err := ioutil.TempDir("", "syzkaller-kernel-build")
if err != nil {
return nil, fmt.Errorf("failed to create temp dir: %v", err)
}
buildDir = dir
} else if *flagBuildDir != "" {
buildDir = *flagBuildDir
} else {
buildDir = *flagSourceDir
}
target := targets.Get(OS, archStr)
if target == nil {
return nil, fmt.Errorf("unknown arch: %v", archStr)
}
arch := &Arch{
target: target,
sourceDir: *flagSourceDir,
buildDir: buildDir,
build: *flagBuild,
done: make(chan bool),
}
for _, f := range files {
arch.files = append(arch.files, &File{
arch: arch,
name: f,
done: make(chan bool),
})
}
arches = append(arches, arch)
}
return arches, nil
}
func checkUnsupportedCalls(arches []*Arch) bool {
supported := make(map[string]bool)
unsupported := make(map[string]string)
for _, arch := range arches {
for _, f := range arch.files {
for name := range f.consts {
supported[name] = true
}
for name := range f.undeclared {
unsupported[name] = f.name
}
}
}
failed := false
for name, file := range unsupported {
if supported[name] {
continue
}
failed = true
fmt.Printf("%v: %v is unsupported on all arches (typo?)\n",
file, name)
}
return failed
}
func archFileList(os, arch string, files []string) (string, []string, []string, error) {
android := false
if os == "android" {
android = true
os = "linux"
}
var arches []string
if arch != "" {
arches = strings.Split(arch, ",")
} else {
for arch := range targets.List[os] {
arches = append(arches, arch)
}
if android {
arches = []string{"386", "amd64", "arm", "arm64"}
}
sort.Strings(arches)
}
if len(files) == 0 {
matches, err := filepath.Glob(filepath.Join("sys", os, "*.txt"))
if err != nil || len(matches) == 0 {
return "", nil, nil, fmt.Errorf("failed to find sys files: %v", err)
}
androidFiles := map[string]bool{
"tlk_device.txt": true,
// This was generated on:
// https://source.codeaurora.org/quic/la/kernel/msm-4.9 msm-4.9
"video4linux.txt": true,
}
for _, f := range matches {
f = filepath.Base(f)
if os == "linux" && android != androidFiles[f] {
continue
}
files = append(files, f)
}
sort.Strings(files)
}
return os, arches, files, nil
}
func processArch(extractor Extractor, arch *Arch) (map[string]*compiler.ConstInfo, error) {
errBuf := new(bytes.Buffer)
eh := func(pos ast.Pos, msg string) {
fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
}
top := ast.ParseGlob(filepath.Join("sys", arch.target.OS, "*.txt"), eh)
if top == nil {
return nil, fmt.Errorf("%v", errBuf.String())
}
infos := compiler.ExtractConsts(top, arch.target, eh)
if infos == nil {
return nil, fmt.Errorf("%v", errBuf.String())
}
if err := extractor.prepareArch(arch); err != nil {
return nil, err
}
return infos, nil
}
func processFile(extractor Extractor, arch *Arch, file *File) (map[string]uint64, map[string]bool, error) {
inname := filepath.Join("sys", arch.target.OS, file.name)
outname := strings.TrimSuffix(inname, ".txt") + "_" + arch.target.Arch + ".const"
if file.info == nil {
return nil, nil, fmt.Errorf("input file %v is missing", inname)
}
if len(file.info.Consts) == 0 {
os.Remove(outname)
return nil, nil, nil
}
consts, undeclared, err := extractor.processFile(arch, file.info)
if err != nil {
return nil, nil, err
}
data := compiler.SerializeConsts(consts, undeclared)
if err := osutil.WriteFile(outname, data); err != nil {
return nil, nil, fmt.Errorf("failed to write output file: %v", err)
}
return consts, undeclared, nil
}