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

     1  // Copyright 2015/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"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"sort"
    15  	"strings"
    16  	"sync"
    17  	"text/template"
    18  
    19  	"github.com/google/syzkaller/pkg/ast"
    20  	"github.com/google/syzkaller/pkg/compiler"
    21  	"github.com/google/syzkaller/pkg/hash"
    22  	"github.com/google/syzkaller/pkg/osutil"
    23  	"github.com/google/syzkaller/pkg/serializer"
    24  	"github.com/google/syzkaller/pkg/tool"
    25  	"github.com/google/syzkaller/prog"
    26  	"github.com/google/syzkaller/sys/targets"
    27  )
    28  
    29  type SyscallData struct {
    30  	Name     string
    31  	CallName string
    32  	NR       int32
    33  	NeedCall bool
    34  	Attrs    []uint64
    35  }
    36  
    37  type Define struct {
    38  	Name  string
    39  	Value string
    40  }
    41  
    42  type ArchData struct {
    43  	Revision   string
    44  	ForkServer int
    45  	Shmem      int
    46  	GOARCH     string
    47  	PageSize   uint64
    48  	NumPages   uint64
    49  	DataOffset uint64
    50  	Calls      []SyscallData
    51  	Defines    []Define
    52  }
    53  
    54  type OSData struct {
    55  	GOOS  string
    56  	Archs []ArchData
    57  }
    58  
    59  type CallPropDescription struct {
    60  	Type string
    61  	Name string
    62  }
    63  
    64  type ExecutorData struct {
    65  	OSes      []OSData
    66  	CallAttrs []string
    67  	CallProps []CallPropDescription
    68  }
    69  
    70  var srcDir = flag.String("src", "", "path to root of syzkaller source dir")
    71  var outDir = flag.String("out", "", "path to out dir")
    72  
    73  func main() {
    74  	defer tool.Init()()
    75  
    76  	var OSList []string
    77  	for OS := range targets.List {
    78  		OSList = append(OSList, OS)
    79  	}
    80  	sort.Strings(OSList)
    81  
    82  	data := &ExecutorData{}
    83  	for _, OS := range OSList {
    84  		descriptions := ast.ParseGlob(filepath.Join(*srcDir, "sys", OS, "*.txt"), nil)
    85  		if descriptions == nil {
    86  			os.Exit(1)
    87  		}
    88  		constFile := compiler.DeserializeConstFile(filepath.Join(*srcDir, "sys", OS, "*.const"), nil)
    89  		if constFile == nil {
    90  			os.Exit(1)
    91  		}
    92  		osutil.MkdirAll(filepath.Join(*outDir, "sys", OS, "gen"))
    93  
    94  		var archs []string
    95  		for arch := range targets.List[OS] {
    96  			archs = append(archs, arch)
    97  		}
    98  		sort.Strings(archs)
    99  
   100  		var jobs []*Job
   101  		for _, arch := range archs {
   102  			target := targets.List[OS][arch]
   103  			constInfo := compiler.ExtractConsts(descriptions, target, nil)
   104  			if OS == targets.TestOS {
   105  				// The ConstFile object provides no guarantees re concurrent read-write,
   106  				// so let's patch it before we start goroutines.
   107  				compiler.FabricateSyscallConsts(target, constInfo, constFile)
   108  			}
   109  			jobs = append(jobs, &Job{
   110  				Target:      target,
   111  				Unsupported: make(map[string]bool),
   112  				ConstInfo:   constInfo,
   113  			})
   114  		}
   115  		sort.Slice(jobs, func(i, j int) bool {
   116  			return jobs[i].Target.Arch < jobs[j].Target.Arch
   117  		})
   118  		var wg sync.WaitGroup
   119  		wg.Add(len(jobs))
   120  
   121  		for _, job := range jobs {
   122  			job := job
   123  			go func() {
   124  				defer wg.Done()
   125  				processJob(job, descriptions, constFile)
   126  			}()
   127  		}
   128  		wg.Wait()
   129  
   130  		var syscallArchs []ArchData
   131  		unsupported := make(map[string]int)
   132  		for _, job := range jobs {
   133  			if !job.OK {
   134  				fmt.Printf("compilation of %v/%v target failed:\n", job.Target.OS, job.Target.Arch)
   135  				for _, msg := range job.Errors {
   136  					fmt.Print(msg)
   137  				}
   138  				os.Exit(1)
   139  			}
   140  			syscallArchs = append(syscallArchs, job.ArchData)
   141  			for u := range job.Unsupported {
   142  				unsupported[u]++
   143  			}
   144  		}
   145  		data.OSes = append(data.OSes, OSData{
   146  			GOOS:  OS,
   147  			Archs: syscallArchs,
   148  		})
   149  
   150  		for what, count := range unsupported {
   151  			if count == len(jobs) {
   152  				tool.Failf("%v is unsupported on all arches (typo?)", what)
   153  			}
   154  		}
   155  	}
   156  
   157  	attrs := reflect.TypeOf(prog.SyscallAttrs{})
   158  	for i := 0; i < attrs.NumField(); i++ {
   159  		data.CallAttrs = append(data.CallAttrs, prog.CppName(attrs.Field(i).Name))
   160  	}
   161  
   162  	props := prog.CallProps{}
   163  	props.ForeachProp(func(name, _ string, value reflect.Value) {
   164  		data.CallProps = append(data.CallProps, CallPropDescription{
   165  			Type: value.Kind().String(),
   166  			Name: prog.CppName(name),
   167  		})
   168  	})
   169  
   170  	writeExecutorSyscalls(data)
   171  }
   172  
   173  type Job struct {
   174  	Target      *targets.Target
   175  	OK          bool
   176  	Errors      []string
   177  	Unsupported map[string]bool
   178  	ArchData    ArchData
   179  	ConstInfo   map[string]*compiler.ConstInfo
   180  }
   181  
   182  func processJob(job *Job, descriptions *ast.Description, constFile *compiler.ConstFile) {
   183  	var flags []prog.FlagDesc
   184  	for _, decl := range descriptions.Nodes {
   185  		switch n := decl.(type) {
   186  		case *ast.IntFlags:
   187  			var flag prog.FlagDesc
   188  			flag.Name = n.Name.Name
   189  			for _, val := range n.Values {
   190  				flag.Values = append(flag.Values, val.Ident)
   191  			}
   192  			flags = append(flags, flag)
   193  		}
   194  	}
   195  
   196  	eh := func(pos ast.Pos, msg string) {
   197  		job.Errors = append(job.Errors, fmt.Sprintf("%v: %v\n", pos, msg))
   198  	}
   199  	consts := constFile.Arch(job.Target.Arch)
   200  	prog := compiler.Compile(descriptions, consts, job.Target, eh)
   201  	if prog == nil {
   202  		return
   203  	}
   204  	for what := range prog.Unsupported {
   205  		job.Unsupported[what] = true
   206  	}
   207  
   208  	sysFile := filepath.Join(*outDir, "sys", job.Target.OS, "gen", job.Target.Arch+".go")
   209  	out := new(bytes.Buffer)
   210  	generate(job.Target, prog, consts, flags, out)
   211  	rev := hash.String(out.Bytes())
   212  	fmt.Fprintf(out, "const revision_%v = %q\n", job.Target.Arch, rev)
   213  	writeSource(sysFile, out.Bytes())
   214  
   215  	job.ArchData = generateExecutorSyscalls(job.Target, prog.Syscalls, rev)
   216  
   217  	// Don't print warnings, they are printed in syz-check.
   218  	job.Errors = nil
   219  	// But let's fail on always actionable errors.
   220  	if job.Target.OS != targets.Fuchsia {
   221  		// There are too many broken consts on Fuchsia.
   222  		constsAreAllDefined(constFile, job.ConstInfo, eh)
   223  	}
   224  	job.OK = len(job.Errors) == 0
   225  }
   226  
   227  func generate(target *targets.Target, prg *compiler.Prog, consts map[string]uint64, flags []prog.FlagDesc,
   228  	out io.Writer) {
   229  	tag := fmt.Sprintf("syz_target,syz_os_%v,syz_arch_%v", target.OS, target.Arch)
   230  	if target.VMArch != "" {
   231  		tag += fmt.Sprintf(" syz_target,syz_os_%v,syz_arch_%v", target.OS, target.VMArch)
   232  	}
   233  	fmt.Fprintf(out, "// AUTOGENERATED FILE\n")
   234  	fmt.Fprintf(out, "// +build !codeanalysis\n")
   235  	fmt.Fprintf(out, "// +build !syz_target %v\n\n", tag)
   236  	fmt.Fprintf(out, "package gen\n\n")
   237  	fmt.Fprintf(out, "import . \"github.com/google/syzkaller/prog\"\n")
   238  	fmt.Fprintf(out, "import . \"github.com/google/syzkaller/sys/%v\"\n\n", target.OS)
   239  
   240  	fmt.Fprintf(out, "func init() {\n")
   241  	fmt.Fprintf(out, "\tRegisterTarget(&Target{"+
   242  		"OS: %q, Arch: %q, Revision: revision_%v, PtrSize: %v, PageSize: %v, "+
   243  		"NumPages: %v, DataOffset: %v, LittleEndian: %v, ExecutorUsesShmem: %v, "+
   244  		"Syscalls: syscalls_%v, Resources: resources_%v, Consts: consts_%v,"+
   245  		"Flags: flags_%v}, types_%v, InitTarget)\n}\n\n",
   246  		target.OS, target.Arch, target.Arch, target.PtrSize, target.PageSize,
   247  		target.NumPages, target.DataOffset, target.LittleEndian, target.ExecutorUsesShmem,
   248  		target.Arch, target.Arch, target.Arch, target.Arch, target.Arch)
   249  
   250  	fmt.Fprintf(out, "var resources_%v = ", target.Arch)
   251  	serializer.Write(out, prg.Resources)
   252  	fmt.Fprintf(out, "\n\n")
   253  
   254  	fmt.Fprintf(out, "var syscalls_%v = ", target.Arch)
   255  	serializer.Write(out, prg.Syscalls)
   256  	fmt.Fprintf(out, "\n\n")
   257  
   258  	fmt.Fprintf(out, "var types_%v = ", target.Arch)
   259  	serializer.Write(out, prg.Types)
   260  	fmt.Fprintf(out, "\n\n")
   261  
   262  	fmt.Fprintf(out, "var flags_%v = ", target.Arch)
   263  	serializer.Write(out, flags)
   264  	fmt.Fprintf(out, "\n\n")
   265  
   266  	constArr := make([]prog.ConstValue, 0, len(consts))
   267  	for name, val := range consts {
   268  		constArr = append(constArr, prog.ConstValue{Name: name, Value: val})
   269  	}
   270  	sort.Slice(constArr, func(i, j int) bool {
   271  		return constArr[i].Name < constArr[j].Name
   272  	})
   273  	fmt.Fprintf(out, "var consts_%v = ", target.Arch)
   274  	serializer.Write(out, constArr)
   275  	fmt.Fprintf(out, "\n\n")
   276  }
   277  
   278  func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall, rev string) ArchData {
   279  	data := ArchData{
   280  		Revision:   rev,
   281  		GOARCH:     target.Arch,
   282  		PageSize:   target.PageSize,
   283  		NumPages:   target.NumPages,
   284  		DataOffset: target.DataOffset,
   285  	}
   286  	if target.ExecutorUsesForkServer {
   287  		data.ForkServer = 1
   288  	}
   289  	if target.ExecutorUsesShmem {
   290  		data.Shmem = 1
   291  	}
   292  	defines := make(map[string]string)
   293  	for _, c := range syscalls {
   294  		var attrVals []uint64
   295  		attrs := reflect.ValueOf(c.Attrs)
   296  		last := -1
   297  		for i := 0; i < attrs.NumField(); i++ {
   298  			attr := attrs.Field(i)
   299  			val := uint64(0)
   300  			switch attr.Type().Kind() {
   301  			case reflect.Bool:
   302  				if attr.Bool() {
   303  					val = 1
   304  				}
   305  			case reflect.Uint64:
   306  				val = attr.Uint()
   307  			default:
   308  				panic("unsupported syscall attribute type")
   309  			}
   310  			attrVals = append(attrVals, val)
   311  			if val != 0 {
   312  				last = i
   313  			}
   314  		}
   315  		data.Calls = append(data.Calls, newSyscallData(target, c, attrVals[:last+1]))
   316  		// Some syscalls might not be present on the compiling machine, so we
   317  		// generate definitions for them.
   318  		if target.HasCallNumber(c.CallName) && target.NeedSyscallDefine(c.NR) {
   319  			defines[target.SyscallPrefix+c.CallName] = fmt.Sprintf("%d", c.NR)
   320  		}
   321  	}
   322  	sort.Slice(data.Calls, func(i, j int) bool {
   323  		return data.Calls[i].Name < data.Calls[j].Name
   324  	})
   325  	// Get a sorted list of definitions.
   326  	defineNames := []string{}
   327  	for key := range defines {
   328  		defineNames = append(defineNames, key)
   329  	}
   330  	sort.Strings(defineNames)
   331  	for _, key := range defineNames {
   332  		data.Defines = append(data.Defines, Define{key, defines[key]})
   333  	}
   334  	return data
   335  }
   336  
   337  func newSyscallData(target *targets.Target, sc *prog.Syscall, attrs []uint64) SyscallData {
   338  	callName, patchCallName := target.SyscallTrampolines[sc.Name]
   339  	if !patchCallName {
   340  		callName = sc.CallName
   341  	}
   342  	return SyscallData{
   343  		Name:     sc.Name,
   344  		CallName: callName,
   345  		NR:       int32(sc.NR),
   346  		NeedCall: (!target.HasCallNumber(sc.CallName) || patchCallName) &&
   347  			// These are declared in the compiler for internal purposes.
   348  			!strings.HasPrefix(sc.Name, "syz_builtin"),
   349  		Attrs: attrs,
   350  	}
   351  }
   352  
   353  func writeExecutorSyscalls(data *ExecutorData) {
   354  	osutil.MkdirAll(filepath.Join(*outDir, "executor"))
   355  	sort.Slice(data.OSes, func(i, j int) bool {
   356  		return data.OSes[i].GOOS < data.OSes[j].GOOS
   357  	})
   358  	buf := new(bytes.Buffer)
   359  	if err := defsTempl.Execute(buf, data); err != nil {
   360  		tool.Failf("failed to execute defs template: %v", err)
   361  	}
   362  	writeFile(filepath.Join(*outDir, "executor", "defs.h"), buf.Bytes())
   363  	buf.Reset()
   364  	if err := syscallsTempl.Execute(buf, data); err != nil {
   365  		tool.Failf("failed to execute syscalls template: %v", err)
   366  	}
   367  	writeFile(filepath.Join(*outDir, "executor", "syscalls.h"), buf.Bytes())
   368  }
   369  
   370  func writeSource(file string, data []byte) {
   371  	if oldSrc, err := os.ReadFile(file); err == nil && bytes.Equal(data, oldSrc) {
   372  		return
   373  	}
   374  	writeFile(file, data)
   375  }
   376  
   377  func writeFile(file string, data []byte) {
   378  	outf, err := os.Create(file)
   379  	if err != nil {
   380  		tool.Failf("failed to create output file: %v", err)
   381  	}
   382  	defer outf.Close()
   383  	outf.Write(data)
   384  }
   385  
   386  var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
   387  
   388  struct call_attrs_t { {{range $attr := $.CallAttrs}}
   389  	uint64_t {{$attr}};{{end}}
   390  };
   391  
   392  struct call_props_t { {{range $attr := $.CallProps}}
   393  	{{$attr.Type}} {{$attr.Name}};{{end}}
   394  };
   395  
   396  #define read_call_props_t(var, reader) { \{{range $attr := $.CallProps}}
   397  	(var).{{$attr.Name}} = ({{$attr.Type}})(reader); \{{end}}
   398  }
   399  
   400  {{range $os := $.OSes}}
   401  #if GOOS_{{$os.GOOS}}
   402  #define GOOS "{{$os.GOOS}}"
   403  {{range $arch := $os.Archs}}
   404  #if GOARCH_{{$arch.GOARCH}}
   405  #define GOARCH "{{.GOARCH}}"
   406  #define SYZ_REVISION "{{.Revision}}"
   407  #define SYZ_EXECUTOR_USES_FORK_SERVER {{.ForkServer}}
   408  #define SYZ_EXECUTOR_USES_SHMEM {{.Shmem}}
   409  #define SYZ_PAGE_SIZE {{.PageSize}}
   410  #define SYZ_NUM_PAGES {{.NumPages}}
   411  #define SYZ_DATA_OFFSET {{.DataOffset}}
   412  {{range $c := $arch.Defines}}#ifndef {{$c.Name}}
   413  #define {{$c.Name}} {{$c.Value}}
   414  #endif
   415  {{end}}#endif
   416  {{end}}
   417  #endif
   418  {{end}}
   419  `))
   420  
   421  // nolint: lll
   422  var syscallsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
   423  // clang-format off
   424  {{range $os := $.OSes}}
   425  #if GOOS_{{$os.GOOS}}
   426  {{range $arch := $os.Archs}}
   427  #if GOARCH_{{$arch.GOARCH}}
   428  const call_t syscalls[] = {
   429  {{range $c := $arch.Calls}}    {"{{$c.Name}}", {{$c.NR}}{{if or $c.Attrs $c.NeedCall}}, { {{- range $attr := $c.Attrs}}{{$attr}}, {{end}}}{{end}}{{if $c.NeedCall}}, (syscall_t){{$c.CallName}}{{end}}},
   430  {{end}}};
   431  #endif
   432  {{end}}
   433  #endif
   434  {{end}}
   435  `))