github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/sys/syz-extract/extract.go (about)

     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  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/google/syzkaller/pkg/ast"
    17  	"github.com/google/syzkaller/pkg/compiler"
    18  	"github.com/google/syzkaller/pkg/osutil"
    19  	"github.com/google/syzkaller/pkg/tool"
    20  	"github.com/google/syzkaller/sys/targets"
    21  )
    22  
    23  var (
    24  	flagOS        = flag.String("os", runtime.GOOS, "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  	flagIncludes  = flag.String("includedirs", "", "path to other kernel source include dirs separated by commas")
    28  	flagBuildDir  = flag.String("builddir", "", "path to kernel build dir")
    29  	flagArch      = flag.String("arch", "", "comma-separated list of arches to generate (all by default)")
    30  )
    31  
    32  type Arch struct {
    33  	target      *targets.Target
    34  	sourceDir   string
    35  	includeDirs string
    36  	buildDir    string
    37  	build       bool
    38  	files       []*File
    39  	err         error
    40  	done        chan bool
    41  }
    42  
    43  type File struct {
    44  	arch       *Arch
    45  	name       string
    46  	consts     map[string]uint64
    47  	undeclared map[string]bool
    48  	info       *compiler.ConstInfo
    49  	err        error
    50  	done       chan bool
    51  }
    52  
    53  type Extractor interface {
    54  	prepare(sourcedir string, build bool, arches []*Arch) error
    55  	prepareArch(arch *Arch) error
    56  	processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error)
    57  }
    58  
    59  var extractors = map[string]Extractor{
    60  	targets.Linux:   new(linux),
    61  	targets.FreeBSD: new(freebsd),
    62  	targets.Darwin:  new(darwin),
    63  	targets.NetBSD:  new(netbsd),
    64  	targets.OpenBSD: new(openbsd),
    65  	"android":       new(linux),
    66  	targets.Fuchsia: new(fuchsia),
    67  	targets.Windows: new(windows),
    68  	targets.Trusty:  new(trusty),
    69  }
    70  
    71  func main() {
    72  	flag.Parse()
    73  	if *flagBuild && *flagBuildDir != "" {
    74  		tool.Failf("-build and -builddir is an invalid combination")
    75  	}
    76  	OS := *flagOS
    77  	extractor := extractors[OS]
    78  	if extractor == nil {
    79  		tool.Failf("unknown os: %v", OS)
    80  	}
    81  	arches, nfiles, err := createArches(OS, archList(OS, *flagArch), flag.Args())
    82  	if err != nil {
    83  		tool.Fail(err)
    84  	}
    85  	if *flagSourceDir == "" {
    86  		tool.Fail(fmt.Errorf("provide path to kernel checkout via -sourcedir " +
    87  			"flag (or make extract SOURCEDIR)"))
    88  	}
    89  	if err := extractor.prepare(*flagSourceDir, *flagBuild, arches); err != nil {
    90  		tool.Fail(err)
    91  	}
    92  
    93  	jobC := make(chan interface{}, len(arches)+nfiles)
    94  	for _, arch := range arches {
    95  		jobC <- arch
    96  	}
    97  
    98  	for p := 0; p < runtime.GOMAXPROCS(0); p++ {
    99  		go worker(extractor, jobC)
   100  	}
   101  
   102  	failed := false
   103  	constFiles := make(map[string]*compiler.ConstFile)
   104  	for _, arch := range arches {
   105  		fmt.Printf("generating %v/%v...\n", OS, arch.target.Arch)
   106  		<-arch.done
   107  		if arch.err != nil {
   108  			failed = true
   109  			fmt.Printf("%v\n", arch.err)
   110  			continue
   111  		}
   112  		for _, f := range arch.files {
   113  			<-f.done
   114  			if f.err != nil {
   115  				failed = true
   116  				fmt.Printf("%v: %v\n", f.name, f.err)
   117  				continue
   118  			}
   119  			if constFiles[f.name] == nil {
   120  				constFiles[f.name] = compiler.NewConstFile()
   121  			}
   122  			constFiles[f.name].AddArch(f.arch.target.Arch, f.consts, f.undeclared)
   123  		}
   124  	}
   125  	for file, cf := range constFiles {
   126  		outname := filepath.Join("sys", OS, file+".const")
   127  		data := cf.Serialize()
   128  		if len(data) == 0 {
   129  			os.Remove(outname)
   130  			continue
   131  		}
   132  		if err := osutil.WriteFile(outname, data); err != nil {
   133  			tool.Failf("failed to write output file: %v", err)
   134  		}
   135  	}
   136  
   137  	if !failed && *flagArch == "" {
   138  		failed = checkUnsupportedCalls(arches)
   139  	}
   140  	for _, arch := range arches {
   141  		if arch.build {
   142  			os.RemoveAll(arch.buildDir)
   143  		}
   144  	}
   145  	if failed {
   146  		os.Exit(1)
   147  	}
   148  }
   149  
   150  func worker(extractor Extractor, jobC chan interface{}) {
   151  	for job := range jobC {
   152  		switch j := job.(type) {
   153  		case *Arch:
   154  			infos, err := processArch(extractor, j)
   155  			j.err = err
   156  			close(j.done)
   157  			if j.err == nil {
   158  				for _, f := range j.files {
   159  					f.info = infos[filepath.Join("sys", j.target.OS, f.name)]
   160  					jobC <- f
   161  				}
   162  			}
   163  		case *File:
   164  			j.consts, j.undeclared, j.err = processFile(extractor, j.arch, j)
   165  			close(j.done)
   166  		}
   167  	}
   168  }
   169  
   170  func createArches(OS string, archArray, files []string) ([]*Arch, int, error) {
   171  	errBuf := new(bytes.Buffer)
   172  	eh := func(pos ast.Pos, msg string) {
   173  		fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
   174  	}
   175  	top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), eh)
   176  	if top == nil {
   177  		return nil, 0, fmt.Errorf("%v", errBuf.String())
   178  	}
   179  	allFiles := compiler.FileList(top, OS, eh)
   180  	if allFiles == nil {
   181  		return nil, 0, fmt.Errorf("%v", errBuf.String())
   182  	}
   183  	if len(files) == 0 {
   184  		for file := range allFiles {
   185  			files = append(files, file)
   186  		}
   187  	}
   188  	nfiles := 0
   189  	var arches []*Arch
   190  	for _, archStr := range archArray {
   191  		buildDir := ""
   192  		if *flagBuild {
   193  			dir, err := os.MkdirTemp("", "syzkaller-kernel-build")
   194  			if err != nil {
   195  				return nil, 0, fmt.Errorf("failed to create temp dir: %w", err)
   196  			}
   197  			buildDir = dir
   198  		} else if *flagBuildDir != "" {
   199  			buildDir = *flagBuildDir
   200  		} else {
   201  			buildDir = *flagSourceDir
   202  		}
   203  
   204  		target := targets.Get(OS, archStr)
   205  		if target == nil {
   206  			return nil, 0, fmt.Errorf("unknown arch: %v", archStr)
   207  		}
   208  
   209  		arch := &Arch{
   210  			target:      target,
   211  			sourceDir:   *flagSourceDir,
   212  			includeDirs: *flagIncludes,
   213  			buildDir:    buildDir,
   214  			build:       *flagBuild,
   215  			done:        make(chan bool),
   216  		}
   217  		var archFiles []string
   218  		for _, file := range files {
   219  			meta, ok := allFiles[file]
   220  			if !ok {
   221  				return nil, 0, fmt.Errorf("unknown file: %v", file)
   222  			}
   223  			if meta.NoExtract || !meta.SupportsArch(archStr) {
   224  				continue
   225  			}
   226  			archFiles = append(archFiles, file)
   227  		}
   228  		sort.Strings(archFiles)
   229  		for _, f := range archFiles {
   230  			arch.files = append(arch.files, &File{
   231  				arch: arch,
   232  				name: f,
   233  				done: make(chan bool),
   234  			})
   235  		}
   236  		arches = append(arches, arch)
   237  		nfiles += len(arch.files)
   238  	}
   239  	return arches, nfiles, nil
   240  }
   241  
   242  func archList(OS, arches string) []string {
   243  	if arches != "" {
   244  		return strings.Split(arches, ",")
   245  	}
   246  	var archArray []string
   247  	for arch := range targets.List[OS] {
   248  		archArray = append(archArray, arch)
   249  	}
   250  	sort.Strings(archArray)
   251  	return archArray
   252  }
   253  
   254  func checkUnsupportedCalls(arches []*Arch) bool {
   255  	supported := make(map[string]bool)
   256  	unsupported := make(map[string]string)
   257  	for _, arch := range arches {
   258  		for _, f := range arch.files {
   259  			for name := range f.consts {
   260  				supported[name] = true
   261  			}
   262  			for name := range f.undeclared {
   263  				unsupported[name] = f.name
   264  			}
   265  		}
   266  	}
   267  	failed := false
   268  	for name, file := range unsupported {
   269  		if supported[name] {
   270  			continue
   271  		}
   272  		failed = true
   273  		fmt.Printf("%v: %v is unsupported on all arches (typo?)\n",
   274  			file, name)
   275  	}
   276  	return failed
   277  }
   278  
   279  func processArch(extractor Extractor, arch *Arch) (map[string]*compiler.ConstInfo, error) {
   280  	errBuf := new(bytes.Buffer)
   281  	eh := func(pos ast.Pos, msg string) {
   282  		fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
   283  	}
   284  	top := ast.ParseGlob(filepath.Join("sys", arch.target.OS, "*.txt"), eh)
   285  	if top == nil {
   286  		return nil, fmt.Errorf("%v", errBuf.String())
   287  	}
   288  	infos := compiler.ExtractConsts(top, arch.target, eh)
   289  	if infos == nil {
   290  		return nil, fmt.Errorf("%v", errBuf.String())
   291  	}
   292  	if err := extractor.prepareArch(arch); err != nil {
   293  		return nil, err
   294  	}
   295  	return infos, nil
   296  }
   297  
   298  func processFile(extractor Extractor, arch *Arch, file *File) (map[string]uint64, map[string]bool, error) {
   299  	inname := filepath.Join("sys", arch.target.OS, file.name)
   300  	if file.info == nil {
   301  		return nil, nil, fmt.Errorf("const info for input file %v is missing", inname)
   302  	}
   303  	if len(file.info.Consts) == 0 {
   304  		return nil, nil, nil
   305  	}
   306  	return extractor.processFile(arch, file.info)
   307  }