Home | History | Annotate | Download | only in syz-extract
      1 // Copyright 2016 syzkaller project authors. All rights reserved.
      2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
      3 
      4 package main
      5 
      6 import (
      7 	"bytes"
      8 	"flag"
      9 	"fmt"
     10 	"io/ioutil"
     11 	"os"
     12 	"path/filepath"
     13 	"runtime"
     14 	"sort"
     15 	"strings"
     16 
     17 	"github.com/google/syzkaller/pkg/ast"
     18 	"github.com/google/syzkaller/pkg/compiler"
     19 	"github.com/google/syzkaller/pkg/osutil"
     20 	"github.com/google/syzkaller/sys/targets"
     21 )
     22 
     23 var (
     24 	flagOS        = flag.String("os", "", "target OS")
     25 	flagBuild     = flag.Bool("build", false, "regenerate arch-specific kernel headers")
     26 	flagSourceDir = flag.String("sourcedir", "", "path to kernel source checkout dir")
     27 	flagBuildDir  = flag.String("builddir", "", "path to kernel build dir")
     28 	flagArch      = flag.String("arch", "", "comma-separated list of arches to generate (all by default)")
     29 )
     30 
     31 type Arch struct {
     32 	target    *targets.Target
     33 	sourceDir string
     34 	buildDir  string
     35 	build     bool
     36 	files     []*File
     37 	err       error
     38 	done      chan bool
     39 }
     40 
     41 type File struct {
     42 	arch       *Arch
     43 	name       string
     44 	consts     map[string]uint64
     45 	undeclared map[string]bool
     46 	info       *compiler.ConstInfo
     47 	err        error
     48 	done       chan bool
     49 }
     50 
     51 type Extractor interface {
     52 	prepare(sourcedir string, build bool, arches []string) error
     53 	prepareArch(arch *Arch) error
     54 	processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error)
     55 }
     56 
     57 var extractors = map[string]Extractor{
     58 	"akaros":  new(akaros),
     59 	"linux":   new(linux),
     60 	"freebsd": new(freebsd),
     61 	"netbsd":  new(netbsd),
     62 	"android": new(linux),
     63 	"fuchsia": new(fuchsia),
     64 	"windows": new(windows),
     65 }
     66 
     67 func main() {
     68 	failf := func(msg string, args ...interface{}) {
     69 		fmt.Fprintf(os.Stderr, msg+"\n", args...)
     70 		os.Exit(1)
     71 	}
     72 	flag.Parse()
     73 	if *flagBuild && *flagBuildDir != "" {
     74 		failf("-build and -builddir is an invalid combination")
     75 	}
     76 
     77 	OS, archArray, files, err := archFileList(*flagOS, *flagArch, flag.Args())
     78 	if err != nil {
     79 		failf("%v", err)
     80 	}
     81 
     82 	extractor := extractors[OS]
     83 	if extractor == nil {
     84 		failf("unknown os: %v", OS)
     85 	}
     86 	if err := extractor.prepare(*flagSourceDir, *flagBuild, archArray); err != nil {
     87 		failf("%v", err)
     88 	}
     89 
     90 	arches, err := createArches(OS, archArray, files)
     91 	if err != nil {
     92 		failf("%v", err)
     93 	}
     94 	jobC := make(chan interface{}, len(archArray)*len(files))
     95 	for _, arch := range arches {
     96 		jobC <- arch
     97 	}
     98 
     99 	for p := 0; p < runtime.GOMAXPROCS(0); p++ {
    100 		go func() {
    101 			for job := range jobC {
    102 				switch j := job.(type) {
    103 				case *Arch:
    104 					infos, err := processArch(extractor, j)
    105 					j.err = err
    106 					close(j.done)
    107 					if j.err == nil {
    108 						for _, f := range j.files {
    109 							f.info = infos[f.name]
    110 							jobC <- f
    111 						}
    112 					}
    113 				case *File:
    114 					j.consts, j.undeclared, j.err = processFile(extractor, j.arch, j)
    115 					close(j.done)
    116 				}
    117 			}
    118 		}()
    119 	}
    120 
    121 	failed := false
    122 	for _, arch := range arches {
    123 		fmt.Printf("generating %v/%v...\n", arch.target.OS, arch.target.Arch)
    124 		<-arch.done
    125 		if arch.err != nil {
    126 			failed = true
    127 			fmt.Printf("	%v\n", arch.err)
    128 			continue
    129 		}
    130 		for _, f := range arch.files {
    131 			fmt.Printf("extracting from %v\n", f.name)
    132 			<-f.done
    133 			if f.err != nil {
    134 				failed = true
    135 				fmt.Printf("	%v\n", f.err)
    136 				continue
    137 			}
    138 		}
    139 		fmt.Printf("\n")
    140 	}
    141 
    142 	if !failed {
    143 		failed = checkUnsupportedCalls(arches)
    144 	}
    145 	for _, arch := range arches {
    146 		if arch.build {
    147 			os.RemoveAll(arch.buildDir)
    148 		}
    149 	}
    150 	if failed {
    151 		os.Exit(1)
    152 	}
    153 }
    154 
    155 func createArches(OS string, archArray, files []string) ([]*Arch, error) {
    156 	var arches []*Arch
    157 	for _, archStr := range archArray {
    158 		buildDir := ""
    159 		if *flagBuild {
    160 			dir, err := ioutil.TempDir("", "syzkaller-kernel-build")
    161 			if err != nil {
    162 				return nil, fmt.Errorf("failed to create temp dir: %v", err)
    163 			}
    164 			buildDir = dir
    165 		} else if *flagBuildDir != "" {
    166 			buildDir = *flagBuildDir
    167 		} else {
    168 			buildDir = *flagSourceDir
    169 		}
    170 
    171 		target := targets.Get(OS, archStr)
    172 		if target == nil {
    173 			return nil, fmt.Errorf("unknown arch: %v", archStr)
    174 		}
    175 
    176 		arch := &Arch{
    177 			target:    target,
    178 			sourceDir: *flagSourceDir,
    179 			buildDir:  buildDir,
    180 			build:     *flagBuild,
    181 			done:      make(chan bool),
    182 		}
    183 		for _, f := range files {
    184 			arch.files = append(arch.files, &File{
    185 				arch: arch,
    186 				name: f,
    187 				done: make(chan bool),
    188 			})
    189 		}
    190 		arches = append(arches, arch)
    191 	}
    192 	return arches, nil
    193 }
    194 
    195 func checkUnsupportedCalls(arches []*Arch) bool {
    196 	supported := make(map[string]bool)
    197 	unsupported := make(map[string]string)
    198 	for _, arch := range arches {
    199 		for _, f := range arch.files {
    200 			for name := range f.consts {
    201 				supported[name] = true
    202 			}
    203 			for name := range f.undeclared {
    204 				unsupported[name] = f.name
    205 			}
    206 		}
    207 	}
    208 	failed := false
    209 	for name, file := range unsupported {
    210 		if supported[name] {
    211 			continue
    212 		}
    213 		failed = true
    214 		fmt.Printf("%v: %v is unsupported on all arches (typo?)\n",
    215 			file, name)
    216 	}
    217 	return failed
    218 }
    219 
    220 func archFileList(os, arch string, files []string) (string, []string, []string, error) {
    221 	android := false
    222 	if os == "android" {
    223 		android = true
    224 		os = "linux"
    225 	}
    226 	var arches []string
    227 	if arch != "" {
    228 		arches = strings.Split(arch, ",")
    229 	} else {
    230 		for arch := range targets.List[os] {
    231 			arches = append(arches, arch)
    232 		}
    233 		if android {
    234 			arches = []string{"386", "amd64", "arm", "arm64"}
    235 		}
    236 		sort.Strings(arches)
    237 	}
    238 	if len(files) == 0 {
    239 		matches, err := filepath.Glob(filepath.Join("sys", os, "*.txt"))
    240 		if err != nil || len(matches) == 0 {
    241 			return "", nil, nil, fmt.Errorf("failed to find sys files: %v", err)
    242 		}
    243 		androidFiles := map[string]bool{
    244 			"tlk_device.txt": true,
    245 			// This was generated on:
    246 			// https://source.codeaurora.org/quic/la/kernel/msm-4.9 msm-4.9
    247 			"video4linux.txt": true,
    248 		}
    249 		for _, f := range matches {
    250 			f = filepath.Base(f)
    251 			if os == "linux" && android != androidFiles[f] {
    252 				continue
    253 			}
    254 			files = append(files, f)
    255 		}
    256 		sort.Strings(files)
    257 	}
    258 	return os, arches, files, nil
    259 }
    260 
    261 func processArch(extractor Extractor, arch *Arch) (map[string]*compiler.ConstInfo, error) {
    262 	errBuf := new(bytes.Buffer)
    263 	eh := func(pos ast.Pos, msg string) {
    264 		fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
    265 	}
    266 	top := ast.ParseGlob(filepath.Join("sys", arch.target.OS, "*.txt"), eh)
    267 	if top == nil {
    268 		return nil, fmt.Errorf("%v", errBuf.String())
    269 	}
    270 	infos := compiler.ExtractConsts(top, arch.target, eh)
    271 	if infos == nil {
    272 		return nil, fmt.Errorf("%v", errBuf.String())
    273 	}
    274 	if err := extractor.prepareArch(arch); err != nil {
    275 		return nil, err
    276 	}
    277 	return infos, nil
    278 }
    279 
    280 func processFile(extractor Extractor, arch *Arch, file *File) (map[string]uint64, map[string]bool, error) {
    281 	inname := filepath.Join("sys", arch.target.OS, file.name)
    282 	outname := strings.TrimSuffix(inname, ".txt") + "_" + arch.target.Arch + ".const"
    283 	if file.info == nil {
    284 		return nil, nil, fmt.Errorf("input file %v is missing", inname)
    285 	}
    286 	if len(file.info.Consts) == 0 {
    287 		os.Remove(outname)
    288 		return nil, nil, nil
    289 	}
    290 	consts, undeclared, err := extractor.processFile(arch, file.info)
    291 	if err != nil {
    292 		return nil, nil, err
    293 	}
    294 	data := compiler.SerializeConsts(consts, undeclared)
    295 	if err := osutil.WriteFile(outname, data); err != nil {
    296 		return nil, nil, fmt.Errorf("failed to write output file: %v", err)
    297 	}
    298 	return consts, undeclared, nil
    299 }
    300