From 874c5754bb22dbf77d6b600ff91f0f4f1fc5073a Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 12 Oct 2015 10:16:57 +0200 Subject: initial commit --- sysgen/parser.go | 92 ++++++++ sysgen/sysgen.go | 649 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 741 insertions(+) create mode 100644 sysgen/parser.go create mode 100644 sysgen/sysgen.go (limited to 'sysgen') diff --git a/sysgen/parser.go b/sysgen/parser.go new file mode 100644 index 000000000..92c27c58a --- /dev/null +++ b/sysgen/parser.go @@ -0,0 +1,92 @@ +// Copyright 2015 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 ( + "bufio" + "fmt" + "io" + "os" +) + +type Parser struct { + r *bufio.Scanner + s string + i int + l int +} + +func NewParser(r io.Reader) *Parser { + return &Parser{r: bufio.NewScanner(r)} +} + +func (p *Parser) Scan() bool { + if !p.r.Scan() { + if err := p.r.Err(); err != nil { + failf("failed to read input file: %v", err) + } + return false + } + p.s = p.r.Text() + p.i = 0 + p.l++ + return true +} + +func (p *Parser) Str() string { + return p.s +} + +func (p *Parser) EOF() bool { + return p.i == len(p.s) +} + +func (p *Parser) Char() byte { + if p.EOF() { + p.failf("unexpected eof") + } + return p.s[p.i] +} + +func (p *Parser) Parse(ch byte) { + if p.EOF() { + p.failf("want %s, got EOF", string(ch)) + } + if p.s[p.i] != ch { + p.failf("want '%v', got '%v'", string(ch), string(p.s[p.i])) + } + p.i++ + p.SkipWs() +} + +func (p *Parser) SkipWs() { + for p.i < len(p.s) && (p.s[p.i] == ' ' || p.s[p.i] == '\t') { + p.i++ + } +} + +func (p *Parser) Ident() string { + i := p.i + for p.i < len(p.s) && + (p.s[p.i] >= 'a' && p.s[p.i] <= 'z' || + p.s[p.i] >= 'A' && p.s[p.i] <= 'Z' || + p.s[p.i] >= '0' && p.s[p.i] <= '9' || + p.s[p.i] == '_' || p.s[p.i] == '$') { // $ is for n-way syscalls (like ptrace$peek) + p.i++ + } + if i == p.i { + p.failf("failed to parse identifier at pos %v", i) + } + if ch := p.s[i]; ch >= '0' && ch <= '9' { + // p.failf("identifier starts with a digit at pos %v", i) + } + s := p.s[i:p.i] + p.SkipWs() + return s +} + +func (p *Parser) failf(msg string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "line #%v: %v\n", p.l, p.s) + failf(msg, args...) +} diff --git a/sysgen/sysgen.go b/sysgen/sysgen.go new file mode 100644 index 000000000..e8a3c62a1 --- /dev/null +++ b/sysgen/sysgen.go @@ -0,0 +1,649 @@ +// Copyright 2015 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 ( + "bufio" + "bytes" + "fmt" + "go/format" + "io" + "io/ioutil" + "os" + "os/exec" + "sort" + "strconv" + "strings" +) + +func main() { + if len(os.Args) != 2 { + failf("usage: gen input_file") + } + inf, err := os.Open(os.Args[1]) + if err != nil { + failf("failed to open input file: %v", err) + } + defer inf.Close() + + includes, defines, syscalls, structs, unnamed, flags := parse(bufio.NewReader(inf)) + intFlags, flagVals := compileFlags(includes, defines, flags) + + out := new(bytes.Buffer) + generate(syscalls, structs, unnamed, intFlags, out) + writeSource("sys.go", out.Bytes()) + + out = new(bytes.Buffer) + generateConsts(flagVals, out) + writeSource("../prog/consts.go", out.Bytes()) + + out = new(bytes.Buffer) + generateHeader(syscalls, out) + writeFile("../executor/syscalls.h", out.Bytes()) +} + +type Syscall struct { + Name string + CallName string + Args [][]string + Ret []string +} + +type Struct struct { + Name string + Flds [][]string +} + +type Flag struct { + Name string + Values []string +} + +func generate(syscalls []Syscall, structs map[string]Struct, unnamed map[string][]string, flags map[string][]string, out io.Writer) { + fmt.Fprintf(out, "// AUTOGENERATED FILE\n") + fmt.Fprintf(out, "package sys\n\n") + + fmt.Fprintf(out, "var Calls = []*Call {\n") + for i, s := range syscalls { + fmt.Fprintf(out, "&Call{ID: %v, Name: \"%v\", CallName: \"%v\"", i, s.Name, s.CallName) + if len(s.Ret) != 0 { + fmt.Fprintf(out, ", Ret: ") + generateArg("ret", s.Ret[0], s.Ret[1:], structs, unnamed, flags, false, out) + } + fmt.Fprintf(out, ", Args: []Type{") + for i, a := range s.Args { + if i != 0 { + fmt.Fprintf(out, ", ") + } + generateArg(a[0], a[1], a[2:], structs, unnamed, flags, false, out) + } + fmt.Fprintf(out, "}},\n") + } + fmt.Fprintf(out, "}\n") +} + +func generateArg(name, typ string, a []string, structs map[string]Struct, unnamed map[string][]string, flags map[string][]string, isField bool, out io.Writer) { + name = "\"" + name + "\"" + opt := false + for i, v := range a { + if v == "opt" { + opt = true + copy(a[i:], a[i+1:]) + a = a[:len(a)-1] + break + } + } + common := func() string { + return fmt.Sprintf("TypeCommon: TypeCommon{TypeName: %v, IsOptional: %v}", name, opt) + } + switch typ { + case "fd": + if len(a) == 0 { + a = append(a, "") + } + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResFD, Subkind: %v}", common(), fmtFdKind(a[0])) + case "io_ctx": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResIOCtx}", common()) + case "ipc": + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResIPC, Subkind: %v}", common(), fmtIPCKind(a[0])) + case "key": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResKey}", common()) + case "inotifydesc": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResInotifyDesc}", common()) + case "timerid": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResTimerid}", common()) + case "pid": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResPid}", common()) + case "uid": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResUid}", common()) + case "gid": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ResourceType{%v, Kind: ResGid}", common()) + case "fileoff": + var size uint64 + if isField { + if want := 2; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + size = typeToSize(a[1]) + } else { + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + } + fmt.Fprintf(out, "FileoffType{%v, File: \"%v\", TypeSize: %v}", common(), a[0], size) + case "buffer": + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + commonHdr := common() + opt = false + fmt.Fprintf(out, "PtrType{%v, Dir: %v, Type: BufferType{%v, Kind: BufferBlob}}", commonHdr, fmtDir(a[0]), common()) + case "string": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + commonHdr := common() + opt = false + fmt.Fprintf(out, "PtrType{%v, Dir: %v, Type: BufferType{%v, Kind: BufferString}}", commonHdr, fmtDir("in"), common()) + case "sockaddr": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "BufferType{%v, Kind: BufferSockaddr}", common()) + case "vma": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "VmaType{%v}", common()) + case "len": + var size uint64 + if isField { + if want := 2; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + size = typeToSize(a[1]) + } else { + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + } + fmt.Fprintf(out, "LenType{%v, Buf: \"%v\", TypeSize: %v}", common(), a[0], size) + case "flags": + var size uint64 + if isField { + if want := 2; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + size = typeToSize(a[1]) + } else { + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + } + vals := flags[a[0]] + if len(vals) == 0 { + failf("unknown flag %v", a[0]) + } + fmt.Fprintf(out, "FlagsType{%v, TypeSize: %v, Vals: []uintptr{%v}}", common(), size, strings.Join(vals, ",")) + case "int8", "int16", "int32", "int64", "intptr": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "IntType{%v, TypeSize: %v}", common(), typeToSize(typ)) + case "signalno": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "IntType{%v, TypeSize: 4, Limit: 130}", common()) + case "filename": + if want := 0; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + commonHdr := common() + opt = false + fmt.Fprintf(out, "PtrType{%v, Dir: DirIn, Type: FilenameType{%v}}", commonHdr, common()) + case "array": + if want := 1; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "ArrayType{%v, Type: %v}", common(), generateType(a[0], structs, unnamed, flags)) + case "ptr": + if want := 2; len(a) != want { + failf("wrong number of arguments for %v arg %v, want %v, got %v", typ, name, want, len(a)) + } + fmt.Fprintf(out, "PtrType{%v, Type: %v, Dir: %v}", common(), generateType(a[1], structs, unnamed, flags), fmtDir(a[0])) + default: + if strings.HasPrefix(typ, "unnamed") { + if inner, ok := unnamed[typ]; ok { + generateArg("", inner[0], inner[1:], structs, unnamed, flags, isField, out) + return + } + failf("unknown unnamed type '%v'", typ) + } + if str, ok := structs[typ]; ok { + fmt.Fprintf(out, "StructType{TypeCommon: TypeCommon{TypeName: \"%v\", IsOptional: %v}, Fields: []Type{", str.Name, false) + for i, a := range str.Flds { + if i != 0 { + fmt.Fprintf(out, ", ") + } + generateArg(a[0], a[1], a[2:], structs, unnamed, flags, true, out) + } + fmt.Fprintf(out, "}}") + return + } + failf("unknown arg type \"%v\" for %v", typ, name) + } +} + +func generateType(typ string, structs map[string]Struct, unnamed map[string][]string, flags map[string][]string) string { + buf := new(bytes.Buffer) + generateArg("", typ, nil, structs, unnamed, flags, true, buf) + return buf.String() +} + +func fmtFdKind(s string) string { + switch s { + case "": + return "ResAny" + case "file": + return "FdFile" + case "sock": + return "FdSock" + case "pipe": + return "FdPipe" + case "signal": + return "FdSignal" + case "event": + return "FdEvent" + case "timer": + return "FdTimer" + case "epoll": + return "FdEpoll" + case "dir": + return "FdDir" + case "mq": + return "FdMq" + case "inotify": + return "FdInotify" + case "fanotify": + return "FdFanotify" + default: + failf("bad fd type %v", s) + return "" + } +} + +func fmtIPCKind(s string) string { + switch s { + case "msq": + return "IPCMsq" + case "sem": + return "IPCSem" + case "shm": + return "IPCShm" + default: + failf("bad ipc type %v", s) + return "" + } +} + +func fmtDir(s string) string { + switch s { + case "in": + return "DirIn" + case "out": + return "DirOut" + case "inout": + return "DirInOut" + default: + failf("bad direction %v", s) + return "" + } +} + +func typeToSize(typ string) uint64 { + switch typ { + case "int8", "int16", "int32", "int64", "intptr": + default: + failf("unknown type %v", typ) + } + sz := int64(64) // TODO: assume that pointer is 8 bytes for now + if typ != "intptr" { + sz, _ = strconv.ParseInt(typ[3:], 10, 64) + } + return uint64(sz / 8) +} + +type F struct { + name string + val string +} + +type FlagArray []F + +func (a FlagArray) Len() int { return len(a) } +func (a FlagArray) Less(i, j int) bool { return a[i].name < a[j].name } +func (a FlagArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func generateConsts(flags map[string]string, out io.Writer) { + var ff []F + for k, v := range flags { + ff = append(ff, F{k, v}) + } + sort.Sort(FlagArray(ff)) + + fmt.Fprintf(out, "// AUTOGENERATED FILE\n") + fmt.Fprintf(out, "package prog\n\n") + fmt.Fprintf(out, "const (\n") + for _, f := range ff { + fmt.Fprintf(out, " %v = %v\n", f.name, f.val) + } + fmt.Fprintf(out, ")\n") +} + +func generateHeader(syscalls []Syscall, out io.Writer) { + fmt.Fprint(out, `// AUTOGENERATED FILE + +struct call_t { + const char* name; + int sys_nr; +}; + +call_t syscalls[] = { +`) + for _, s := range syscalls { + fmt.Fprintf(out, "\t{\"%v\", __NR_%v},\n", s.Name, s.CallName) + } + fmt.Fprintf(out, "};\n") +} + +func compileFlags(includes []string, defines map[string]string, flags []Flag) (map[string][]string, map[string]string) { + vals := make(map[string]string) + for _, f := range flags { + for _, v := range f.Values { + vals[v] = "" + } + } + for k := range defines { + vals[k] = "" + } + valArray := make([]string, 0, len(vals)) + for k := range vals { + valArray = append(valArray, k) + } + flagVals := fetchValues(valArray, includes, defines) + for i, f := range valArray { + vals[f] = flagVals[i] + } + res := make(map[string][]string) + for _, f := range flags { + var arr []string + for _, v := range f.Values { + arr = append(arr, vals[v]) + } + if res[f.Name] != nil { + failf("flag %v is defined multiple times", f.Name) + } + res[f.Name] = arr + } + ids := make(map[string]string) + for k, v := range vals { + if isIdentifier(k) { + ids[k] = v + } + } + return res, ids +} + +func fetchValues(vals []string, includes []string, defines map[string]string) []string { + includeText := "" + for _, inc := range includes { + includeText += fmt.Sprintf("#include <%v>\n", inc) + } + definesText := "" + for k, v := range defines { + definesText += fmt.Sprintf("#ifndef %v\n#define %v %v\n#endif\n", k, k, v) + } + src := strings.Replace(fetchSrc, "[[INCLUDES]]", includeText, 1) + src = strings.Replace(src, "[[DEFAULTS]]", definesText, 1) + src = strings.Replace(src, "[[VALS]]", strings.Join(vals, ","), 1) + bin, err := ioutil.TempFile("", "") + if err != nil { + failf("failed to create temp file: %v", err) + } + bin.Close() + defer os.Remove(bin.Name()) + + cmd := exec.Command("gcc", "-x", "c", "-", "-o", bin.Name()) + cmd.Stdin = strings.NewReader(src) + out, err := cmd.CombinedOutput() + if err != nil { + failf("failed to run gcc: %v\n%v", err, string(out)) + } + + out, err = exec.Command(bin.Name()).CombinedOutput() + if err != nil { + failf("failed to flags binary: %v\n%v", err, string(out)) + } + + flagVals := strings.Split(string(out), " ") + if len(flagVals) != len(vals) { + failf("fetched wrong number of values") + } + for _, v := range flagVals { + _, err := strconv.ParseUint(v, 10, 64) + if err != nil { + failf("failed to parse value: %v (%v)", err, v) + } + } + return flagVals +} + +func isIdentifier(s string) bool { + for i, c := range s { + if c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || i > 0 && (c >= '0' && c <= '9') { + continue + } + return false + } + return true +} + +func parse(in io.Reader) (includes []string, defines map[string]string, syscalls []Syscall, structs map[string]Struct, unnamed map[string][]string, flags []Flag) { + p := NewParser(in) + defines = make(map[string]string) + structs = make(map[string]Struct) + unnamed = make(map[string][]string) + var str *Struct + for p.Scan() { + if p.EOF() || p.Char() == '#' { + continue + } + if str != nil { + // Parsing a struct. + if p.Char() == '}' { + p.Parse('}') + if _, ok := structs[str.Name]; ok { + failf("%v struct is defined multiple times", str.Name) + } + structs[str.Name] = *str + str = nil + } else { + p.SkipWs() + fld := []string{p.Ident()} + fld = append(fld, parseType(p, unnamed)...) + str.Flds = append(str.Flds, fld) + } + } else { + name := p.Ident() + if name == "include" { + p.Parse('<') + var include []byte + for { + ch := p.Char() + if ch == '>' { + break + } + p.Parse(ch) + include = append(include, ch) + } + p.Parse('>') + includes = append(includes, string(include)) + } else if name == "define" { + key := p.Ident() + var val []byte + for !p.EOF() { + ch := p.Char() + p.Parse(ch) + val = append(val, ch) + } + if defines[key] != "" { + failf("%v define is defined multiple times", key) + } + defines[key] = string(val) + } else { + switch p.Char() { + case '(': + // syscall + p.Parse('(') + var args [][]string + for p.Char() != ')' { + arg := []string{p.Ident()} + arg = append(arg, parseType(p, unnamed)...) + args = append(args, arg) + if p.Char() != ')' { + p.Parse(',') + } + } + p.Parse(')') + var ret []string + if !p.EOF() { + ret = parseType(p, unnamed) + } + callName := name + if idx := strings.IndexByte(callName, '$'); idx != -1 { + callName = callName[:idx] + } + syscalls = append(syscalls, Syscall{name, callName, args, ret}) + case '=': + // flag + p.Parse('=') + vals := []string{p.Ident()} + for !p.EOF() { + p.Parse(',') + vals = append(vals, p.Ident()) + } + flags = append(flags, Flag{name, vals}) + case '{': + p.Parse('{') + str = &Struct{Name: name} + default: + failf("bad line (%v)", p.Str()) + } + } + } + if !p.EOF() { + failf("trailing data (%v)", p.Str()) + } + } + return +} + +func parseType(p *Parser, unnamed map[string][]string) []string { + return parseType1(p, unnamed, p.Ident()) +} + +var unnamedSeq int + +func parseType1(p *Parser, unnamed map[string][]string, name string) []string { + typ := []string{name} + if !p.EOF() && p.Char() == '[' { + p.Parse('[') + for { + id := p.Ident() + if p.Char() == '[' { + inner := parseType1(p, unnamed, id) + id = fmt.Sprintf("unnamed%v", unnamedSeq) + unnamedSeq++ + unnamed[id] = inner + } + typ = append(typ, id) + if p.Char() == ']' { + break + } + p.Parse(',') + } + p.Parse(']') + } + return typ +} + +func writeSource(file string, data []byte) { + src, err := format.Source(data) + if err != nil { + fmt.Printf("%s\n", data) + failf("failed to format output: %v", err) + } + writeFile(file, src) +} + +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 fetchSrc = ` +#define _GNU_SOURCE +#include +[[INCLUDES]] + +[[DEFAULTS]] + +int main() { + int i; + unsigned long vals[] = {[[VALS]]}; + for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) { + if (i != 0) + printf(" "); + printf("%lu", vals[i]); + } + return 0; +} +` -- cgit mrf-deployment