diff options
| author | Joey Jiaojg <joeyjiaojg@qq.com> | 2021-03-03 00:22:57 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-02 17:22:57 +0100 |
| commit | aff16d3fed742e27093858a70f8bd5cbf6f0dedd (patch) | |
| tree | eca728c55e7c17709db46c1832bfe75649bd3a1d | |
| parent | e1b9a5704d15b1f26f7cae9142c34544768175b2 (diff) | |
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
| -rw-r--r-- | pkg/cover/cover.go | 5 | ||||
| -rw-r--r-- | pkg/cover/html.go | 219 | ||||
| -rw-r--r-- | pkg/cover/report.go | 27 | ||||
| -rw-r--r-- | pkg/cover/report_test.go | 22 | ||||
| -rw-r--r-- | pkg/kconfig/cause.config | 3 | ||||
| -rw-r--r-- | pkg/mgrconfig/config.go | 11 | ||||
| -rw-r--r-- | syz-manager/cover.go | 3 | ||||
| -rw-r--r-- | syz-manager/html.go | 61 | ||||
| -rw-r--r-- | tools/syz-cover/syz-cover.go | 2 |
9 files changed, 318 insertions, 35 deletions
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(` +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <style> + body { + background: white; + } + #content { + color: rgb(70, 70, 70); + margin-top: 50px; + } + th, td { + text-align: left; + border: 1px solid black; + } + th { + background: gray; + } + tr:nth-child(2n+1) { + background: #CCC + } + table { + border-collapse: collapse; + border: 1px solid black; + margin-bottom: 20px; + } + </style> + </head> + <body> + <div> + <table> + <thead> + <tr> + <th>Subsystem</th> + <th>Covered / Total Lines / %</th> + <th>Covered / Total PCs in File / %</th> + <th>Covered / Total PCs in Function / %</th> + <th>Covered Functions</th> + </tr> + </thead> + <tbody id="content"> + {{range $i, $p := .}} + <tr> + <td>{{$p.subsystem}}</td> + <td>{{$p.lines}}</td> + <td>{{$p.PCsInFiles}}</td> + <td>{{$p.PCsInFuncs}}</td> + <td>{{$p.totalFuncs}}</td> + </tr> + {{end}} + </tbody> + </table> + </div> + </body> +</html> + +`)) 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) } |
