| // Copyright 2017 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 compiler |
| |
| import ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "io/ioutil" |
| "path/filepath" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "github.com/google/syzkaller/pkg/ast" |
| "github.com/google/syzkaller/sys/targets" |
| ) |
| |
| type ConstInfo struct { |
| Consts []string |
| Includes []string |
| Incdirs []string |
| Defines map[string]string |
| } |
| |
| // ExtractConsts returns list of literal constants and other info required const value extraction. |
| func ExtractConsts(desc *ast.Description, target *targets.Target, eh0 ast.ErrorHandler) *ConstInfo { |
| errors := 0 |
| eh := func(pos ast.Pos, msg string, args ...interface{}) { |
| errors++ |
| msg = fmt.Sprintf(msg, args...) |
| if eh0 != nil { |
| eh0(pos, msg) |
| } else { |
| ast.LoggingHandler(pos, msg) |
| } |
| } |
| info := &ConstInfo{ |
| Defines: make(map[string]string), |
| } |
| includeMap := make(map[string]bool) |
| incdirMap := make(map[string]bool) |
| constMap := make(map[string]bool) |
| |
| ast.Walk(desc, func(n1 ast.Node) { |
| switch n := n1.(type) { |
| case *ast.Include: |
| file := n.File.Value |
| if includeMap[file] { |
| eh(n.Pos, "duplicate include %q", file) |
| } |
| includeMap[file] = true |
| info.Includes = append(info.Includes, file) |
| case *ast.Incdir: |
| dir := n.Dir.Value |
| if incdirMap[dir] { |
| eh(n.Pos, "duplicate incdir %q", dir) |
| } |
| incdirMap[dir] = true |
| info.Incdirs = append(info.Incdirs, dir) |
| case *ast.Define: |
| v := fmt.Sprint(n.Value.Value) |
| switch { |
| case n.Value.CExpr != "": |
| v = n.Value.CExpr |
| case n.Value.Ident != "": |
| v = n.Value.Ident |
| } |
| name := n.Name.Name |
| if info.Defines[name] != "" { |
| eh(n.Pos, "duplicate define %v", name) |
| } |
| info.Defines[name] = v |
| constMap[name] = true |
| case *ast.Call: |
| if target.SyscallNumbers && !strings.HasPrefix(n.CallName, "syz_") { |
| constMap[target.SyscallPrefix+n.CallName] = true |
| } |
| case *ast.Type: |
| if c := typeConstIdentifier(n); c != nil { |
| constMap[c.Ident] = true |
| constMap[c.Ident2] = true |
| } |
| case *ast.Int: |
| constMap[n.Ident] = true |
| } |
| }) |
| |
| if errors != 0 { |
| return nil |
| } |
| info.Consts = toArray(constMap) |
| return info |
| } |
| |
| // assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls |
| // and removes no longer irrelevant nodes from the tree (comments, new lines, etc). |
| func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) { |
| // Pseudo syscalls starting from syz_ are assigned numbers starting from syzbase. |
| // Note: the numbers must be stable (not depend on file reading order, etc), |
| // so we have to do it in 2 passes. |
| const syzbase = 1000000 |
| syzcalls := make(map[string]bool) |
| for _, decl := range comp.desc.Nodes { |
| c, ok := decl.(*ast.Call) |
| if !ok { |
| continue |
| } |
| if strings.HasPrefix(c.CallName, "syz_") { |
| syzcalls[c.CallName] = true |
| } |
| } |
| syznr := make(map[string]uint64) |
| for i, name := range toArray(syzcalls) { |
| syznr[name] = syzbase + uint64(i) |
| } |
| |
| var top []ast.Node |
| for _, decl := range comp.desc.Nodes { |
| switch decl.(type) { |
| case *ast.Call: |
| c := decl.(*ast.Call) |
| if strings.HasPrefix(c.CallName, "syz_") { |
| c.NR = syznr[c.CallName] |
| top = append(top, decl) |
| continue |
| } |
| if !comp.target.SyscallNumbers { |
| top = append(top, decl) |
| continue |
| } |
| // Lookup in consts. |
| str := comp.target.SyscallPrefix + c.CallName |
| nr, ok := consts[str] |
| top = append(top, decl) |
| if ok { |
| c.NR = nr |
| continue |
| } |
| c.NR = ^uint64(0) // mark as unused to not generate it |
| name := "syscall " + c.CallName |
| if !comp.unsupported[name] { |
| comp.unsupported[name] = true |
| comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v", |
| c.CallName, str) |
| } |
| case *ast.IntFlags, *ast.Resource, *ast.Struct, *ast.StrFlags: |
| top = append(top, decl) |
| case *ast.NewLine, *ast.Comment, *ast.Include, *ast.Incdir, *ast.Define: |
| // These are not needed anymore. |
| default: |
| panic(fmt.Sprintf("unknown node type: %#v", decl)) |
| } |
| } |
| comp.desc.Nodes = top |
| } |
| |
| // patchConsts replaces all symbolic consts with their numeric values taken from consts map. |
| // Updates desc and returns set of unsupported syscalls and flags. |
| // After this pass consts are not needed for compilation. |
| func (comp *compiler) patchConsts(consts map[string]uint64) { |
| var top []ast.Node |
| for _, decl := range comp.desc.Nodes { |
| switch decl.(type) { |
| case *ast.IntFlags: |
| // Unsupported flag values are dropped. |
| n := decl.(*ast.IntFlags) |
| var values []*ast.Int |
| for _, v := range n.Values { |
| if comp.patchIntConst(v.Pos, &v.Value, &v.Ident, consts, nil) { |
| values = append(values, v) |
| } |
| } |
| n.Values = values |
| top = append(top, n) |
| case *ast.StrFlags: |
| top = append(top, decl) |
| case *ast.Resource, *ast.Struct, *ast.Call: |
| // Walk whole tree and replace consts in Int's and Type's. |
| missing := "" |
| ast.WalkNode(decl, func(n0 ast.Node) { |
| switch n := n0.(type) { |
| case *ast.Int: |
| comp.patchIntConst(n.Pos, &n.Value, &n.Ident, consts, &missing) |
| case *ast.Type: |
| if c := typeConstIdentifier(n); c != nil { |
| comp.patchIntConst(c.Pos, &c.Value, &c.Ident, |
| consts, &missing) |
| if c.HasColon { |
| comp.patchIntConst(c.Pos2, &c.Value2, &c.Ident2, |
| consts, &missing) |
| } |
| } |
| } |
| }) |
| if missing == "" { |
| top = append(top, decl) |
| continue |
| } |
| // Produce a warning about unsupported syscall/resource/struct. |
| // TODO(dvyukov): we should transitively remove everything that |
| // depends on unsupported things. |
| // Potentially we still can get, say, a bad int range error |
| // due to the 0 const value. |
| pos, typ, name := decl.Info() |
| if id := typ + " " + name; !comp.unsupported[id] { |
| comp.unsupported[id] = true |
| comp.warning(pos, "unsupported %v: %v due to missing const %v", |
| typ, name, missing) |
| } |
| // We have to keep partially broken resources and structs, |
| // because otherwise their usages will error. |
| top = append(top, decl) |
| if c, ok := decl.(*ast.Call); ok { |
| c.NR = ^uint64(0) // mark as unused to not generate it |
| } |
| } |
| } |
| comp.desc.Nodes = top |
| } |
| |
| func (comp *compiler) patchIntConst(pos ast.Pos, val *uint64, id *string, |
| consts map[string]uint64, missing *string) bool { |
| if *id == "" { |
| return true |
| } |
| v, ok := consts[*id] |
| if !ok { |
| name := "const " + *id |
| if !comp.unsupported[name] { |
| comp.unsupported[name] = true |
| comp.warning(pos, "unsupported const: %v", *id) |
| } |
| if missing != nil && *missing == "" { |
| *missing = *id |
| } |
| } |
| *val = v |
| *id = "" |
| return ok |
| } |
| |
| // typeConstIdentifier returns type arg that is an integer constant (subject for const patching), if any. |
| func typeConstIdentifier(n *ast.Type) *ast.Type { |
| // TODO: see if we can extract this info from typeDesc/typeArg. |
| if n.Ident == "const" && len(n.Args) > 0 { |
| return n.Args[0] |
| } |
| if n.Ident == "array" && len(n.Args) > 1 && n.Args[1].Ident != "opt" { |
| return n.Args[1] |
| } |
| if n.Ident == "vma" && len(n.Args) > 0 && n.Args[0].Ident != "opt" { |
| return n.Args[0] |
| } |
| if n.Ident == "string" && len(n.Args) > 1 && n.Args[1].Ident != "opt" { |
| return n.Args[1] |
| } |
| if n.Ident == "csum" && len(n.Args) > 2 && n.Args[1].Ident == "pseudo" { |
| return n.Args[2] |
| } |
| switch n.Ident { |
| case "int8", "int16", "int16be", "int32", "int32be", "int64", "int64be", "intptr": |
| if len(n.Args) > 0 && n.Args[0].Ident != "opt" { |
| return n.Args[0] |
| } |
| } |
| return nil |
| } |
| |
| func SerializeConsts(consts map[string]uint64) []byte { |
| type nameValuePair struct { |
| name string |
| val uint64 |
| } |
| var nv []nameValuePair |
| for k, v := range consts { |
| nv = append(nv, nameValuePair{k, v}) |
| } |
| sort.Slice(nv, func(i, j int) bool { |
| return nv[i].name < nv[j].name |
| }) |
| |
| buf := new(bytes.Buffer) |
| fmt.Fprintf(buf, "# AUTOGENERATED FILE\n") |
| for _, x := range nv { |
| fmt.Fprintf(buf, "%v = %v\n", x.name, x.val) |
| } |
| return buf.Bytes() |
| } |
| |
| func DeserializeConsts(data []byte, file string, eh ast.ErrorHandler) map[string]uint64 { |
| consts := make(map[string]uint64) |
| pos := ast.Pos{ |
| File: file, |
| Line: 1, |
| } |
| ok := true |
| s := bufio.NewScanner(bytes.NewReader(data)) |
| for ; s.Scan(); pos.Line++ { |
| line := s.Text() |
| if line == "" || line[0] == '#' { |
| continue |
| } |
| eq := strings.IndexByte(line, '=') |
| if eq == -1 { |
| eh(pos, "expect '='") |
| ok = false |
| continue |
| } |
| name := strings.TrimSpace(line[:eq]) |
| val, err := strconv.ParseUint(strings.TrimSpace(line[eq+1:]), 0, 64) |
| if err != nil { |
| eh(pos, fmt.Sprintf("failed to parse int: %v", err)) |
| ok = false |
| continue |
| } |
| if _, ok := consts[name]; ok { |
| eh(pos, fmt.Sprintf("duplicate const %q", name)) |
| ok = false |
| continue |
| } |
| consts[name] = val |
| } |
| if err := s.Err(); err != nil { |
| eh(pos, fmt.Sprintf("failed to parse: %v", err)) |
| ok = false |
| } |
| if !ok { |
| return nil |
| } |
| return consts |
| } |
| |
| func DeserializeConstsGlob(glob string, eh ast.ErrorHandler) map[string]uint64 { |
| if eh == nil { |
| eh = ast.LoggingHandler |
| } |
| files, err := filepath.Glob(glob) |
| if err != nil { |
| eh(ast.Pos{}, fmt.Sprintf("failed to find const files: %v", err)) |
| return nil |
| } |
| if len(files) == 0 { |
| eh(ast.Pos{}, fmt.Sprintf("no const files matched by glob %q", glob)) |
| return nil |
| } |
| consts := make(map[string]uint64) |
| for _, f := range files { |
| data, err := ioutil.ReadFile(f) |
| if err != nil { |
| eh(ast.Pos{}, fmt.Sprintf("failed to read const file: %v", err)) |
| return nil |
| } |
| consts1 := DeserializeConsts(data, filepath.Base(f), eh) |
| if consts1 == nil { |
| consts = nil |
| } |
| if consts != nil { |
| for n, v := range consts1 { |
| if old, ok := consts[n]; ok && old != v { |
| eh(ast.Pos{}, fmt.Sprintf( |
| "different values for const %q: %v vs %v", n, v, old)) |
| return nil |
| } |
| consts[n] = v |
| } |
| } |
| } |
| return consts |
| } |