@@ -351,7 +351,36 @@ type pcFixFn = (func([2]uint64) ([2]uint64, bool))
351351func readTextRanges (debugInfo * dwarf.Data , module * vminfo.KernelModule , pcFix pcFixFn ) (
352352 []pcRange , []* CompileUnit , error ) {
353353 var ranges []pcRange
354- var units []* CompileUnit
354+ unitMap := map [string ]* CompileUnit {}
355+ addRange := func (r [2 ]uint64 , unitName string ) {
356+ if pcFix != nil {
357+ var filtered bool
358+ r , filtered = pcFix (r )
359+ if filtered {
360+ return
361+ }
362+ }
363+ // Rust compilation units have a special naming scheme.
364+ if strings .Contains (unitName , "/@/" ) {
365+ unitName , _ , _ = strings .Cut (unitName , "/@/" )
366+ }
367+ unit , ok := unitMap [unitName ]
368+ if ! ok {
369+ unit = & CompileUnit {
370+ ObjectUnit : ObjectUnit {
371+ Name : unitName ,
372+ },
373+ Module : module ,
374+ }
375+ unitMap [unitName ] = unit
376+ }
377+ if module .Name == "" {
378+ ranges = append (ranges , pcRange {r [0 ], r [1 ], unit })
379+ } else {
380+ ranges = append (ranges , pcRange {r [0 ] + module .Addr , r [1 ] + module .Addr , unit })
381+ }
382+ }
383+
355384 for r := debugInfo .Reader (); ; {
356385 ent , err := r .Next ()
357386 if err != nil {
@@ -363,41 +392,97 @@ func readTextRanges(debugInfo *dwarf.Data, module *vminfo.KernelModule, pcFix pc
363392 if ent .Tag != dwarf .TagCompileUnit {
364393 return nil , nil , fmt .Errorf ("found unexpected tag %v on top level" , ent .Tag )
365394 }
366- attrName := ent .Val (dwarf .AttrName )
367- if attrName == nil {
395+ attrName , ok := ent .Val (dwarf .AttrName ).( string )
396+ if ! ok {
368397 continue
369398 }
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- }
399+ attrCompDir , _ := ent .Val (dwarf .AttrCompDir ).(string )
400+ // Compile unit names are relative to the compilation dir, while per-line info isn't.
401+ // Let's stick to the common approach.
402+ unitName := attrCompDir + attrName
381403
382- var filtered bool
383- for _ , r := range ranges1 {
384- if pcFix != nil {
385- r , filtered = pcFix ( r )
386- if filtered {
387- continue
388- }
404+ const languageRust = 28
405+
406+ language := ent . Val ( dwarf . AttrLanguage )
407+ if language != nil && language .( int64 ) == languageRust {
408+ rawRanges , err := rustRanges ( debugInfo , ent )
409+ if err != nil {
410+ return nil , nil , fmt . Errorf ( "failed to query Rust PC ranges: %w" , err )
389411 }
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 })
412+ for _ , r := range rawRanges {
413+ addRange ([2 ]uint64 {r .start , r .end }, r .file )
414+ }
415+ } else {
416+ ranges1 , err := debugInfo .Ranges (ent )
417+ if err != nil {
418+ return nil , nil , err
419+ }
420+ for _ , r := range ranges1 {
421+ addRange (r , unitName )
394422 }
395423 }
396424 r .SkipChildren ()
397425 }
426+ var units []* CompileUnit
427+ for _ , unit := range unitMap {
428+ units = append (units , unit )
429+ }
398430 return ranges , units , nil
399431}
400432
433+ type rustRange struct {
434+ start uint64
435+ end uint64
436+ file string
437+ }
438+
439+ func rustRanges (debugInfo * dwarf.Data , ent * dwarf.Entry ) ([]rustRange , error ) {
440+ // For Rust, a single compilation unit may comprise all .rs files that belong to the crate.
441+ // To properly render the coverage, we need to somehow infer the ranges that belong to
442+ // those individual .rs files.
443+ // For simplicity, let's create fake ranges by looking at the DWARF line information.
444+ var ret []rustRange
445+ lr , err := debugInfo .LineReader (ent )
446+ if err != nil {
447+ return nil , fmt .Errorf ("failed to query line reader: %w" , err )
448+ }
449+ var startPC uint64
450+ var files []string
451+ for {
452+ var entry dwarf.LineEntry
453+ if err = lr .Next (& entry ); err != nil {
454+ if err == io .EOF {
455+ break
456+ }
457+ return nil , fmt .Errorf ("failed to parse next line entry: %w" , err )
458+ }
459+ if startPC == 0 || entry .Address != startPC {
460+ for _ , file := range files {
461+ ret = append (ret , rustRange {
462+ start : startPC ,
463+ end : entry .Address - 1 ,
464+ file : file ,
465+ })
466+ }
467+ files = files [:0 ]
468+ startPC = entry .Address
469+ }
470+ // Keep on collecting file names that are covered by the range.
471+ files = append (files , entry .File .Name )
472+ }
473+ if startPC != 0 {
474+ // We don't know the end PC for these, but let's still add them to the ranges.
475+ for _ , file := range files {
476+ ret = append (ret , rustRange {
477+ start : startPC ,
478+ end : startPC ,
479+ file : file ,
480+ })
481+ }
482+ }
483+ return ret , nil
484+ }
485+
401486func symbolizeModule (target * targets.Target , interner * symbolizer.Interner , kernelDirs * mgrconfig.KernelDirs ,
402487 splitBuildDelimiters []string , mod * vminfo.KernelModule , pcs []uint64 ) ([]* Frame , error ) {
403488 procs := min (runtime .GOMAXPROCS (0 )/ 2 , len (pcs )/ 1000 )
0 commit comments