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
4package main
5
6import (
7	"bytes"
8	"flag"
9	"fmt"
10	"go/format"
11	"io"
12	"io/ioutil"
13	"os"
14	"path/filepath"
15	"runtime"
16	"runtime/pprof"
17	"sort"
18	"strings"
19	"sync"
20	"text/template"
21
22	"github.com/google/syzkaller/pkg/ast"
23	"github.com/google/syzkaller/pkg/compiler"
24	"github.com/google/syzkaller/pkg/hash"
25	"github.com/google/syzkaller/pkg/osutil"
26	"github.com/google/syzkaller/pkg/serializer"
27	"github.com/google/syzkaller/prog"
28	"github.com/google/syzkaller/sys/targets"
29)
30
31var (
32	flagMemProfile = flag.String("memprofile", "", "write a memory profile to the file")
33)
34
35type SyscallData struct {
36	Name     string
37	CallName string
38	NR       int32
39	NeedCall bool
40}
41
42type 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}
52
53type OSData struct {
54	GOOS  string
55	Archs []ArchData
56}
57
58func main() {
59	flag.Parse()
60
61	var oses []OSData
62	for OS, archs := range targets.List {
63		top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), nil)
64		if top == nil {
65			os.Exit(1)
66		}
67		osutil.MkdirAll(filepath.Join("sys", OS, "gen"))
68
69		type Job struct {
70			Target      *targets.Target
71			OK          bool
72			Errors      []string
73			Unsupported map[string]bool
74			ArchData    ArchData
75		}
76		var jobs []*Job
77		for _, target := range archs {
78			jobs = append(jobs, &Job{
79				Target: target,
80			})
81		}
82		sort.Slice(jobs, func(i, j int) bool {
83			return jobs[i].Target.Arch < jobs[j].Target.Arch
84		})
85		var wg sync.WaitGroup
86		wg.Add(len(jobs))
87
88		for _, job := range jobs {
89			job := job
90			go func() {
91				defer wg.Done()
92				eh := func(pos ast.Pos, msg string) {
93					job.Errors = append(job.Errors, fmt.Sprintf("%v: %v\n", pos, msg))
94				}
95				consts := compiler.DeserializeConstsGlob(filepath.Join("sys", OS, "*_"+job.Target.Arch+".const"), eh)
96				if consts == nil {
97					return
98				}
99				prog := compiler.Compile(top, consts, job.Target, eh)
100				if prog == nil {
101					return
102				}
103				job.Unsupported = prog.Unsupported
104
105				sysFile := filepath.Join("sys", OS, "gen", job.Target.Arch+".go")
106				out := new(bytes.Buffer)
107				generate(job.Target, prog, consts, out)
108				rev := hash.String(out.Bytes())
109				fmt.Fprintf(out, "const revision_%v = %q\n", job.Target.Arch, rev)
110				writeSource(sysFile, out.Bytes())
111
112				job.ArchData = generateExecutorSyscalls(job.Target, prog.Syscalls, rev)
113
114				job.OK = true
115			}()
116		}
117		writeEmpty(OS)
118		wg.Wait()
119
120		var syscallArchs []ArchData
121		unsupported := make(map[string]int)
122		for _, job := range jobs {
123			fmt.Printf("generating %v/%v...\n", job.Target.OS, job.Target.Arch)
124			for _, msg := range job.Errors {
125				fmt.Print(msg)
126			}
127			if !job.OK {
128				os.Exit(1)
129			}
130			syscallArchs = append(syscallArchs, job.ArchData)
131			for u := range job.Unsupported {
132				unsupported[u]++
133			}
134			fmt.Printf("\n")
135		}
136		oses = append(oses, OSData{
137			GOOS:  OS,
138			Archs: syscallArchs,
139		})
140
141		for what, count := range unsupported {
142			if count == len(jobs) {
143				failf("%v is unsupported on all arches (typo?)", what)
144			}
145		}
146	}
147
148	writeExecutorSyscalls(oses)
149
150	if *flagMemProfile != "" {
151		f, err := os.Create(*flagMemProfile)
152		if err != nil {
153			failf("could not create memory profile: ", err)
154		}
155		runtime.GC() // get up-to-date statistics
156		if err := pprof.WriteHeapProfile(f); err != nil {
157			failf("could not write memory profile: ", err)
158		}
159		f.Close()
160	}
161}
162
163func generate(target *targets.Target, prg *compiler.Prog, consts map[string]uint64, out io.Writer) {
164	tag := fmt.Sprintf("syz_target,syz_os_%v,syz_arch_%v", target.OS, target.Arch)
165	if target.VMArch != "" {
166		tag += fmt.Sprintf(" syz_target,syz_os_%v,syz_arch_%v", target.OS, target.VMArch)
167	}
168	fmt.Fprintf(out, "// AUTOGENERATED FILE\n")
169	fmt.Fprintf(out, "// +build !syz_target %v\n\n", tag)
170	fmt.Fprintf(out, "package gen\n\n")
171	fmt.Fprintf(out, "import . \"github.com/google/syzkaller/prog\"\n")
172	fmt.Fprintf(out, "import . \"github.com/google/syzkaller/sys/%v\"\n\n", target.OS)
173
174	fmt.Fprintf(out, "func init() {\n")
175	fmt.Fprintf(out, "\tRegisterTarget(&Target{"+
176		"OS: %q, Arch: %q, Revision: revision_%v, PtrSize: %v, "+
177		"PageSize: %v, NumPages: %v, DataOffset: %v, Syscalls: syscalls_%v, "+
178		"Resources: resources_%v, Structs: structDescs_%v, Consts: consts_%v}, "+
179		"InitTarget)\n}\n\n",
180		target.OS, target.Arch, target.Arch, target.PtrSize,
181		target.PageSize, target.NumPages, target.DataOffset,
182		target.Arch, target.Arch, target.Arch, target.Arch)
183
184	fmt.Fprintf(out, "var resources_%v = ", target.Arch)
185	serializer.Write(out, prg.Resources)
186	fmt.Fprintf(out, "\n\n")
187
188	fmt.Fprintf(out, "var structDescs_%v = ", target.Arch)
189	serializer.Write(out, prg.StructDescs)
190	fmt.Fprintf(out, "\n\n")
191
192	fmt.Fprintf(out, "var syscalls_%v = ", target.Arch)
193	serializer.Write(out, prg.Syscalls)
194	fmt.Fprintf(out, "\n\n")
195
196	constArr := make([]prog.ConstValue, 0, len(consts))
197	for name, val := range consts {
198		constArr = append(constArr, prog.ConstValue{Name: name, Value: val})
199	}
200	sort.Slice(constArr, func(i, j int) bool {
201		return constArr[i].Name < constArr[j].Name
202	})
203	fmt.Fprintf(out, "var consts_%v = ", target.Arch)
204	serializer.Write(out, constArr)
205	fmt.Fprintf(out, "\n\n")
206}
207
208func writeEmpty(OS string) {
209	const data = `// AUTOGENERATED FILE
210// This file is needed if OS is completely excluded by build tags.
211package gen
212`
213	writeSource(filepath.Join("sys", OS, "gen", "empty.go"), []byte(data))
214}
215
216func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall, rev string) ArchData {
217	data := ArchData{
218		Revision:   rev,
219		GOARCH:     target.Arch,
220		PageSize:   target.PageSize,
221		NumPages:   target.NumPages,
222		DataOffset: target.DataOffset,
223	}
224	if target.ExecutorUsesForkServer {
225		data.ForkServer = 1
226	}
227	if target.ExecutorUsesShmem {
228		data.Shmem = 1
229	}
230	for _, c := range syscalls {
231		data.Calls = append(data.Calls, SyscallData{
232			Name:     c.Name,
233			CallName: c.CallName,
234			NR:       int32(c.NR),
235			NeedCall: !target.SyscallNumbers || strings.HasPrefix(c.CallName, "syz_"),
236		})
237	}
238	sort.Slice(data.Calls, func(i, j int) bool {
239		return data.Calls[i].Name < data.Calls[j].Name
240	})
241	return data
242}
243
244func writeExecutorSyscalls(oses []OSData) {
245	sort.Slice(oses, func(i, j int) bool {
246		return oses[i].GOOS < oses[j].GOOS
247	})
248	buf := new(bytes.Buffer)
249	if err := defsTempl.Execute(buf, oses); err != nil {
250		failf("failed to execute defs template: %v", err)
251	}
252	writeFile(filepath.FromSlash("executor/defs.h"), buf.Bytes())
253	buf.Reset()
254	if err := syscallsTempl.Execute(buf, oses); err != nil {
255		failf("failed to execute syscalls template: %v", err)
256	}
257	writeFile(filepath.FromSlash("executor/syscalls.h"), buf.Bytes())
258}
259
260func writeSource(file string, data []byte) {
261	src, err := format.Source(data)
262	if err != nil {
263		fmt.Printf("%s\n", data)
264		failf("failed to format output: %v", err)
265	}
266	if oldSrc, err := ioutil.ReadFile(file); err == nil && bytes.Equal(src, oldSrc) {
267		return
268	}
269	writeFile(file, src)
270}
271
272func writeFile(file string, data []byte) {
273	outf, err := os.Create(file)
274	if err != nil {
275		failf("failed to create output file: %v", err)
276	}
277	defer outf.Close()
278	outf.Write(data)
279}
280
281func failf(msg string, args ...interface{}) {
282	fmt.Fprintf(os.Stderr, msg+"\n", args...)
283	os.Exit(1)
284}
285
286var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
287{{range $os := $}}
288#if GOOS_{{$os.GOOS}}
289#define GOOS "{{$os.GOOS}}"
290{{range $arch := $os.Archs}}
291#if GOARCH_{{$arch.GOARCH}}
292#define GOARCH "{{.GOARCH}}"
293#define SYZ_REVISION "{{.Revision}}"
294#define SYZ_EXECUTOR_USES_FORK_SERVER {{.ForkServer}}
295#define SYZ_EXECUTOR_USES_SHMEM {{.Shmem}}
296#define SYZ_PAGE_SIZE {{.PageSize}}
297#define SYZ_NUM_PAGES {{.NumPages}}
298#define SYZ_DATA_OFFSET {{.DataOffset}}
299#endif
300{{end}}
301#endif
302{{end}}
303`))
304
305var syscallsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
306{{range $os := $}}
307#if GOOS_{{$os.GOOS}}
308{{range $arch := $os.Archs}}
309#if GOARCH_{{$arch.GOARCH}}
310const call_t syscalls[] = {
311{{range $c := $arch.Calls}}	{"{{$c.Name}}", {{$c.NR}}{{if $c.NeedCall}}, (syscall_t){{$c.CallName}}{{end}}},
312{{end}}
313};
314#endif
315{{end}}
316#endif
317{{end}}
318`))
319