blob: 06f941e3dd0bd76da7977825bc6b44915eaba384 [file] [log] [blame]
// Copyright 2020 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"
"encoding/binary"
"fmt"
"os"
"regexp"
"sort"
"strconv"
"github.com/google/syzkaller/pkg/cover/backend"
"github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/sys/targets"
)
func (mgr *Manager) createCoverageFilter() (map[uint32]uint32, map[uint32]uint32, error) {
if !mgr.cfg.HasCovFilter() {
return nil, nil, nil
}
// Always initialize ReportGenerator because RPCServer.NewInput will need it to filter coverage.
rg, err := getReportGenerator(mgr.cfg, mgr.modules)
if err != nil {
return nil, nil, err
}
pcs := make(map[uint32]uint32)
foreachSymbol := func(apply func(*backend.ObjectUnit)) {
for _, sym := range rg.Symbols {
apply(&sym.ObjectUnit)
}
}
if err := covFilterAddFilter(pcs, mgr.cfg.CovFilter.Functions, foreachSymbol); err != nil {
return nil, nil, err
}
foreachUnit := func(apply func(*backend.ObjectUnit)) {
for _, unit := range rg.Units {
apply(&unit.ObjectUnit)
}
}
if err := covFilterAddFilter(pcs, mgr.cfg.CovFilter.Files, foreachUnit); err != nil {
return nil, nil, err
}
if err := covFilterAddRawPCs(pcs, mgr.cfg.CovFilter.RawPCs); err != nil {
return nil, nil, err
}
if len(pcs) == 0 {
return nil, nil, nil
}
if !mgr.cfg.SysTarget.ExecutorUsesShmem {
return nil, nil, fmt.Errorf("coverage filter is only supported for targets that use shmem")
}
// Copy pcs into execPCs. This is used to filter coverage in the executor.
execPCs := make(map[uint32]uint32)
for pc, val := range pcs {
execPCs[pc] = val
}
// PCs from CMPs are deleted to calculate `filtered coverage` statistics
// in syz-manager.
for _, sym := range rg.Symbols {
for _, pc := range sym.CMPs {
delete(pcs, uint32(pc))
}
}
return execPCs, pcs, nil
}
func covFilterAddFilter(pcs map[uint32]uint32, filters []string, foreach func(func(*backend.ObjectUnit))) error {
res, err := compileRegexps(filters)
if err != nil {
return err
}
used := make(map[*regexp.Regexp][]string)
foreach(func(unit *backend.ObjectUnit) {
for _, re := range res {
if re.MatchString(unit.Name) {
// We add both coverage points and comparison interception points
// because executor filters comparisons as well.
for _, pc := range unit.PCs {
pcs[uint32(pc)] = 1
}
for _, pc := range unit.CMPs {
pcs[uint32(pc)] = 1
}
used[re] = append(used[re], unit.Name)
break
}
}
})
for _, re := range res {
sort.Strings(used[re])
log.Logf(0, "coverage filter: %v: %v", re, used[re])
}
if len(res) != len(used) {
return fmt.Errorf("some filters don't match anything")
}
return nil
}
func covFilterAddRawPCs(pcs map[uint32]uint32, rawPCsFiles []string) error {
re := regexp.MustCompile(`(0x[0-9a-f]+)(?:: (0x[0-9a-f]+))?`)
for _, f := range rawPCsFiles {
rawFile, err := os.Open(f)
if err != nil {
return fmt.Errorf("failed to open raw PCs file: %w", err)
}
defer rawFile.Close()
s := bufio.NewScanner(rawFile)
for s.Scan() {
match := re.FindStringSubmatch(s.Text())
if match == nil {
return fmt.Errorf("bad line: %q", s.Text())
}
pc, err := strconv.ParseUint(match[1], 0, 64)
if err != nil {
return err
}
weight, err := strconv.ParseUint(match[2], 0, 32)
if match[2] != "" && err != nil {
return err
}
// If no weight is detected, set the weight to 0x1 by default.
if match[2] == "" || weight < 1 {
weight = 1
}
pcs[uint32(pc)] = uint32(weight)
}
if err := s.Err(); err != nil {
return err
}
}
return nil
}
func createCoverageBitmap(target *targets.Target, pcs map[uint32]uint32) []byte {
// Return nil if filtering is not used.
if len(pcs) == 0 {
return nil
}
start, size := coverageFilterRegion(pcs)
log.Logf(0, "coverage filter from 0x%x to 0x%x, size 0x%x, pcs %v", start, start+size, size, len(pcs))
// The file starts with two uint32: covFilterStart and covFilterSize,
// and a bitmap with size ((covFilterSize>>4)/8+2 bytes follow them.
// 8-bit = 1-byte
data := make([]byte, 8+((size>>4)/8+2))
order := binary.ByteOrder(binary.BigEndian)
if target.LittleEndian {
order = binary.LittleEndian
}
order.PutUint32(data, start)
order.PutUint32(data[4:], size)
bitmap := data[8:]
for pc := range pcs {
// The lowest 4-bit is dropped.
pc = uint32(backend.NextInstructionPC(target, uint64(pc)))
pc = (pc - start) >> 4
bitmap[pc/8] |= (1 << (pc % 8))
}
return data
}
func coverageFilterRegion(pcs map[uint32]uint32) (uint32, uint32) {
start, end := ^uint32(0), uint32(0)
for pc := range pcs {
if start > pc {
start = pc
}
if end < pc {
end = pc
}
}
return start, end - start
}
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
var regexps []*regexp.Regexp
for _, rs := range regexpStrings {
r, err := regexp.Compile(rs)
if err != nil {
return nil, fmt.Errorf("failed to compile regexp: %w", err)
}
regexps = append(regexps, r)
}
return regexps, nil
}