github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/cover/backend/dwarf.go (about)

     1  // Copyright 2021 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 backend
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"debug/dwarf"
    10  	"debug/elf"
    11  	"encoding/binary"
    12  	"fmt"
    13  	"io"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"sort"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"github.com/google/syzkaller/pkg/osutil"
    22  	"github.com/google/syzkaller/pkg/symbolizer"
    23  	"github.com/google/syzkaller/sys/targets"
    24  )
    25  
    26  type dwarfParams struct {
    27  	target                *targets.Target
    28  	objDir                string
    29  	srcDir                string
    30  	buildDir              string
    31  	splitBuildDelimiters  []string
    32  	moduleObj             []string
    33  	hostModules           []KernelModule
    34  	readSymbols           func(*Module, *symbolInfo) ([]*Symbol, error)
    35  	readTextData          func(*Module) ([]byte, error)
    36  	readModuleCoverPoints func(*targets.Target, *Module, *symbolInfo) ([2][]uint64, error)
    37  	readTextRanges        func(*Module) ([]pcRange, []*CompileUnit, error)
    38  	getCompilerVersion    func(string) string
    39  }
    40  
    41  type Arch struct {
    42  	scanSize      int
    43  	callLen       int
    44  	relaOffset    uint64
    45  	callRelocType uint64
    46  	isCallInsn    func(arch *Arch, insn []byte) bool
    47  	callTarget    func(arch *Arch, insn []byte, pc uint64) uint64
    48  }
    49  
    50  var arches = map[string]Arch{
    51  	targets.AMD64: {
    52  		scanSize:      1,
    53  		callLen:       5,
    54  		relaOffset:    1,
    55  		callRelocType: uint64(elf.R_X86_64_PLT32),
    56  		isCallInsn: func(arch *Arch, insn []byte) bool {
    57  			return insn[0] == 0xe8
    58  		},
    59  		callTarget: func(arch *Arch, insn []byte, pc uint64) uint64 {
    60  			off := uint64(int64(int32(binary.LittleEndian.Uint32(insn[1:]))))
    61  			return pc + off + uint64(arch.callLen)
    62  		},
    63  	},
    64  	targets.ARM64: {
    65  		scanSize:      4,
    66  		callLen:       4,
    67  		callRelocType: uint64(elf.R_AARCH64_CALL26),
    68  		isCallInsn: func(arch *Arch, insn []byte) bool {
    69  			const mask = uint32(0xfc000000)
    70  			const opc = uint32(0x94000000)
    71  			return binary.LittleEndian.Uint32(insn)&mask == opc
    72  		},
    73  		callTarget: func(arch *Arch, insn []byte, pc uint64) uint64 {
    74  			off26 := binary.LittleEndian.Uint32(insn) & 0x3ffffff
    75  			sign := off26 >> 25
    76  			off := uint64(off26)
    77  			// Sign-extend the 26-bit offset stored in the instruction.
    78  			if sign == 1 {
    79  				off |= 0xfffffffffc000000
    80  			}
    81  			return pc + 4*off
    82  		},
    83  	},
    84  }
    85  
    86  func makeDWARF(params *dwarfParams) (impl *Impl, err error) {
    87  	defer func() {
    88  		// It turns out that the DWARF-parsing library in Go crashes while parsing DWARF 5 data.
    89  		// As GCC11 uses DWARF 5 by default, we can expect larger number of problems with
    90  		// syzkallers compiled using old go versions.
    91  		// So we just catch the panic and turn it into a meaningful error message.
    92  		if recErr := recover(); recErr != nil {
    93  			impl = nil
    94  			err = fmt.Errorf("panic occurred while parsing DWARF "+
    95  				"(possible remedy: use go1.16+ which support DWARF 5 debug data): %s", recErr)
    96  		}
    97  	}()
    98  	impl, err = makeDWARFUnsafe(params)
    99  	return
   100  }
   101  
   102  type Result struct {
   103  	CoverPoints [2][]uint64
   104  	Symbols     []*Symbol
   105  }
   106  
   107  func processModule(params *dwarfParams, module *Module, info *symbolInfo,
   108  	target *targets.Target) (*Result, error) {
   109  	symbols, err := params.readSymbols(module, info)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	var data []byte
   115  	var coverPoints [2][]uint64
   116  	if target.Arch != targets.AMD64 && target.Arch != targets.ARM64 {
   117  		coverPoints, err = objdump(target, module)
   118  	} else if module.Name == "" {
   119  		data, err = params.readTextData(module)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		coverPoints, err = readCoverPoints(target, info, data)
   124  	} else {
   125  		coverPoints, err = params.readModuleCoverPoints(target, module, info)
   126  	}
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	result := &Result{
   132  		Symbols:     symbols,
   133  		CoverPoints: coverPoints,
   134  	}
   135  	return result, nil
   136  }
   137  
   138  func makeDWARFUnsafe(params *dwarfParams) (*Impl, error) {
   139  	target := params.target
   140  	objDir := params.objDir
   141  	srcDir := params.srcDir
   142  	buildDir := params.buildDir
   143  	splitBuildDelimiters := params.splitBuildDelimiters
   144  	modules, err := discoverModules(target, objDir, params.moduleObj, params.hostModules)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	// Here and below index 0 refers to coverage callbacks (__sanitizer_cov_trace_pc(_guard))
   150  	// and index 1 refers to comparison callbacks (__sanitizer_cov_trace_cmp*).
   151  	var allCoverPoints [2][]uint64
   152  	var allSymbols []*Symbol
   153  	var allRanges []pcRange
   154  	var allUnits []*CompileUnit
   155  	var pcBase uint64
   156  	preciseCoverage := true
   157  	for _, module := range modules {
   158  		errc := make(chan error, 1)
   159  		go func() {
   160  			info := &symbolInfo{
   161  				tracePC:     make(map[uint64]bool),
   162  				traceCmp:    make(map[uint64]bool),
   163  				tracePCIdx:  make(map[int]bool),
   164  				traceCmpIdx: make(map[int]bool),
   165  			}
   166  			result, err := processModule(params, module, info, target)
   167  			if err != nil {
   168  				errc <- err
   169  				return
   170  			}
   171  			allSymbols = append(allSymbols, result.Symbols...)
   172  			if module.Name == "" {
   173  				pcBase = info.textAddr
   174  			}
   175  			allCoverPoints[0] = append(allCoverPoints[0], result.CoverPoints[0]...)
   176  			allCoverPoints[1] = append(allCoverPoints[1], result.CoverPoints[1]...)
   177  			if module.Name == "" && len(result.CoverPoints[0]) == 0 {
   178  				err = fmt.Errorf("%v doesn't contain coverage callbacks (set CONFIG_KCOV=y on linux)", module.Path)
   179  			}
   180  			errc <- err
   181  		}()
   182  		ranges, units, err := params.readTextRanges(module)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		if err := <-errc; err != nil {
   187  			return nil, err
   188  		}
   189  		allRanges = append(allRanges, ranges...)
   190  		allUnits = append(allUnits, units...)
   191  		if isKcovBrokenInCompiler(params.getCompilerVersion(module.Path)) {
   192  			preciseCoverage = false
   193  		}
   194  	}
   195  
   196  	sort.Slice(allSymbols, func(i, j int) bool {
   197  		return allSymbols[i].Start < allSymbols[j].Start
   198  	})
   199  	sort.Slice(allRanges, func(i, j int) bool {
   200  		return allRanges[i].start < allRanges[j].start
   201  	})
   202  	for k := range allCoverPoints {
   203  		sort.Slice(allCoverPoints[k], func(i, j int) bool {
   204  			return allCoverPoints[k][i] < allCoverPoints[k][j]
   205  		})
   206  	}
   207  
   208  	allSymbols = buildSymbols(allSymbols, allRanges, allCoverPoints)
   209  	nunit := 0
   210  	for _, unit := range allUnits {
   211  		if len(unit.PCs) == 0 {
   212  			continue // drop the unit
   213  		}
   214  		// TODO: objDir won't work for out-of-tree modules.
   215  		unit.Name, unit.Path = cleanPath(unit.Name, objDir, srcDir, buildDir, splitBuildDelimiters)
   216  		allUnits[nunit] = unit
   217  		nunit++
   218  	}
   219  	allUnits = allUnits[:nunit]
   220  	if len(allSymbols) == 0 || len(allUnits) == 0 {
   221  		return nil, fmt.Errorf("failed to parse DWARF (set CONFIG_DEBUG_INFO=y on linux)")
   222  	}
   223  	if target.OS == targets.FreeBSD {
   224  		// On FreeBSD .text address in ELF is 0, but .text is actually mapped at 0xffffffff.
   225  		pcBase = ^uint64(0)
   226  	}
   227  	var interner symbolizer.Interner
   228  	impl := &Impl{
   229  		Units:   allUnits,
   230  		Symbols: allSymbols,
   231  		Symbolize: func(pcs map[*Module][]uint64) ([]Frame, error) {
   232  			return symbolize(target, &interner, objDir, srcDir, buildDir, splitBuildDelimiters, pcs)
   233  		},
   234  		RestorePC:       makeRestorePC(params, pcBase),
   235  		CallbackPoints:  allCoverPoints[0],
   236  		PreciseCoverage: preciseCoverage,
   237  	}
   238  	return impl, nil
   239  }
   240  
   241  func makeRestorePC(params *dwarfParams, pcBase uint64) func(pc uint32) uint64 {
   242  	return func(pcLow uint32) uint64 {
   243  		return PreviousInstructionPC(params.target, RestorePC(pcLow, uint32(pcBase>>32)))
   244  	}
   245  }
   246  
   247  func buildSymbols(symbols []*Symbol, ranges []pcRange, coverPoints [2][]uint64) []*Symbol {
   248  	// Assign coverage point PCs to symbols.
   249  	// Both symbols and coverage points are sorted, so we do it one pass over both.
   250  	selectPCs := func(u *ObjectUnit, typ int) *[]uint64 {
   251  		return [2]*[]uint64{&u.PCs, &u.CMPs}[typ]
   252  	}
   253  	for pcType := range coverPoints {
   254  		pcs := coverPoints[pcType]
   255  		var curSymbol *Symbol
   256  		firstSymbolPC, symbolIdx := -1, 0
   257  		for i := 0; i < len(pcs); i++ {
   258  			pc := pcs[i]
   259  			for ; symbolIdx < len(symbols) && pc >= symbols[symbolIdx].End; symbolIdx++ {
   260  			}
   261  			var symb *Symbol
   262  			if symbolIdx < len(symbols) && pc >= symbols[symbolIdx].Start && pc < symbols[symbolIdx].End {
   263  				symb = symbols[symbolIdx]
   264  			}
   265  			if curSymbol != nil && curSymbol != symb {
   266  				*selectPCs(&curSymbol.ObjectUnit, pcType) = pcs[firstSymbolPC:i]
   267  				firstSymbolPC = -1
   268  			}
   269  			curSymbol = symb
   270  			if symb != nil && firstSymbolPC == -1 {
   271  				firstSymbolPC = i
   272  			}
   273  		}
   274  		if curSymbol != nil {
   275  			*selectPCs(&curSymbol.ObjectUnit, pcType) = pcs[firstSymbolPC:]
   276  		}
   277  	}
   278  	// Assign compile units to symbols based on unit pc ranges.
   279  	// Do it one pass as both are sorted.
   280  	nsymbol := 0
   281  	rangeIndex := 0
   282  	for _, s := range symbols {
   283  		for ; rangeIndex < len(ranges) && ranges[rangeIndex].end <= s.Start; rangeIndex++ {
   284  		}
   285  		if rangeIndex == len(ranges) || s.Start < ranges[rangeIndex].start || len(s.PCs) == 0 {
   286  			continue // drop the symbol
   287  		}
   288  		unit := ranges[rangeIndex].unit
   289  		s.Unit = unit
   290  		symbols[nsymbol] = s
   291  		nsymbol++
   292  	}
   293  	symbols = symbols[:nsymbol]
   294  
   295  	for pcType := range coverPoints {
   296  		for _, s := range symbols {
   297  			symbPCs := selectPCs(&s.ObjectUnit, pcType)
   298  			unitPCs := selectPCs(&s.Unit.ObjectUnit, pcType)
   299  			pos := len(*unitPCs)
   300  			*unitPCs = append(*unitPCs, *symbPCs...)
   301  			*symbPCs = (*unitPCs)[pos:]
   302  		}
   303  	}
   304  	return symbols
   305  }
   306  
   307  // Regexps to parse compiler version string in isKcovBrokenInCompiler.
   308  // Some targets (e.g. NetBSD) use g++ instead of gcc.
   309  var gccRE = regexp.MustCompile(`gcc|GCC|g\+\+`)
   310  var gccVersionRE = regexp.MustCompile(`(gcc|GCC|g\+\+).* ([0-9]{1,2})\.[0-9]+\.[0-9]+`)
   311  
   312  // GCC < 14 incorrectly tail-calls kcov callbacks, which does not let syzkaller
   313  // verify that collected coverage points have matching callbacks.
   314  // See https://github.com/google/syzkaller/issues/4447 for more information.
   315  func isKcovBrokenInCompiler(versionStr string) bool {
   316  	if !gccRE.MatchString(versionStr) {
   317  		return false
   318  	}
   319  	groups := gccVersionRE.FindStringSubmatch(versionStr)
   320  	if len(groups) > 0 {
   321  		version, err := strconv.Atoi(groups[2])
   322  		if err == nil {
   323  			return version < 14
   324  		}
   325  	}
   326  	return true
   327  }
   328  
   329  type symbolInfo struct {
   330  	textAddr uint64
   331  	// Set of addresses that correspond to __sanitizer_cov_trace_pc or its trampolines.
   332  	tracePC     map[uint64]bool
   333  	traceCmp    map[uint64]bool
   334  	tracePCIdx  map[int]bool
   335  	traceCmpIdx map[int]bool
   336  }
   337  
   338  type pcRange struct {
   339  	start uint64
   340  	end   uint64
   341  	unit  *CompileUnit
   342  }
   343  
   344  type pcFixFn = (func([2]uint64) ([2]uint64, bool))
   345  
   346  func readTextRanges(debugInfo *dwarf.Data, module *Module, pcFix pcFixFn) (
   347  	[]pcRange, []*CompileUnit, error) {
   348  	var ranges []pcRange
   349  	var units []*CompileUnit
   350  	for r := debugInfo.Reader(); ; {
   351  		ent, err := r.Next()
   352  		if err != nil {
   353  			return nil, nil, err
   354  		}
   355  		if ent == nil {
   356  			break
   357  		}
   358  		if ent.Tag != dwarf.TagCompileUnit {
   359  			return nil, nil, fmt.Errorf("found unexpected tag %v on top level", ent.Tag)
   360  		}
   361  		attrName := ent.Val(dwarf.AttrName)
   362  		if attrName == nil {
   363  			continue
   364  		}
   365  		unit := &CompileUnit{
   366  			ObjectUnit: ObjectUnit{
   367  				Name: attrName.(string),
   368  			},
   369  			Module: module,
   370  		}
   371  		units = append(units, unit)
   372  		ranges1, err := debugInfo.Ranges(ent)
   373  		if err != nil {
   374  			return nil, nil, err
   375  		}
   376  
   377  		var filtered bool
   378  		for _, r := range ranges1 {
   379  			if pcFix != nil {
   380  				r, filtered = pcFix(r)
   381  				if filtered {
   382  					continue
   383  				}
   384  			}
   385  			ranges = append(ranges, pcRange{r[0] + module.Addr, r[1] + module.Addr, unit})
   386  		}
   387  		r.SkipChildren()
   388  	}
   389  	return ranges, units, nil
   390  }
   391  
   392  func symbolizeModule(target *targets.Target, interner *symbolizer.Interner, objDir, srcDir, buildDir string,
   393  	splitBuildDelimiters []string, mod *Module, pcs []uint64) ([]Frame, error) {
   394  	procs := runtime.GOMAXPROCS(0) / 2
   395  	if need := len(pcs) / 1000; procs > need {
   396  		procs = need
   397  	}
   398  	const (
   399  		minProcs = 1
   400  		maxProcs = 4
   401  	)
   402  	// addr2line on a beefy vmlinux takes up to 1.6GB of RAM, so don't create too many of them.
   403  	if procs > maxProcs {
   404  		procs = maxProcs
   405  	}
   406  	if procs < minProcs {
   407  		procs = minProcs
   408  	}
   409  	type symbolizerResult struct {
   410  		frames []symbolizer.Frame
   411  		err    error
   412  	}
   413  	symbolizerC := make(chan symbolizerResult, procs)
   414  	pcchan := make(chan []uint64, procs)
   415  	for p := 0; p < procs; p++ {
   416  		go func() {
   417  			symb := symbolizer.NewSymbolizer(target)
   418  			defer symb.Close()
   419  			var res symbolizerResult
   420  			for pcs := range pcchan {
   421  				for i, pc := range pcs {
   422  					pcs[i] = pc - mod.Addr
   423  				}
   424  				frames, err := symb.SymbolizeArray(mod.Path, pcs)
   425  				if err != nil {
   426  					res.err = fmt.Errorf("failed to symbolize: %w", err)
   427  				}
   428  				res.frames = append(res.frames, frames...)
   429  			}
   430  			symbolizerC <- res
   431  		}()
   432  	}
   433  	for i := 0; i < len(pcs); {
   434  		end := i + 100
   435  		if end > len(pcs) {
   436  			end = len(pcs)
   437  		}
   438  		pcchan <- pcs[i:end]
   439  		i = end
   440  	}
   441  	close(pcchan)
   442  	var err0 error
   443  	var frames []Frame
   444  	for p := 0; p < procs; p++ {
   445  		res := <-symbolizerC
   446  		if res.err != nil {
   447  			err0 = res.err
   448  		}
   449  		for _, frame := range res.frames {
   450  			name, path := cleanPath(frame.File, objDir, srcDir, buildDir, splitBuildDelimiters)
   451  			frames = append(frames, Frame{
   452  				Module:   mod,
   453  				PC:       frame.PC + mod.Addr,
   454  				Name:     interner.Do(name),
   455  				FuncName: frame.Func,
   456  				Path:     interner.Do(path),
   457  				Inline:   frame.Inline,
   458  				Range: Range{
   459  					StartLine: frame.Line,
   460  					StartCol:  0,
   461  					EndLine:   frame.Line,
   462  					EndCol:    LineEnd,
   463  				},
   464  			})
   465  		}
   466  	}
   467  	if err0 != nil {
   468  		return nil, err0
   469  	}
   470  	return frames, nil
   471  }
   472  
   473  func symbolize(target *targets.Target, interner *symbolizer.Interner, objDir, srcDir, buildDir string,
   474  	splitBuildDelimiters []string, pcs map[*Module][]uint64) ([]Frame, error) {
   475  	var frames []Frame
   476  	for mod, pcs1 := range pcs {
   477  		frames1, err := symbolizeModule(target, interner, objDir, srcDir, buildDir, splitBuildDelimiters, mod, pcs1)
   478  		if err != nil {
   479  			return nil, err
   480  		}
   481  		frames = append(frames, frames1...)
   482  	}
   483  	return frames, nil
   484  }
   485  
   486  // nextCallTarget finds the next call instruction in data[] starting at *pos and returns that
   487  // instruction's target and pc.
   488  func nextCallTarget(arch *Arch, textAddr uint64, data []byte, pos *int) (uint64, uint64) {
   489  	for *pos < len(data) {
   490  		i := *pos
   491  		if i+arch.callLen > len(data) {
   492  			break
   493  		}
   494  		*pos += arch.scanSize
   495  		insn := data[i : i+arch.callLen]
   496  		if !arch.isCallInsn(arch, insn) {
   497  			continue
   498  		}
   499  		pc := textAddr + uint64(i)
   500  		callTarget := arch.callTarget(arch, insn, pc)
   501  		*pos = i + arch.scanSize
   502  		return callTarget, pc
   503  	}
   504  	return 0, 0
   505  }
   506  
   507  // readCoverPoints finds all coverage points (calls of __sanitizer_cov_trace_*) in the object file.
   508  // Currently it is [amd64|arm64]-specific: looks for opcode and correct offset.
   509  // Running objdump on the whole object file is too slow.
   510  func readCoverPoints(target *targets.Target, info *symbolInfo, data []byte) ([2][]uint64, error) {
   511  	var pcs [2][]uint64
   512  	if len(info.tracePC) == 0 {
   513  		return pcs, fmt.Errorf("no __sanitizer_cov_trace_pc symbol in the object file")
   514  	}
   515  
   516  	i := 0
   517  	for {
   518  		arch := arches[target.Arch]
   519  		callTarget, pc := nextCallTarget(&arch, info.textAddr, data, &i)
   520  		if callTarget == 0 {
   521  			break
   522  		}
   523  		if info.tracePC[callTarget] {
   524  			pcs[0] = append(pcs[0], pc)
   525  		} else if info.traceCmp[callTarget] {
   526  			pcs[1] = append(pcs[1], pc)
   527  		}
   528  	}
   529  	return pcs, nil
   530  }
   531  
   532  // Source files for Android may be split between two subdirectories: the common AOSP kernel
   533  // and the device-specific drivers: https://source.android.com/docs/setup/build/building-pixel-kernels.
   534  // Android build system references these subdirectories in various ways, which often results in
   535  // paths to non-existent files being recorded in the debug info.
   536  //
   537  // cleanPathAndroid() assumes that the subdirectories reside in `srcDir`, with their names being listed in
   538  // `delimiters`.
   539  // If one of the `delimiters` occurs in the `path`, it is stripped together with the path prefix, and the
   540  // remaining file path is appended to `srcDir + delimiter`.
   541  // If none of the `delimiters` occur in the `path`, `path` is treated as a relative path that needs to be
   542  // looked up in `srcDir + delimiters[i]`.
   543  func cleanPathAndroid(path, srcDir string, delimiters []string, existFn func(string) bool) (string, string) {
   544  	if len(delimiters) == 0 {
   545  		return "", ""
   546  	}
   547  	reStr := "(" + strings.Join(delimiters, "|") + ")(.*)"
   548  	re := regexp.MustCompile(reStr)
   549  	match := re.FindStringSubmatch(path)
   550  	if match != nil {
   551  		delimiter := match[1]
   552  		filename := match[2]
   553  		path := filepath.Clean(srcDir + delimiter + filename)
   554  		return filename, path
   555  	}
   556  	// None of the delimiters found in `path`: it is probably a relative path to the source file.
   557  	// Try to look it up in every subdirectory of srcDir.
   558  	for _, delimiter := range delimiters {
   559  		absPath := filepath.Clean(srcDir + delimiter + path)
   560  		if existFn(absPath) {
   561  			return path, absPath
   562  		}
   563  	}
   564  	return "", ""
   565  }
   566  
   567  func cleanPath(path, objDir, srcDir, buildDir string, splitBuildDelimiters []string) (string, string) {
   568  	filename := ""
   569  
   570  	path = filepath.Clean(path)
   571  	aname, apath := cleanPathAndroid(path, srcDir, splitBuildDelimiters, osutil.IsExist)
   572  	if aname != "" {
   573  		return aname, apath
   574  	}
   575  	absPath := osutil.Abs(path)
   576  	switch {
   577  	case strings.HasPrefix(absPath, objDir):
   578  		// Assume the file was built there.
   579  		path = strings.TrimPrefix(absPath, objDir)
   580  		filename = filepath.Join(objDir, path)
   581  	case strings.HasPrefix(absPath, buildDir):
   582  		// Assume the file was moved from buildDir to srcDir.
   583  		path = strings.TrimPrefix(absPath, buildDir)
   584  		filename = filepath.Join(srcDir, path)
   585  	default:
   586  		// Assume this is relative path.
   587  		filename = filepath.Join(srcDir, path)
   588  	}
   589  	return strings.TrimLeft(filepath.Clean(path), "/\\"), filename
   590  }
   591  
   592  // objdump is an old, slow way of finding coverage points.
   593  // amd64 uses faster option of parsing binary directly (readCoverPoints).
   594  // TODO: use the faster approach for all other arches and drop this.
   595  func objdump(target *targets.Target, mod *Module) ([2][]uint64, error) {
   596  	var pcs [2][]uint64
   597  	cmd := osutil.Command(target.Objdump, "-d", "--no-show-raw-insn", mod.Path)
   598  	stdout, err := cmd.StdoutPipe()
   599  	if err != nil {
   600  		return pcs, err
   601  	}
   602  	defer stdout.Close()
   603  	stderr, err := cmd.StderrPipe()
   604  	if err != nil {
   605  		return pcs, err
   606  	}
   607  	defer stderr.Close()
   608  	if err := cmd.Start(); err != nil {
   609  		return pcs, fmt.Errorf("failed to run objdump on %v: %w", mod.Path, err)
   610  	}
   611  	defer func() {
   612  		cmd.Process.Kill()
   613  		cmd.Wait()
   614  	}()
   615  	s := bufio.NewScanner(stdout)
   616  	callInsns, traceFuncs := archCallInsn(target)
   617  	for s.Scan() {
   618  		if pc := parseLine(callInsns, traceFuncs, s.Bytes()); pc != 0 {
   619  			pcs[0] = append(pcs[0], pc+mod.Addr)
   620  		}
   621  	}
   622  	stderrOut, _ := io.ReadAll(stderr)
   623  	if err := cmd.Wait(); err != nil {
   624  		return pcs, fmt.Errorf("failed to run objdump on %v: %w\n%s", mod.Path, err, stderrOut)
   625  	}
   626  	if err := s.Err(); err != nil {
   627  		return pcs, fmt.Errorf("failed to run objdump on %v: %w\n%s", mod.Path, err, stderrOut)
   628  	}
   629  	return pcs, nil
   630  }
   631  
   632  func parseLine(callInsns, traceFuncs [][]byte, ln []byte) uint64 {
   633  	pos := -1
   634  	for _, callInsn := range callInsns {
   635  		if pos = bytes.Index(ln, callInsn); pos != -1 {
   636  			break
   637  		}
   638  	}
   639  	if pos == -1 {
   640  		return 0
   641  	}
   642  	hasCall := false
   643  	for _, traceFunc := range traceFuncs {
   644  		if hasCall = bytes.Contains(ln[pos:], traceFunc); hasCall {
   645  			break
   646  		}
   647  	}
   648  	if !hasCall {
   649  		return 0
   650  	}
   651  	for len(ln) != 0 && ln[0] == ' ' {
   652  		ln = ln[1:]
   653  	}
   654  	colon := bytes.IndexByte(ln, ':')
   655  	if colon == -1 {
   656  		return 0
   657  	}
   658  	pc, err := strconv.ParseUint(string(ln[:colon]), 16, 64)
   659  	if err != nil {
   660  		return 0
   661  	}
   662  	return pc
   663  }
   664  
   665  func archCallInsn(target *targets.Target) ([][]byte, [][]byte) {
   666  	callName := [][]byte{[]byte(" <__sanitizer_cov_trace_pc>")}
   667  	switch target.Arch {
   668  	case targets.I386:
   669  		// c1000102:       call   c10001f0 <__sanitizer_cov_trace_pc>
   670  		return [][]byte{[]byte("\tcall ")}, callName
   671  	case targets.ARM64:
   672  		// ffff0000080d9cc0:       bl      ffff00000820f478 <__sanitizer_cov_trace_pc>
   673  		return [][]byte{[]byte("\tbl ")}, [][]byte{
   674  			[]byte("<__sanitizer_cov_trace_pc>"),
   675  			[]byte("<____sanitizer_cov_trace_pc_veneer>"),
   676  		}
   677  
   678  	case targets.ARM:
   679  		// 8010252c:       bl      801c3280 <__sanitizer_cov_trace_pc>
   680  		return [][]byte{[]byte("\tbl\t")}, callName
   681  	case targets.PPC64LE:
   682  		// c00000000006d904:       bl      c000000000350780 <.__sanitizer_cov_trace_pc>
   683  		// This is only known to occur in the test:
   684  		// 838:   bl      824 <__sanitizer_cov_trace_pc+0x8>
   685  		// This occurs on PPC64LE:
   686  		// c0000000001c21a8:       bl      c0000000002df4a0 <__sanitizer_cov_trace_pc>
   687  		return [][]byte{[]byte("\tbl ")}, [][]byte{
   688  			[]byte("<__sanitizer_cov_trace_pc>"),
   689  			[]byte("<__sanitizer_cov_trace_pc+0x8>"),
   690  			[]byte(" <.__sanitizer_cov_trace_pc>"),
   691  		}
   692  	case targets.MIPS64LE:
   693  		// ffffffff80100420:       jal     ffffffff80205880 <__sanitizer_cov_trace_pc>
   694  		// This is only known to occur in the test:
   695  		// b58:   bal     b30 <__sanitizer_cov_trace_pc>
   696  		return [][]byte{[]byte("\tjal\t"), []byte("\tbal\t")}, callName
   697  	case targets.S390x:
   698  		// 1001de:       brasl   %r14,2bc090 <__sanitizer_cov_trace_pc>
   699  		return [][]byte{[]byte("\tbrasl\t")}, callName
   700  	case targets.RiscV64:
   701  		// ffffffe000200018:       jal     ra,ffffffe0002935b0 <__sanitizer_cov_trace_pc>
   702  		// ffffffe0000010da:       jalr    1242(ra) # ffffffe0002935b0 <__sanitizer_cov_trace_pc>
   703  		return [][]byte{[]byte("\tjal\t"), []byte("\tjalr\t")}, callName
   704  	default:
   705  		panic(fmt.Sprintf("unknown arch %q", target.Arch))
   706  	}
   707  }