github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/cover/report.go (about)

     1  // Copyright 2018 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package cover
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/google/syzkaller/pkg/cover/backend"
    11  	"github.com/google/syzkaller/pkg/mgrconfig"
    12  	"github.com/google/syzkaller/sys/targets"
    13  	"golang.org/x/exp/maps"
    14  )
    15  
    16  type ReportGenerator struct {
    17  	target          *targets.Target
    18  	srcDir          string
    19  	buildDir        string
    20  	subsystem       []mgrconfig.Subsystem
    21  	rawCoverEnabled bool
    22  	*backend.Impl
    23  }
    24  
    25  type Prog struct {
    26  	Sig  string
    27  	Data string
    28  	PCs  []uint64
    29  }
    30  
    31  type KernelModule = backend.KernelModule
    32  
    33  var RestorePC = backend.RestorePC
    34  
    35  func MakeReportGenerator(cfg *mgrconfig.Config, subsystem []mgrconfig.Subsystem,
    36  	modules []KernelModule, rawCover bool) (*ReportGenerator, error) {
    37  	impl, err := backend.Make(cfg.SysTarget, cfg.Type, cfg.KernelObj,
    38  		cfg.KernelSrc, cfg.KernelBuildSrc, cfg.AndroidSplitBuild, cfg.ModuleObj, modules)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	subsystem = append(subsystem, mgrconfig.Subsystem{
    43  		Name:  "all",
    44  		Paths: []string{""},
    45  	})
    46  	rg := &ReportGenerator{
    47  		target:          cfg.SysTarget,
    48  		srcDir:          cfg.KernelSrc,
    49  		buildDir:        cfg.KernelBuildSrc,
    50  		subsystem:       subsystem,
    51  		rawCoverEnabled: rawCover,
    52  		Impl:            impl,
    53  	}
    54  	return rg, nil
    55  }
    56  
    57  type file struct {
    58  	module     string
    59  	filename   string
    60  	lines      map[int]line
    61  	functions  []*function
    62  	covered    []backend.Range
    63  	uncovered  []backend.Range
    64  	totalPCs   int
    65  	coveredPCs int
    66  }
    67  
    68  type function struct {
    69  	name    string
    70  	pcs     int
    71  	covered int
    72  }
    73  
    74  type line struct {
    75  	progCount   map[int]bool   // program indices that cover this line
    76  	progIndex   int            // example program index that covers this line
    77  	pcProgCount map[uint64]int // some lines have multiple BBs
    78  }
    79  
    80  type fileMap map[string]*file
    81  
    82  func (rg *ReportGenerator) prepareFileMap(progs []Prog, force, debug bool) (fileMap, error) {
    83  	if err := rg.symbolizePCs(uniquePCs(progs)); err != nil {
    84  		return nil, err
    85  	}
    86  	files := make(fileMap)
    87  	for _, unit := range rg.Units {
    88  		files[unit.Name] = &file{
    89  			module:   unit.Module.Name,
    90  			filename: unit.Path,
    91  			lines:    make(map[int]line),
    92  			totalPCs: len(unit.PCs),
    93  		}
    94  	}
    95  	progPCs := make(map[uint64]map[int]bool)
    96  	unmatchedProgPCs := make(map[uint64]bool)
    97  	for i, prog := range progs {
    98  		for _, pc := range prog.PCs {
    99  			if progPCs[pc] == nil {
   100  				progPCs[pc] = make(map[int]bool)
   101  			}
   102  			progPCs[pc][i] = true
   103  			if rg.PreciseCoverage && !contains(rg.CallbackPoints, pc) {
   104  				unmatchedProgPCs[pc] = true
   105  			}
   106  		}
   107  	}
   108  	matchedPC := false
   109  	for _, frame := range rg.Frames {
   110  		f := fileByFrame(files, &frame)
   111  		ln := f.lines[frame.StartLine]
   112  		coveredBy := progPCs[frame.PC]
   113  		if len(coveredBy) == 0 {
   114  			f.uncovered = append(f.uncovered, frame.Range)
   115  			continue
   116  		}
   117  		// Covered frame.
   118  		f.covered = append(f.covered, frame.Range)
   119  		matchedPC = true
   120  		if ln.progCount == nil {
   121  			ln.progCount = make(map[int]bool)
   122  			ln.pcProgCount = make(map[uint64]int)
   123  			ln.progIndex = -1
   124  		}
   125  		for progIndex := range coveredBy {
   126  			ln.progCount[progIndex] = true
   127  			if ln.progIndex == -1 || len(progs[progIndex].Data) < len(progs[ln.progIndex].Data) {
   128  				ln.progIndex = progIndex
   129  			}
   130  			ln.pcProgCount[frame.PC]++
   131  		}
   132  		f.lines[frame.StartLine] = ln
   133  	}
   134  	if !matchedPC {
   135  		return nil, fmt.Errorf("coverage doesn't match any coverage callbacks")
   136  	}
   137  	// If the backend provided coverage callback locations for the binaries, use them to
   138  	// verify data returned by kcov.
   139  	if len(unmatchedProgPCs) > 0 && !force {
   140  		return nil, coverageCallbackMismatch(debug, len(progPCs), unmatchedProgPCs)
   141  	}
   142  	for _, unit := range rg.Units {
   143  		f := files[unit.Name]
   144  		for _, pc := range unit.PCs {
   145  			if progPCs[pc] != nil {
   146  				f.coveredPCs++
   147  			}
   148  		}
   149  	}
   150  	for _, s := range rg.Symbols {
   151  		fun := &function{
   152  			name: s.Name,
   153  			pcs:  len(s.PCs),
   154  		}
   155  		for _, pc := range s.PCs {
   156  			if progPCs[pc] != nil {
   157  				fun.covered++
   158  			}
   159  		}
   160  		f := files[s.Unit.Name]
   161  		f.functions = append(f.functions, fun)
   162  	}
   163  	for _, f := range files {
   164  		sort.Slice(f.functions, func(i, j int) bool {
   165  			return f.functions[i].name < f.functions[j].name
   166  		})
   167  	}
   168  	return files, nil
   169  }
   170  
   171  func contains(pcs []uint64, pc uint64) bool {
   172  	idx := sort.Search(len(pcs), func(i int) bool { return pcs[i] >= pc })
   173  	return idx < len(pcs) && pcs[idx] == pc
   174  }
   175  
   176  func coverageCallbackMismatch(debug bool, numPCs int, unmatchedProgPCs map[uint64]bool) error {
   177  	debugStr := ""
   178  	if debug {
   179  		debugStr += "\n\nUnmatched PCs:\n"
   180  		for pc := range unmatchedProgPCs {
   181  			debugStr += fmt.Sprintf("%x\n", pc)
   182  		}
   183  	}
   184  	return fmt.Errorf("%d out of %d PCs returned by kcov do not have matching coverage callbacks."+
   185  		" Check the discoverModules() code. Use ?force=1 to disable this message.%s",
   186  		len(unmatchedProgPCs), numPCs, debugStr)
   187  }
   188  
   189  func uniquePCs(progs []Prog) []uint64 {
   190  	PCs := make(map[uint64]bool)
   191  	for _, p := range progs {
   192  		for _, pc := range p.PCs {
   193  			PCs[pc] = true
   194  		}
   195  	}
   196  	return maps.Keys(PCs)
   197  }
   198  
   199  func (rg *ReportGenerator) symbolizePCs(PCs []uint64) error {
   200  	if len(PCs) == 0 {
   201  		return fmt.Errorf("no coverage collected so far to symbolize")
   202  	}
   203  	if len(rg.Symbols) == 0 {
   204  		return nil
   205  	}
   206  	symbolize := make(map[*backend.Symbol]bool)
   207  	pcs := make(map[*backend.Module][]uint64)
   208  	for _, pc := range PCs {
   209  		sym := rg.findSymbol(pc)
   210  		if sym == nil || sym.Symbolized || symbolize[sym] {
   211  			continue
   212  		}
   213  		symbolize[sym] = true
   214  		pcs[sym.Module] = append(pcs[sym.Module], sym.PCs...)
   215  	}
   216  	frames, err := rg.Symbolize(pcs)
   217  	if err != nil {
   218  		return err
   219  	}
   220  	rg.Frames = append(rg.Frames, frames...)
   221  	for sym := range symbolize {
   222  		sym.Symbolized = true
   223  	}
   224  	return nil
   225  }
   226  
   227  func fileByFrame(files map[string]*file, frame *backend.Frame) *file {
   228  	f := files[frame.Name]
   229  	if f == nil {
   230  		f = &file{
   231  			module:   frame.Module.Name,
   232  			filename: frame.Path,
   233  			lines:    make(map[int]line),
   234  			// Special mark for header files, if a file does not have coverage at all it is not shown.
   235  			totalPCs:   1,
   236  			coveredPCs: 1,
   237  		}
   238  		files[frame.Name] = f
   239  	}
   240  	return f
   241  }
   242  
   243  func (rg *ReportGenerator) findSymbol(pc uint64) *backend.Symbol {
   244  	idx := sort.Search(len(rg.Symbols), func(i int) bool {
   245  		return pc < rg.Symbols[i].End
   246  	})
   247  	if idx == len(rg.Symbols) {
   248  		return nil
   249  	}
   250  	s := rg.Symbols[idx]
   251  	if pc < s.Start || pc > s.End {
   252  		return nil
   253  	}
   254  	return s
   255  }