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