github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-cover/syz-cover.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  // syz-cover generates coverage HTML report from raw coverage files.
     5  // Raw coverage files are text files with one PC in hex form per line, e.g.:
     6  //
     7  //	0xffffffff8398658d
     8  //	0xffffffff839862fc
     9  //	0xffffffff8398633f
    10  //
    11  // Raw coverage files can be obtained either from /rawcover manager HTTP handler,
    12  // or from syz-execprog with -coverfile flag.
    13  //
    14  // Usage:
    15  //
    16  //	syz-cover -config config_file rawcover.file*
    17  //
    18  // or use all pcs in rg.Symbols
    19  //
    20  //	syz-cover -config config_file
    21  package main
    22  
    23  import (
    24  	"bufio"
    25  	"bytes"
    26  	"context"
    27  	"encoding/json"
    28  	"flag"
    29  	"fmt"
    30  	"io"
    31  	"os"
    32  	"os/exec"
    33  	"strconv"
    34  	"strings"
    35  	"time"
    36  
    37  	"cloud.google.com/go/civil"
    38  	"github.com/google/syzkaller/pkg/cover"
    39  	"github.com/google/syzkaller/pkg/cover/backend"
    40  	"github.com/google/syzkaller/pkg/coveragedb"
    41  	"github.com/google/syzkaller/pkg/covermerger"
    42  	"github.com/google/syzkaller/pkg/log"
    43  	"github.com/google/syzkaller/pkg/mgrconfig"
    44  	"github.com/google/syzkaller/pkg/osutil"
    45  	"github.com/google/syzkaller/pkg/tool"
    46  	"github.com/google/syzkaller/pkg/vminfo"
    47  )
    48  
    49  var (
    50  	flagConfig  = flag.String("config", "", "configuration file")
    51  	flagModules = flag.String("modules", "",
    52  		"modules JSON info obtained from /modules (optional)")
    53  	flagPeriod = flag.String("period", "day", "time period(day[default], month, quarter)")
    54  	flagDateTo = flag.String("to",
    55  		civil.DateOf(time.Now()).String(), "heatmap date to(optional)")
    56  	flagForFile = flag.String("for-file", "", "[optional]show file coverage")
    57  	flagRepo    = flag.String("repo", "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git",
    58  		"[optional] repo to be used by -for-file")
    59  	flagCommit       = flag.String("commit", "latest", "[optional] commit to be used by -for-file")
    60  	flagNamespace    = flag.String("namespace", "upstream", "[optional] used by -for-file")
    61  	flagDebug        = flag.Bool("debug", false, "[optional] enables detailed output")
    62  	flagSourceCommit = flag.String("source-commit", "", "[optional] filter input commit")
    63  	flagExports      = flag.String("exports", "cover",
    64  		"[optional] comma separated list of exports for which we want to generate coverage, "+
    65  			"possible values are: cover, subsystem, module, funccover, json, jsonl, rawcover, rawcoverfiles, all")
    66  	flagForce = flag.Bool("force", false, "[optional] create coverage report when "+
    67  		"there are missing coverage callbacks")
    68  )
    69  
    70  func toolFileCover() {
    71  	dateTo, err := civil.ParseDate(*flagDateTo)
    72  	if err != nil {
    73  		tool.Failf("failed to parse date from: %v", err)
    74  	}
    75  	tp, err := coveragedb.MakeTimePeriod(dateTo, *flagPeriod)
    76  	if err != nil {
    77  		tool.Fail(err)
    78  	}
    79  	config := cover.DefaultTextRenderConfig()
    80  	config.ShowLineSourceExplanation = *flagDebug
    81  	mr, err := cover.GetMergeResult(context.Background(),
    82  		*flagNamespace,
    83  		*flagRepo,
    84  		*flagCommit,
    85  		*flagSourceCommit,
    86  		*flagForFile,
    87  		nil, tp)
    88  	if err != nil {
    89  		tool.Fail(err)
    90  	}
    91  
    92  	details, err := cover.RendFileCoverage(
    93  		*flagRepo,
    94  		*flagCommit,
    95  		*flagForFile,
    96  		covermerger.MakeWebGit(nil), // get files directly from WebGits
    97  		mr,
    98  		config,
    99  	)
   100  	if err != nil {
   101  		tool.Fail(err)
   102  	}
   103  	fmt.Println(details)
   104  }
   105  
   106  func initModules(cfg *mgrconfig.Config) []*vminfo.KernelModule {
   107  	modules, err := backend.DiscoverModules(cfg.SysTarget, cfg.KernelObj, cfg.ModuleObj)
   108  	if err != nil {
   109  		tool.Fail(err)
   110  	}
   111  	if *flagModules != "" {
   112  		m, err := loadModules(*flagModules)
   113  		if err != nil {
   114  			tool.Fail(err)
   115  		}
   116  		modules = m
   117  	}
   118  	return modules
   119  }
   120  
   121  func main() {
   122  	defer tool.Init()()
   123  	if *flagForFile != "" {
   124  		toolFileCover()
   125  		return
   126  	}
   127  	cfg, err := mgrconfig.LoadFile(*flagConfig)
   128  	if err != nil {
   129  		tool.Fail(err)
   130  	}
   131  	modules := initModules(cfg)
   132  	rg, err := cover.MakeReportGenerator(cfg, modules)
   133  	if err != nil {
   134  		tool.Fail(err)
   135  	}
   136  	pcs := initPCs(rg)
   137  	progs := []cover.Prog{{PCs: pcs}}
   138  	params := cover.HandlerParams{
   139  		Progs: progs,
   140  		Debug: *flagDebug,
   141  		Force: *flagForce,
   142  	}
   143  
   144  	if *flagExports == "all" {
   145  		*flagExports = "cover,subsystem,module,funccover,rawcover,rawcoverfiles"
   146  	}
   147  	exports := strings.Split(*flagExports, ",")
   148  	for _, export := range exports {
   149  		log.Logf(1, "start generate %v", export)
   150  		switch export {
   151  		case "cover":
   152  			doReport(params, "syz-cover.html", rg.DoHTML)
   153  		case "subsystem":
   154  			doReport(params, "syz-cover-subsystem.html", rg.DoSubsystemCover)
   155  		case "module":
   156  			doReport(params, "syz-cover-module.html", rg.DoModuleCover)
   157  		case "funccover":
   158  			doReport(params, "syz-cover-funccover.csv", rg.DoFuncCover)
   159  		case "rawcover":
   160  			doReport(params, "rawcoverpcs", rg.DoRawCover)
   161  		case "rawcoverfiles":
   162  			doReport(params, "rawcoverfiles", rg.DoRawCoverFiles)
   163  		case "json":
   164  			doReport(params, "json", rg.DoLineJSON)
   165  		case "jsonl":
   166  			doReport(params, "jsonl", rg.DoCoverJSONL)
   167  		default:
   168  			tool.Failf("unknown export type: %q", export)
   169  		}
   170  	}
   171  }
   172  
   173  func doReport(params cover.HandlerParams, fname string,
   174  	fn func(w io.Writer, params cover.HandlerParams) error) {
   175  	buf := new(bytes.Buffer)
   176  	if err := fn(buf, params); err != nil {
   177  		tool.Fail(err)
   178  	}
   179  	log.Logf(0, "write to %v", fname)
   180  	if err := osutil.WriteFile(fname, buf.Bytes()); err != nil {
   181  		tool.Fail(err)
   182  	}
   183  	exec.Command("xdg-open", fname).Start()
   184  }
   185  
   186  func initPCs(rg *cover.ReportGenerator) []uint64 {
   187  	var pcs []uint64
   188  	if len(flag.Args()) == 0 {
   189  		pcs = rg.CallbackPoints
   190  		return pcs
   191  	}
   192  	pcs, err := readPCs(flag.Args())
   193  	if err != nil {
   194  		tool.Fail(err)
   195  	}
   196  	return pcs
   197  }
   198  
   199  func readPCs(files []string) ([]uint64, error) {
   200  	var pcs []uint64
   201  	for _, file := range files {
   202  		data, err := os.ReadFile(file)
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  		for s := bufio.NewScanner(bytes.NewReader(data)); s.Scan(); {
   207  			line := strings.TrimSpace(s.Text())
   208  			if line == "" {
   209  				continue
   210  			}
   211  			pc, err := strconv.ParseUint(line, 0, 64)
   212  			if err != nil {
   213  				return nil, err
   214  			}
   215  			pcs = append(pcs, pc)
   216  		}
   217  	}
   218  	return pcs, nil
   219  }
   220  
   221  func loadModules(fname string) ([]*vminfo.KernelModule, error) {
   222  	data, err := os.ReadFile(fname)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	var modules []*vminfo.KernelModule
   227  	err = json.Unmarshal(data, &modules)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	return modules, nil
   232  }