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 }