| // 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 |
| } |