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 `))