Skip to content

Commit a5cc2ee

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 77908e5 commit a5cc2ee

File tree

1 file changed

+109
-25
lines changed

1 file changed

+109
-25
lines changed

pkg/cover/backend/dwarf.go

Lines changed: 109 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,99 @@ 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+
400+
language := ent.Val(dwarf.AttrLanguage)
401+
if language != nil && language.(int64) == languageRust {
402+
rawRanges, err := rustRanges(debugInfo, ent)
403+
if err != nil {
404+
return nil, nil, fmt.Errorf("failed to query Rust PC ranges: %w", err)
405+
}
406+
for _, r := range rawRanges {
407+
addRange([2]uint64{r.start, r.end}, r.file)
389408
}
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})
409+
} else {
410+
// Compile unit names are relative to the compilation dir,
411+
// while per-line info isn't.
412+
// Let's stick to the common approach.
413+
unitName := filepath.Join(attrCompDir, attrName)
414+
ranges1, err := debugInfo.Ranges(ent)
415+
if err != nil {
416+
return nil, nil, err
417+
}
418+
for _, r := range ranges1 {
419+
addRange(r, unitName)
394420
}
395421
}
396422
r.SkipChildren()
397423
}
424+
var units []*CompileUnit
425+
for _, unit := range unitMap {
426+
units = append(units, unit)
427+
}
398428
return ranges, units, nil
399429
}
400430

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

0 commit comments

Comments
 (0)