From aff16d3fed742e27093858a70f8bd5cbf6f0dedd Mon Sep 17 00:00:00 2001 From: Joey Jiaojg Date: Wed, 3 Mar 2021 00:22:57 +0800 Subject: pkg/cover, syz-manager: show coverage summary * pkg/cover, syz-manager: show coverage summary The funccover or cover page is not easy for statistic purpose. So add /cover?type=rawfiles to show coverage based on each file. And /cover?type=table page to show coverage for group of components. If driver_path_map.json exists, /cover?type=table can show component coverage. Format example: { "all": [ "/" ], "audio": [ "/techpack/audio/asoc", "/techpack/audio/dsp", "/techpack/audio/ipc", "/sound/core" ] } If driver_path_map.json not exist, it will show one line summary. * pkg/cover: use subsystem naming * syz-manager: use /subsystemcover and /filecover * pkg/cover: use subsystem from config * pkg/mgrconfig: add kernel_subsystem * pkg/cover, tools/syz-cover: fix make test * all: fix presumit errors * pkg/cover, syz-manager: fix subsystem --- pkg/cover/cover.go | 5 + pkg/cover/html.go | 219 +++++++++++++++++++++++++++++++++++++++++++ pkg/cover/report.go | 27 ++++-- pkg/cover/report_test.go | 22 ++++- pkg/kconfig/cause.config | 3 + pkg/mgrconfig/config.go | 11 ++- syz-manager/cover.go | 3 +- syz-manager/html.go | 61 +++++++----- tools/syz-cover/syz-cover.go | 2 +- 9 files changed, 318 insertions(+), 35 deletions(-) create mode 100644 pkg/kconfig/cause.config diff --git a/pkg/cover/cover.go b/pkg/cover/cover.go index 814ae4fb6..572d5e5da 100644 --- a/pkg/cover/cover.go +++ b/pkg/cover/cover.go @@ -6,6 +6,11 @@ package cover type Cover map[uint32]struct{} +type Subsystem struct { + Name string `json:"name"` + Paths []string `json:"path"` +} + func (cov *Cover) Merge(raw []uint32) { c := *cov if c == nil { diff --git a/pkg/cover/html.go b/pkg/cover/html.go index b353029a3..1757964b5 100644 --- a/pkg/cover/html.go +++ b/pkg/cover/html.go @@ -103,6 +103,165 @@ func (rg *ReportGenerator) DoHTML(w io.Writer, progs []Prog) error { return coverTemplate.Execute(w, d) } +type fileStats struct { + Name string + CoveredLines int + TotalLines int + CoveredPCs int + TotalPCs int + TotalFunctions int + CoveredPCsInFunctions int + TotalPCsInFunctions int +} + +var csvFilesHeader = []string{ + "Filename", + "CoveredLines", + "TotalLines", + "CoveredPCs", + "TotalPCs", + "TotalFunctions", + "CoveredPCsInFunctions", + "TotalPCsInFunctions", +} + +func (rg *ReportGenerator) convertToStats(progs []Prog) ([]fileStats, error) { + files, err := rg.prepareFileMap(progs) + if err != nil { + return nil, err + } + + var data []fileStats + for fname, file := range files { + lines, err := parseFile(file.filename) + if err != nil { + fmt.Printf("failed to open/locate %s\n", file.filename) + continue + } + totalFuncs := len(file.functions) + var coveredInFunc int + var pcsInFunc int + for _, function := range file.functions { + coveredInFunc += function.covered + pcsInFunc += function.pcs + } + totalLines := len(lines) + var coveredLines int + for _, line := range file.lines { + if len(line.progCount) != 0 { + coveredLines++ + } + } + data = append(data, fileStats{ + Name: fname, + CoveredLines: coveredLines, + TotalLines: totalLines, + CoveredPCs: file.coveredPCs, + TotalPCs: file.totalPCs, + TotalFunctions: totalFuncs, + CoveredPCsInFunctions: coveredInFunc, + TotalPCsInFunctions: pcsInFunc, + }) + } + + return data, nil +} + +func (rg *ReportGenerator) DoCSVFiles(w io.Writer, progs []Prog) error { + data, err := rg.convertToStats(progs) + if err != nil { + return err + } + + sort.SliceStable(data, func(i, j int) bool { + return data[i].Name < data[j].Name + }) + + writer := csv.NewWriter(w) + defer writer.Flush() + if err := writer.Write(csvFilesHeader); err != nil { + return err + } + + var d [][]string + for _, dt := range data { + d = append(d, []string{ + dt.Name, + strconv.Itoa(dt.CoveredLines), + strconv.Itoa(dt.TotalLines), + strconv.Itoa(dt.CoveredPCs), + strconv.Itoa(dt.TotalPCs), + strconv.Itoa(dt.TotalFunctions), + strconv.Itoa(dt.CoveredPCsInFunctions), + strconv.Itoa(dt.TotalPCsInFunctions), + }) + } + return writer.WriteAll(d) +} + +func groupCoverByFilePrefixes(datas []fileStats, subsystems []Subsystem) map[string]map[string]string { + d := make(map[string]map[string]string) + + for _, subsystem := range subsystems { + var coveredLines int + var totalLines int + var coveredPCsInFile int + var totalPCsInFile int + var totalFuncs int + var coveredPCsInFuncs int + var pcsInFuncs int + var percentLines float64 + var percentPCsInFile float64 + var percentPCsInFunc float64 + + for _, path := range subsystem.Paths { + for _, data := range datas { + if !strings.HasPrefix(data.Name, path) { + continue + } + coveredLines += data.CoveredLines + totalLines += data.TotalLines + coveredPCsInFile += data.CoveredPCs + totalPCsInFile += data.TotalPCs + totalFuncs += data.TotalFunctions + coveredPCsInFuncs += data.CoveredPCsInFunctions + pcsInFuncs += data.TotalPCsInFunctions + } + } + + if totalLines != 0 { + percentLines = 100.0 * float64(coveredLines) / float64(totalLines) + } + if totalPCsInFile != 0 { + percentPCsInFile = 100.0 * float64(coveredPCsInFile) / float64(totalPCsInFile) + } + if pcsInFuncs != 0 { + percentPCsInFunc = 100.0 * float64(coveredPCsInFuncs) / float64(pcsInFuncs) + } + + d[subsystem.Name] = map[string]string{ + "subsystem": subsystem.Name, + "lines": fmt.Sprintf("%v / %v / %.2f%%", coveredLines, totalLines, percentLines), + "PCsInFiles": fmt.Sprintf("%v / %v / %.2f%%", coveredPCsInFile, totalPCsInFile, percentPCsInFile), + "totalFuncs": strconv.Itoa(totalFuncs), + "PCsInFuncs": fmt.Sprintf("%v / %v / %.2f%%", coveredPCsInFuncs, pcsInFuncs, percentPCsInFunc), + } + } + + return d +} + +func (rg *ReportGenerator) DoHTMLTable(w io.Writer, progs []Prog) error { + data, err := rg.convertToStats(progs) + if err != nil { + return err + } + + d := groupCoverByFilePrefixes(data, rg.subsystem) + + return coverTableTemplate.Execute(w, d) +} + var csvHeader = []string{ "Filename", "Function", @@ -557,3 +716,63 @@ var coverTemplate = template.Must(template.New("").Parse(` {{end}} {{end}} `)) + +var coverTableTemplate = template.Must(template.New("coverTable").Parse(` + + + + + + + +
+ + + + + + + + + + + + {{range $i, $p := .}} + + + + + + + + {{end}} + +
SubsystemCovered / Total Lines / %Covered / Total PCs in File / %Covered / Total PCs in Function / %Covered Functions
{{$p.subsystem}}{{$p.lines}}{{$p.PCsInFiles}}{{$p.PCsInFuncs}}{{$p.totalFuncs}}
+
+ + + +`)) diff --git a/pkg/cover/report.go b/pkg/cover/report.go index f4e88954e..d8f2115e3 100644 --- a/pkg/cover/report.go +++ b/pkg/cover/report.go @@ -12,10 +12,11 @@ import ( ) type ReportGenerator struct { - target *targets.Target - srcDir string - objDir string - buildDir string + target *targets.Target + srcDir string + objDir string + buildDir string + subsystem []Subsystem *backend.Impl } @@ -26,17 +27,23 @@ type Prog struct { var RestorePC = backend.RestorePC -func MakeReportGenerator(target *targets.Target, vm, objDir, srcDir, buildDir string) (*ReportGenerator, error) { +func MakeReportGenerator(target *targets.Target, vm, objDir, srcDir, buildDir string, + subsystem []Subsystem) (*ReportGenerator, error) { impl, err := backend.Make(target, vm, objDir, srcDir, buildDir) if err != nil { return nil, err } + subsystem = append(subsystem, Subsystem{ + Name: "all", + Paths: []string{""}, + }) rg := &ReportGenerator{ - target: target, - srcDir: srcDir, - objDir: objDir, - buildDir: buildDir, - Impl: impl, + target: target, + srcDir: srcDir, + objDir: objDir, + buildDir: buildDir, + subsystem: subsystem, + Impl: impl, } return rg, nil } diff --git a/pkg/cover/report_test.go b/pkg/cover/report_test.go index 63f1ff50a..0707eab64 100644 --- a/pkg/cover/report_test.go +++ b/pkg/cover/report_test.go @@ -180,7 +180,17 @@ func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, [] } defer os.RemoveAll(dir) bin := buildTestBinary(t, target, test, dir) - rg, err := MakeReportGenerator(target, "", dir, dir, dir) + subsystem := []Subsystem{ + { + Name: "sound", + Paths: []string{ + "sound", + "techpack/audio", + }, + }, + } + + rg, err := MakeReportGenerator(target, "", dir, dir, dir, subsystem) if err != nil { return nil, nil, err } @@ -216,10 +226,20 @@ func generateReport(t *testing.T, target *targets.Target, test Test) ([]byte, [] if err := rg.DoHTML(html, test.Progs); err != nil { return nil, nil, err } + htmlTable := new(bytes.Buffer) + if err := rg.DoHTMLTable(htmlTable, test.Progs); err != nil { + return nil, nil, err + } + _ = htmlTable csv := new(bytes.Buffer) if err := rg.DoCSV(csv, test.Progs); err != nil { return nil, nil, err } + csvFiles := new(bytes.Buffer) + if err := rg.DoCSVFiles(csvFiles, test.Progs); err != nil { + return nil, nil, err + } + _ = csvFiles return html.Bytes(), csv.Bytes(), nil } diff --git a/pkg/kconfig/cause.config b/pkg/kconfig/cause.config new file mode 100644 index 000000000..977642377 --- /dev/null +++ b/pkg/kconfig/cause.config @@ -0,0 +1,3 @@ +CONFIG_AX25=y +CONFIG_HAMRADIO=y +CONFIG_ROSE=y diff --git a/pkg/mgrconfig/config.go b/pkg/mgrconfig/config.go index a7476ac8e..e18607e7f 100644 --- a/pkg/mgrconfig/config.go +++ b/pkg/mgrconfig/config.go @@ -3,7 +3,10 @@ package mgrconfig -import "encoding/json" +import ( + "encoding/json" + "github.com/google/syzkaller/pkg/cover" +) type Config struct { // Instance name (used for identification and as GCE instance prefix). @@ -41,6 +44,12 @@ type Config struct { KernelSrc string `json:"kernel_src,omitempty"` // Location of the driectory where the kernel was built (if not set defaults to KernelSrc) KernelBuildSrc string `json:"kernel_build_src"` + // Kernel subsystem with paths to each subsystem + // "kernel_subsystem": [ + // { "name": "sound", "path": ["sound", "techpack/audio"]}, + // { "name": "mydriver": "path": ["mydriver_path"]} + // ] + KernelSubsystem []cover.Subsystem `json:"kernel_subsystem,omitempty"` // Arbitrary optional tag that is saved along with crash reports (e.g. branch/commit). Tag string `json:"tag,omitempty"` // Location of the disk image file. diff --git a/syz-manager/cover.go b/syz-manager/cover.go index f5ca86631..1d33b4d91 100644 --- a/syz-manager/cover.go +++ b/syz-manager/cover.go @@ -18,7 +18,8 @@ var getReportGenerator = func() func(cfg *mgrconfig.Config) (*cover.ReportGenera return func(cfg *mgrconfig.Config) (*cover.ReportGenerator, error) { once.Do(func() { log.Logf(0, "initializing coverage information...") - rg, err = cover.MakeReportGenerator(cfg.SysTarget, cfg.Type, cfg.KernelObj, cfg.KernelSrc, cfg.KernelBuildSrc) + rg, err = cover.MakeReportGenerator(cfg.SysTarget, cfg.Type, cfg.KernelObj, cfg.KernelSrc, + cfg.KernelBuildSrc, cfg.KernelSubsystem) }) return rg, err } diff --git a/syz-manager/html.go b/syz-manager/html.go index 91b3cc68d..c5b3aac33 100644 --- a/syz-manager/html.go +++ b/syz-manager/html.go @@ -38,12 +38,14 @@ func (mgr *Manager) initHTTP() { http.HandleFunc("/corpus", mgr.httpCorpus) http.HandleFunc("/crash", mgr.httpCrash) http.HandleFunc("/cover", mgr.httpCover) + http.HandleFunc("/subsystemcover", mgr.httpSubsystemCover) http.HandleFunc("/prio", mgr.httpPrio) http.HandleFunc("/file", mgr.httpFile) http.HandleFunc("/report", mgr.httpReport) http.HandleFunc("/rawcover", mgr.httpRawCover) http.HandleFunc("/filterpcs", mgr.httpFilterPCs) http.HandleFunc("/funccover", mgr.httpFuncCover) + http.HandleFunc("/filecover", mgr.httpFileCover) http.HandleFunc("/input", mgr.httpInput) // Browsers like to request this, without special handler this goes to / handler. http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) @@ -208,26 +210,42 @@ func (mgr *Manager) httpCorpus(w http.ResponseWriter, r *http.Request) { executeTemplate(w, corpusTemplate, data) } +const ( + DoHTML int = iota + DoHTMLTable + DoCSV + DoCSVFiles +) + func (mgr *Manager) httpCover(w http.ResponseWriter, r *http.Request) { + mgr.httpCoverCover(w, r, DoHTML, true) +} + +func (mgr *Manager) httpSubsystemCover(w http.ResponseWriter, r *http.Request) { + mgr.httpCoverCover(w, r, DoHTMLTable, true) +} + +func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, funcFlag int, isHTMLCover bool) { if !mgr.cfg.Cover { - mgr.mu.Lock() - defer mgr.mu.Unlock() - mgr.httpCoverFallback(w, r) + if isHTMLCover { + mgr.mu.Lock() + defer mgr.mu.Unlock() + mgr.httpCoverFallback(w, r) + } else { + http.Error(w, "coverage is not enabled", http.StatusInternalServerError) + } + return } - // Note: initCover is executed without mgr.mu because it takes very long time - // (but it only reads config and it protected by initCoverOnce). + rg, err := getReportGenerator(mgr.cfg) if err != nil { http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) return } + mgr.mu.Lock() defer mgr.mu.Unlock() - mgr.httpCoverCover(w, r, rg, rg.DoHTML) -} -func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, - rg *cover.ReportGenerator, do func(io.Writer, []cover.Prog) error) { convert := coverToPCs if r.FormValue("filter") != "" && mgr.coverFilter != nil { convert = func(rg *cover.ReportGenerator, cover []uint32) (ret []uint64) { @@ -258,6 +276,14 @@ func (mgr *Manager) httpCoverCover(w http.ResponseWriter, r *http.Request, }) } } + do := rg.DoHTML + if funcFlag == DoHTMLTable { + do = rg.DoHTMLTable + } else if funcFlag == DoCSV { + do = rg.DoCSV + } else if funcFlag == DoCSVFiles { + do = rg.DoCSVFiles + } if err := do(w, progs); err != nil { http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) return @@ -297,18 +323,11 @@ func (mgr *Manager) httpCoverFallback(w http.ResponseWriter, r *http.Request) { } func (mgr *Manager) httpFuncCover(w http.ResponseWriter, r *http.Request) { - if !mgr.cfg.Cover { - http.Error(w, "coverage is not enabled", http.StatusInternalServerError) - return - } - rg, err := getReportGenerator(mgr.cfg) - if err != nil { - http.Error(w, fmt.Sprintf("failed to generate coverage profile: %v", err), http.StatusInternalServerError) - return - } - mgr.mu.Lock() - defer mgr.mu.Unlock() - mgr.httpCoverCover(w, r, rg, rg.DoCSV) + mgr.httpCoverCover(w, r, DoCSV, false) +} + +func (mgr *Manager) httpFileCover(w http.ResponseWriter, r *http.Request) { + mgr.httpCoverCover(w, r, DoCSVFiles, false) } func (mgr *Manager) httpPrio(w http.ResponseWriter, r *http.Request) { diff --git a/tools/syz-cover/syz-cover.go b/tools/syz-cover/syz-cover.go index 221567f06..4c69b6c09 100644 --- a/tools/syz-cover/syz-cover.go +++ b/tools/syz-cover/syz-cover.go @@ -67,7 +67,7 @@ func main() { if err != nil { tool.Fail(err) } - rg, err := cover.MakeReportGenerator(target, *flagVM, *flagKernelObj, *flagKernelSrc, *flagKernelBuildSrc) + rg, err := cover.MakeReportGenerator(target, *flagVM, *flagKernelObj, *flagKernelSrc, *flagKernelBuildSrc, nil) if err != nil { tool.Fail(err) } -- cgit mrf-deployment