mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-26 21:00:30 +00:00
b6de93e603
We now have 8 arches for Linux and .const files produce lots of noise in PRs and lots of diffs. If 3 .txt files are touched, the PR will have 24 .const files, which will be intermixed with .txt files. Frequently const values are equal across arches, and even if they don't spreading a single value across 8 files is inconvinient. Merge all 8 *_arch.const files into a single .const file. See the test for details of the new format. The old format is still parsed for now, we can't update all OSes at once. For Linux this reduces number of const files/lines from 1288/96599 to 158/11603. Fixes #1983
378 lines
10 KiB
Go
378 lines
10 KiB
Go
// Copyright 2015/2016 syzkaller project authors. All rights reserved.
|
|
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
|
|
"github.com/google/syzkaller/pkg/ast"
|
|
"github.com/google/syzkaller/pkg/cmdprof"
|
|
"github.com/google/syzkaller/pkg/compiler"
|
|
"github.com/google/syzkaller/pkg/hash"
|
|
"github.com/google/syzkaller/pkg/osutil"
|
|
"github.com/google/syzkaller/pkg/serializer"
|
|
"github.com/google/syzkaller/prog"
|
|
"github.com/google/syzkaller/sys/targets"
|
|
)
|
|
|
|
type SyscallData struct {
|
|
Name string
|
|
CallName string
|
|
NR int32
|
|
NeedCall bool
|
|
Attrs []uint64
|
|
}
|
|
|
|
type ArchData struct {
|
|
Revision string
|
|
ForkServer int
|
|
Shmem int
|
|
GOARCH string
|
|
PageSize uint64
|
|
NumPages uint64
|
|
DataOffset uint64
|
|
Calls []SyscallData
|
|
}
|
|
|
|
type OSData struct {
|
|
GOOS string
|
|
Archs []ArchData
|
|
}
|
|
|
|
type ExecutorData struct {
|
|
OSes []OSData
|
|
CallAttrs []string
|
|
}
|
|
|
|
var srcDir = flag.String("src", "", "path to root of syzkaller source dir")
|
|
var outDir = flag.String("out", "", "path to out dir")
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
defer cmdprof.Install()()
|
|
|
|
var OSList []string
|
|
for OS := range targets.List {
|
|
OSList = append(OSList, OS)
|
|
}
|
|
sort.Strings(OSList)
|
|
|
|
data := &ExecutorData{}
|
|
for _, OS := range OSList {
|
|
descriptions := ast.ParseGlob(filepath.Join(*srcDir, "sys", OS, "*.txt"), nil)
|
|
if descriptions == nil {
|
|
os.Exit(1)
|
|
}
|
|
constFile := compiler.DeserializeConstFile(filepath.Join(*srcDir, "sys", OS, "*.const"), nil)
|
|
if constFile == nil {
|
|
os.Exit(1)
|
|
}
|
|
osutil.MkdirAll(filepath.Join(*outDir, "sys", OS, "gen"))
|
|
|
|
var archs []string
|
|
for arch := range targets.List[OS] {
|
|
archs = append(archs, arch)
|
|
}
|
|
sort.Strings(archs)
|
|
|
|
type Job struct {
|
|
Target *targets.Target
|
|
OK bool
|
|
Errors []string
|
|
Unsupported map[string]bool
|
|
ArchData ArchData
|
|
}
|
|
var jobs []*Job
|
|
for _, arch := range archs {
|
|
jobs = append(jobs, &Job{
|
|
Target: targets.List[OS][arch],
|
|
})
|
|
}
|
|
sort.Slice(jobs, func(i, j int) bool {
|
|
return jobs[i].Target.Arch < jobs[j].Target.Arch
|
|
})
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(jobs))
|
|
|
|
for _, job := range jobs {
|
|
job := job
|
|
go func() {
|
|
defer wg.Done()
|
|
eh := func(pos ast.Pos, msg string) {
|
|
job.Errors = append(job.Errors, fmt.Sprintf("%v: %v\n", pos, msg))
|
|
}
|
|
consts := constFile.Arch(job.Target.Arch)
|
|
top := descriptions
|
|
if OS == "linux" && (job.Target.Arch == "arm" || job.Target.Arch == "riscv64") {
|
|
// Hack: KVM is not supported on ARM anymore. On riscv64 it
|
|
// is not supported yet but might be in the future.
|
|
// Note: syz-extract also ignores this file for arm and
|
|
// riscv64.
|
|
top = descriptions.Filter(func(n ast.Node) bool {
|
|
pos, _, _ := n.Info()
|
|
return !strings.HasSuffix(pos.File, "_kvm.txt")
|
|
})
|
|
}
|
|
if OS == "test" {
|
|
constInfo := compiler.ExtractConsts(top, job.Target, eh)
|
|
compiler.FabricateSyscallConsts(job.Target, constInfo, consts)
|
|
}
|
|
prog := compiler.Compile(top, consts, job.Target, eh)
|
|
if prog == nil {
|
|
return
|
|
}
|
|
job.Unsupported = prog.Unsupported
|
|
|
|
sysFile := filepath.Join(*outDir, "sys", OS, "gen", job.Target.Arch+".go")
|
|
out := new(bytes.Buffer)
|
|
generate(job.Target, prog, consts, out)
|
|
rev := hash.String(out.Bytes())
|
|
fmt.Fprintf(out, "const revision_%v = %q\n", job.Target.Arch, rev)
|
|
writeSource(sysFile, out.Bytes())
|
|
|
|
job.ArchData = generateExecutorSyscalls(job.Target, prog.Syscalls, rev)
|
|
|
|
// Don't print warnings, they are printed in syz-check.
|
|
job.Errors = nil
|
|
job.OK = true
|
|
}()
|
|
}
|
|
writeEmpty(OS)
|
|
wg.Wait()
|
|
|
|
var syscallArchs []ArchData
|
|
unsupported := make(map[string]int)
|
|
for _, job := range jobs {
|
|
if !job.OK {
|
|
fmt.Printf("compilation of %v/%v target failed:\n", job.Target.OS, job.Target.Arch)
|
|
for _, msg := range job.Errors {
|
|
fmt.Print(msg)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
syscallArchs = append(syscallArchs, job.ArchData)
|
|
for u := range job.Unsupported {
|
|
unsupported[u]++
|
|
}
|
|
}
|
|
data.OSes = append(data.OSes, OSData{
|
|
GOOS: OS,
|
|
Archs: syscallArchs,
|
|
})
|
|
|
|
for what, count := range unsupported {
|
|
if count == len(jobs) {
|
|
failf("%v is unsupported on all arches (typo?)", what)
|
|
}
|
|
}
|
|
}
|
|
|
|
attrs := reflect.TypeOf(prog.SyscallAttrs{})
|
|
for i := 0; i < attrs.NumField(); i++ {
|
|
data.CallAttrs = append(data.CallAttrs, prog.CppName(attrs.Field(i).Name))
|
|
}
|
|
|
|
writeExecutorSyscalls(data)
|
|
}
|
|
|
|
func generate(target *targets.Target, prg *compiler.Prog, consts map[string]uint64, out io.Writer) {
|
|
tag := fmt.Sprintf("syz_target,syz_os_%v,syz_arch_%v", target.OS, target.Arch)
|
|
if target.VMArch != "" {
|
|
tag += fmt.Sprintf(" syz_target,syz_os_%v,syz_arch_%v", target.OS, target.VMArch)
|
|
}
|
|
fmt.Fprintf(out, "// AUTOGENERATED FILE\n")
|
|
fmt.Fprintf(out, "// +build !codeanalysis\n")
|
|
fmt.Fprintf(out, "// +build !syz_target %v\n\n", tag)
|
|
fmt.Fprintf(out, "package gen\n\n")
|
|
fmt.Fprintf(out, "import . \"github.com/google/syzkaller/prog\"\n")
|
|
fmt.Fprintf(out, "import . \"github.com/google/syzkaller/sys/%v\"\n\n", target.OS)
|
|
|
|
fmt.Fprintf(out, "func init() {\n")
|
|
fmt.Fprintf(out, "\tRegisterTarget(&Target{"+
|
|
"OS: %q, Arch: %q, Revision: revision_%v, PtrSize: %v, "+
|
|
"PageSize: %v, NumPages: %v, DataOffset: %v, LittleEndian: %v, Syscalls: syscalls_%v, "+
|
|
"Resources: resources_%v, Consts: consts_%v}, "+
|
|
"types_%v, InitTarget)\n}\n\n",
|
|
target.OS, target.Arch, target.Arch, target.PtrSize,
|
|
target.PageSize, target.NumPages, target.DataOffset,
|
|
target.LittleEndian, target.Arch, target.Arch, target.Arch, target.Arch)
|
|
|
|
fmt.Fprintf(out, "var resources_%v = ", target.Arch)
|
|
serializer.Write(out, prg.Resources)
|
|
fmt.Fprintf(out, "\n\n")
|
|
|
|
fmt.Fprintf(out, "var syscalls_%v = ", target.Arch)
|
|
serializer.Write(out, prg.Syscalls)
|
|
fmt.Fprintf(out, "\n\n")
|
|
|
|
fmt.Fprintf(out, "var types_%v = ", target.Arch)
|
|
serializer.Write(out, prg.Types)
|
|
fmt.Fprintf(out, "\n\n")
|
|
|
|
constArr := make([]prog.ConstValue, 0, len(consts))
|
|
for name, val := range consts {
|
|
constArr = append(constArr, prog.ConstValue{Name: name, Value: val})
|
|
}
|
|
sort.Slice(constArr, func(i, j int) bool {
|
|
return constArr[i].Name < constArr[j].Name
|
|
})
|
|
fmt.Fprintf(out, "var consts_%v = ", target.Arch)
|
|
serializer.Write(out, constArr)
|
|
fmt.Fprintf(out, "\n\n")
|
|
}
|
|
|
|
func writeEmpty(OS string) {
|
|
const data = `// AUTOGENERATED FILE
|
|
// This file is needed if OS is completely excluded by build tags.
|
|
package gen
|
|
`
|
|
writeSource(filepath.Join(*outDir, "sys", OS, "gen", "empty.go"), []byte(data))
|
|
}
|
|
|
|
func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall, rev string) ArchData {
|
|
data := ArchData{
|
|
Revision: rev,
|
|
GOARCH: target.Arch,
|
|
PageSize: target.PageSize,
|
|
NumPages: target.NumPages,
|
|
DataOffset: target.DataOffset,
|
|
}
|
|
if target.ExecutorUsesForkServer {
|
|
data.ForkServer = 1
|
|
}
|
|
if target.ExecutorUsesShmem {
|
|
data.Shmem = 1
|
|
}
|
|
for _, c := range syscalls {
|
|
var attrVals []uint64
|
|
attrs := reflect.ValueOf(c.Attrs)
|
|
last := -1
|
|
for i := 0; i < attrs.NumField(); i++ {
|
|
attr := attrs.Field(i)
|
|
val := uint64(0)
|
|
switch attr.Type().Kind() {
|
|
case reflect.Bool:
|
|
if attr.Bool() {
|
|
val = 1
|
|
}
|
|
case reflect.Uint64:
|
|
val = attr.Uint()
|
|
default:
|
|
panic("unsupported syscall attribute type")
|
|
}
|
|
attrVals = append(attrVals, val)
|
|
if val != 0 {
|
|
last = i
|
|
}
|
|
}
|
|
data.Calls = append(data.Calls, newSyscallData(target, c, attrVals[:last+1]))
|
|
}
|
|
sort.Slice(data.Calls, func(i, j int) bool {
|
|
return data.Calls[i].Name < data.Calls[j].Name
|
|
})
|
|
return data
|
|
}
|
|
|
|
func newSyscallData(target *targets.Target, sc *prog.Syscall, attrs []uint64) SyscallData {
|
|
callName, patchCallName := target.SyscallTrampolines[sc.Name]
|
|
if !patchCallName {
|
|
callName = sc.CallName
|
|
}
|
|
return SyscallData{
|
|
Name: sc.Name,
|
|
CallName: callName,
|
|
NR: int32(sc.NR),
|
|
NeedCall: (!target.SyscallNumbers || strings.HasPrefix(sc.CallName, "syz_") || patchCallName) && !sc.Attrs.Disabled,
|
|
Attrs: attrs,
|
|
}
|
|
}
|
|
|
|
func writeExecutorSyscalls(data *ExecutorData) {
|
|
osutil.MkdirAll(filepath.Join(*outDir, "executor"))
|
|
sort.Slice(data.OSes, func(i, j int) bool {
|
|
return data.OSes[i].GOOS < data.OSes[j].GOOS
|
|
})
|
|
buf := new(bytes.Buffer)
|
|
if err := defsTempl.Execute(buf, data); err != nil {
|
|
failf("failed to execute defs template: %v", err)
|
|
}
|
|
writeFile(filepath.Join(*outDir, "executor", "defs.h"), buf.Bytes())
|
|
buf.Reset()
|
|
if err := syscallsTempl.Execute(buf, data); err != nil {
|
|
failf("failed to execute syscalls template: %v", err)
|
|
}
|
|
writeFile(filepath.Join(*outDir, "executor", "syscalls.h"), buf.Bytes())
|
|
}
|
|
|
|
func writeSource(file string, data []byte) {
|
|
if oldSrc, err := ioutil.ReadFile(file); err == nil && bytes.Equal(data, oldSrc) {
|
|
return
|
|
}
|
|
writeFile(file, data)
|
|
}
|
|
|
|
func writeFile(file string, data []byte) {
|
|
outf, err := os.Create(file)
|
|
if err != nil {
|
|
failf("failed to create output file: %v", err)
|
|
}
|
|
defer outf.Close()
|
|
outf.Write(data)
|
|
}
|
|
|
|
func failf(msg string, args ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, msg+"\n", args...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
|
|
|
|
struct call_attrs_t { {{range $attr := $.CallAttrs}}
|
|
uint64_t {{$attr}};{{end}}
|
|
};
|
|
{{range $os := $.OSes}}
|
|
#if GOOS_{{$os.GOOS}}
|
|
#define GOOS "{{$os.GOOS}}"
|
|
{{range $arch := $os.Archs}}
|
|
#if GOARCH_{{$arch.GOARCH}}
|
|
#define GOARCH "{{.GOARCH}}"
|
|
#define SYZ_REVISION "{{.Revision}}"
|
|
#define SYZ_EXECUTOR_USES_FORK_SERVER {{.ForkServer}}
|
|
#define SYZ_EXECUTOR_USES_SHMEM {{.Shmem}}
|
|
#define SYZ_PAGE_SIZE {{.PageSize}}
|
|
#define SYZ_NUM_PAGES {{.NumPages}}
|
|
#define SYZ_DATA_OFFSET {{.DataOffset}}
|
|
#endif
|
|
{{end}}
|
|
#endif
|
|
{{end}}
|
|
`))
|
|
|
|
// nolint: lll
|
|
var syscallsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
|
|
// clang-format off
|
|
{{range $os := $.OSes}}
|
|
#if GOOS_{{$os.GOOS}}
|
|
{{range $arch := $os.Archs}}
|
|
#if GOARCH_{{$arch.GOARCH}}
|
|
const call_t syscalls[] = {
|
|
{{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}}},
|
|
{{end}}};
|
|
#endif
|
|
{{end}}
|
|
#endif
|
|
{{end}}
|
|
`))
|