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