Skip to content

Commit 7b19e0e

Browse files
committed
pkg/cover/backend: extract PC ranges from Rust DWARF
Rust compilation units are different from C in that a single compilation unit includes multiple source files, but we still need to tell which PC range belong to which source file. Infer that information from the LineEntry structures. Cc #6000.
1 parent 9497799 commit 7b19e0e

File tree

1 file changed

+107
-25
lines changed

1 file changed

+107
-25
lines changed

pkg/cover/backend/dwarf.go

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ type symbolInfo struct {
341341
}
342342

343343
type pcRange struct {
344+
// [start; end)
344345
start uint64
345346
end uint64
346347
unit *CompileUnit
@@ -351,7 +352,32 @@ type pcFixFn = (func([2]uint64) ([2]uint64, bool))
351352
func readTextRanges(debugInfo *dwarf.Data, module *vminfo.KernelModule, pcFix pcFixFn) (
352353
[]pcRange, []*CompileUnit, error) {
353354
var ranges []pcRange
354-
var units []*CompileUnit
355+
unitMap := map[string]*CompileUnit{}
356+
addRange := func(r [2]uint64, fileName string) {
357+
if pcFix != nil {
358+
var filtered bool
359+
r, filtered = pcFix(r)
360+
if filtered {
361+
return
362+
}
363+
}
364+
unit, ok := unitMap[fileName]
365+
if !ok {
366+
unit = &CompileUnit{
367+
ObjectUnit: ObjectUnit{
368+
Name: fileName,
369+
},
370+
Module: module,
371+
}
372+
unitMap[fileName] = unit
373+
}
374+
if module.Name == "" {
375+
ranges = append(ranges, pcRange{r[0], r[1], unit})
376+
} else {
377+
ranges = append(ranges, pcRange{r[0] + module.Addr, r[1] + module.Addr, unit})
378+
}
379+
}
380+
355381
for r := debugInfo.Reader(); ; {
356382
ent, err := r.Next()
357383
if err != nil {
@@ -363,41 +389,97 @@ func readTextRanges(debugInfo *dwarf.Data, module *vminfo.KernelModule, pcFix pc
363389
if ent.Tag != dwarf.TagCompileUnit {
364390
return nil, nil, fmt.Errorf("found unexpected tag %v on top level", ent.Tag)
365391
}
366-
attrName := ent.Val(dwarf.AttrName)
367-
if attrName == nil {
392+
attrName, ok := ent.Val(dwarf.AttrName).(string)
393+
if !ok {
368394
continue
369395
}
370-
unit := &CompileUnit{
371-
ObjectUnit: ObjectUnit{
372-
Name: attrName.(string),
373-
},
374-
Module: module,
375-
}
376-
units = append(units, unit)
377-
ranges1, err := debugInfo.Ranges(ent)
378-
if err != nil {
379-
return nil, nil, err
380-
}
396+
attrCompDir, _ := ent.Val(dwarf.AttrCompDir).(string)
381397

382-
var filtered bool
383-
for _, r := range ranges1 {
384-
if pcFix != nil {
385-
r, filtered = pcFix(r)
386-
if filtered {
387-
continue
388-
}
398+
const languageRust = 28
399+
if language, ok := ent.Val(dwarf.AttrLanguage).(int64); ok && language == languageRust {
400+
rawRanges, err := rustRanges(debugInfo, ent)
401+
if err != nil {
402+
return nil, nil, fmt.Errorf("failed to query Rust PC ranges: %w", err)
389403
}
390-
if module.Name == "" {
391-
ranges = append(ranges, pcRange{r[0], r[1], unit})
392-
} else {
393-
ranges = append(ranges, pcRange{r[0] + module.Addr, r[1] + module.Addr, unit})
404+
for _, r := range rawRanges {
405+
addRange([2]uint64{r.start, r.end}, r.file)
406+
}
407+
} else {
408+
// Compile unit names are relative to the compilation dir,
409+
// while per-line info isn't.
410+
// Let's stick to the common approach.
411+
unitName := filepath.Join(attrCompDir, attrName)
412+
ranges1, err := debugInfo.Ranges(ent)
413+
if err != nil {
414+
return nil, nil, err
415+
}
416+
for _, r := range ranges1 {
417+
addRange(r, unitName)
394418
}
395419
}
396420
r.SkipChildren()
397421
}
422+
var units []*CompileUnit
423+
for _, unit := range unitMap {
424+
units = append(units, unit)
425+
}
398426
return ranges, units, nil
399427
}
400428

429+
type rustRange struct {
430+
// [start; end)
431+
start uint64
432+
end uint64
433+
file string
434+
}
435+
436+
func rustRanges(debugInfo *dwarf.Data, ent *dwarf.Entry) ([]rustRange, error) {
437+
// For Rust, a single compilation unit may comprise all .rs files that belong to the crate.
438+
// To properly render the coverage, we need to somehow infer the ranges that belong to
439+
// those individual .rs files.
440+
// For simplicity, let's create fake ranges by looking at the DWARF line information.
441+
var ret []rustRange
442+
lr, err := debugInfo.LineReader(ent)
443+
if err != nil {
444+
return nil, fmt.Errorf("failed to query line reader: %w", err)
445+
}
446+
var startPC uint64
447+
var files []string
448+
for {
449+
var entry dwarf.LineEntry
450+
if err = lr.Next(&entry); err != nil {
451+
if err == io.EOF {
452+
break
453+
}
454+
return nil, fmt.Errorf("failed to parse next line entry: %w", err)
455+
}
456+
if startPC == 0 || entry.Address != startPC {
457+
for _, file := range files {
458+
ret = append(ret, rustRange{
459+
start: startPC,
460+
end: entry.Address,
461+
file: file,
462+
})
463+
}
464+
files = files[:0]
465+
startPC = entry.Address
466+
}
467+
// Keep on collecting file names that are covered by the range.
468+
files = append(files, entry.File.Name)
469+
}
470+
if startPC != 0 {
471+
// We don't know the end PC for these, but let's still add them to the ranges.
472+
for _, file := range files {
473+
ret = append(ret, rustRange{
474+
start: startPC,
475+
end: startPC + 1,
476+
file: file,
477+
})
478+
}
479+
}
480+
return ret, nil
481+
}
482+
401483
func symbolizeModule(target *targets.Target, interner *symbolizer.Interner, kernelDirs *mgrconfig.KernelDirs,
402484
splitBuildDelimiters []string, mod *vminfo.KernelModule, pcs []uint64) ([]*Frame, error) {
403485
procs := min(runtime.GOMAXPROCS(0)/2, len(pcs)/1000)

0 commit comments

Comments
 (0)