// 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
|
}
|