blob: 0427272f8ba30c2de3878900d1316e7f27f4ff57 [file] [log] [blame]
// 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/compiler"
"github.com/google/syzkaller/pkg/hash"
"github.com/google/syzkaller/pkg/osutil"
"github.com/google/syzkaller/pkg/serializer"
"github.com/google/syzkaller/pkg/tool"
"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 Define struct {
Name string
Value string
}
type ArchData struct {
Revision string
ForkServer int
Shmem int
GOARCH string
PageSize uint64
NumPages uint64
DataOffset uint64
Calls []SyscallData
Defines []Define
}
type OSData struct {
GOOS string
Archs []ArchData
}
type CallPropDescription struct {
Type string
Name string
}
type ExecutorData struct {
OSes []OSData
CallAttrs []string
CallProps []CallPropDescription
}
var srcDir = flag.String("src", "", "path to root of syzkaller source dir")
var outDir = flag.String("out", "", "path to out dir")
func main() {
defer tool.Init()()
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)
var jobs []*Job
for _, arch := range archs {
jobs = append(jobs, &Job{
Target: targets.List[OS][arch],
Unsupported: make(map[string]bool),
})
}
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()
processJob(job, descriptions, constFile)
}()
}
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) {
tool.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))
}
props := prog.CallProps{}
props.ForeachProp(func(name, _ string, value reflect.Value) {
data.CallProps = append(data.CallProps, CallPropDescription{
Type: value.Kind().String(),
Name: prog.CppName(name),
})
})
writeExecutorSyscalls(data)
}
type Job struct {
Target *targets.Target
OK bool
Errors []string
Unsupported map[string]bool
ArchData ArchData
}
func processJob(job *Job, descriptions *ast.Description, constFile *compiler.ConstFile) {
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)
if job.Target.OS == targets.TestOS {
constInfo := compiler.ExtractConsts(descriptions, job.Target, eh)
compiler.FabricateSyscallConsts(job.Target, constInfo, consts)
}
prog := compiler.Compile(descriptions, consts, job.Target, eh)
if prog == nil {
return
}
for what := range prog.Unsupported {
job.Unsupported[what] = true
}
sysFile := filepath.Join(*outDir, "sys", job.Target.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
}
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, ExecutorUsesShmem: %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.ExecutorUsesShmem,
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 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
}
defines := make(map[string]string)
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]))
// Some syscalls might not be present on the compiling machine, so we
// generate definitions for them.
if target.SyscallNumbers && !strings.HasPrefix(c.CallName, "syz_") &&
target.NeedSyscallDefine(c.NR) {
defines[target.SyscallPrefix+c.CallName] = fmt.Sprintf("%d", c.NR)
}
}
sort.Slice(data.Calls, func(i, j int) bool {
return data.Calls[i].Name < data.Calls[j].Name
})
// Get a sorted list of definitions.
defineNames := []string{}
for key := range defines {
defineNames = append(defineNames, key)
}
sort.Strings(defineNames)
for _, key := range defineNames {
data.Defines = append(data.Defines, Define{key, defines[key]})
}
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 {
tool.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 {
tool.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 {
tool.Failf("failed to create output file: %v", err)
}
defer outf.Close()
outf.Write(data)
}
var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
struct call_attrs_t { {{range $attr := $.CallAttrs}}
uint64_t {{$attr}};{{end}}
};
struct call_props_t { {{range $attr := $.CallProps}}
{{$attr.Type}} {{$attr.Name}};{{end}}
};
#define read_call_props_t(var, reader) { \{{range $attr := $.CallProps}}
(var).{{$attr.Name}} = ({{$attr.Type}})(reader); \{{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}}
{{range $c := $arch.Defines}}#ifndef {{$c.Name}}
#define {{$c.Name}} {{$c.Value}}
#endif
{{end}}#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}}
`))