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 }