| // 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/prog" |
| "github.com/google/syzkaller/sys/targets" |
| ) |
| |
| type ConstInfo struct { |
| Consts []string |
| Includes []string |
| Incdirs []string |
| Defines map[string]string |
| } |
| |
| func ExtractConsts(desc *ast.Description, target *targets.Target, eh ast.ErrorHandler) map[string]*ConstInfo { |
| res := Compile(desc, nil, target, eh) |
| if res == nil { |
| return nil |
| } |
| return res.fileConsts |
| } |
| |
| // extractConsts returns list of literal constants and other info required for const value extraction. |
| func (comp *compiler) extractConsts() map[string]*ConstInfo { |
| infos := make(map[string]*constInfo) |
| for _, decl := range comp.desc.Nodes { |
| pos, _, _ := decl.Info() |
| info := getConstInfo(infos, pos) |
| switch n := decl.(type) { |
| case *ast.Include: |
| info.includeArray = append(info.includeArray, n.File.Value) |
| case *ast.Incdir: |
| info.incdirArray = append(info.incdirArray, n.Dir.Value) |
| 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 |
| info.defines[name] = v |
| info.consts[name] = true |
| case *ast.Call: |
| if comp.target.SyscallNumbers && !strings.HasPrefix(n.CallName, "syz_") { |
| info.consts[comp.target.SyscallPrefix+n.CallName] = true |
| } |
| } |
| } |
| |
| for _, decl := range comp.desc.Nodes { |
| switch decl.(type) { |
| case *ast.Call, *ast.Struct, *ast.Resource, *ast.TypeDef: |
| comp.foreachType(decl, func(t *ast.Type, desc *typeDesc, |
| args []*ast.Type, _ prog.IntTypeCommon) { |
| for i, arg := range args { |
| if desc.Args[i].Type.Kind == kindInt { |
| if arg.Ident != "" { |
| info := getConstInfo(infos, arg.Pos) |
| info.consts[arg.Ident] = true |
| } |
| if arg.Ident2 != "" { |
| info := getConstInfo(infos, arg.Pos2) |
| info.consts[arg.Ident2] = true |
| } |
| } |
| } |
| }) |
| } |
| } |
| |
| for _, decl := range comp.desc.Nodes { |
| switch n := decl.(type) { |
| case *ast.Struct: |
| for _, attr := range n.Attrs { |
| if attr.Ident == "size" { |
| info := getConstInfo(infos, attr.Pos) |
| info.consts[attr.Args[0].Ident] = true |
| } |
| } |
| } |
| } |
| |
| comp.desc.Walk(ast.Recursive(func(n0 ast.Node) { |
| if n, ok := n0.(*ast.Int); ok { |
| info := getConstInfo(infos, n.Pos) |
| info.consts[n.Ident] = true |
| } |
| })) |
| |
| return convertConstInfo(infos) |
| } |
| |
| type constInfo struct { |
| consts map[string]bool |
| defines map[string]string |
| includeArray []string |
| incdirArray []string |
| } |
| |
| func getConstInfo(infos map[string]*constInfo, pos ast.Pos) *constInfo { |
| info := infos[pos.File] |
| if info == nil { |
| info = &constInfo{ |
| consts: make(map[string]bool), |
| defines: make(map[string]string), |
| } |
| infos[pos.File] = info |
| } |
| return info |
| } |
| |
| func convertConstInfo(infos map[string]*constInfo) map[string]*ConstInfo { |
| res := make(map[string]*ConstInfo) |
| for file, info := range infos { |
| res[file] = &ConstInfo{ |
| Consts: toArray(info.consts), |
| Includes: info.includeArray, |
| Incdirs: info.incdirArray, |
| Defines: info.defines, |
| } |
| } |
| return res |
| } |
| |
| // assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls. |
| func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) { |
| for _, decl := range comp.desc.Nodes { |
| c, ok := decl.(*ast.Call) |
| if !ok || strings.HasPrefix(c.CallName, "syz_") { |
| continue |
| } |
| str := comp.target.SyscallPrefix + c.CallName |
| nr, ok := consts[str] |
| 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) |
| } |
| } |
| } |
| |
| // patchConsts replaces all symbolic consts with their numeric values taken from consts map. |
| // Updates desc and returns set of unsupported syscalls and flags. |
| func (comp *compiler) patchConsts(consts map[string]uint64) { |
| 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.Value, &v.Ident, consts, nil) { |
| values = append(values, v) |
| } |
| } |
| n.Values = values |
| case *ast.Resource, *ast.Struct, *ast.Call, *ast.TypeDef: |
| // Walk whole tree and replace consts in Type's and Int's. |
| missing := "" |
| comp.foreachType(decl, func(_ *ast.Type, desc *typeDesc, |
| args []*ast.Type, _ prog.IntTypeCommon) { |
| for i, arg := range args { |
| if desc.Args[i].Type.Kind == kindInt { |
| comp.patchIntConst(&arg.Value, &arg.Ident, consts, &missing) |
| if arg.HasColon { |
| comp.patchIntConst(&arg.Value2, |
| &arg.Ident2, consts, &missing) |
| } |
| } |
| } |
| }) |
| if n, ok := decl.(*ast.Resource); ok { |
| for _, v := range n.Values { |
| comp.patchIntConst(&v.Value, &v.Ident, consts, &missing) |
| } |
| } |
| if n, ok := decl.(*ast.Struct); ok { |
| for _, attr := range n.Attrs { |
| if attr.Ident == "size" { |
| sz := attr.Args[0] |
| comp.patchIntConst(&sz.Value, &sz.Ident, consts, &missing) |
| } |
| } |
| } |
| if missing == "" { |
| 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) |
| } |
| if c, ok := decl.(*ast.Call); ok { |
| c.NR = ^uint64(0) // mark as unused to not generate it |
| } |
| } |
| } |
| } |
| |
| func (comp *compiler) patchIntConst(val *uint64, id *string, consts map[string]uint64, missing *string) bool { |
| if *id == "" { |
| return true |
| } |
| v, ok := consts[*id] |
| if !ok { |
| if missing != nil && *missing == "" { |
| *missing = *id |
| } |
| } |
| *val = v |
| return ok |
| } |
| |
| func SerializeConsts(consts map[string]uint64, undeclared map[string]bool) []byte { |
| type nameValuePair struct { |
| declared bool |
| name string |
| val uint64 |
| } |
| var nv []nameValuePair |
| for k, v := range consts { |
| nv = append(nv, nameValuePair{true, k, v}) |
| } |
| for k := range undeclared { |
| nv = append(nv, nameValuePair{false, k, 0}) |
| } |
| 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 { |
| if x.declared { |
| fmt.Fprintf(buf, "%v = %v\n", x.name, x.val) |
| } else { |
| fmt.Fprintf(buf, "# %v is not set\n", x.name) |
| } |
| } |
| 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 _, dup := consts[name]; dup { |
| 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 |
| } |