@@ -32,19 +32,24 @@ import (
32
32
"go.opentelemetry.io/ebpf-profiler/libpf"
33
33
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
34
34
"go.opentelemetry.io/ebpf-profiler/libpf/readatbuf"
35
+ "go.opentelemetry.io/ebpf-profiler/nativeunwind/elfunwindinfo"
35
36
"go.opentelemetry.io/ebpf-profiler/process"
36
37
)
37
38
38
- const uploadCacheSize = 16384
39
- const uploadQueueSize = 1000
40
- const uploadWorkerCount = 10
39
+ const (
40
+ uploadCacheSize = 16384
41
+ uploadQueueSize = 1000
42
+ uploadWorkerCount = 10
41
43
42
- const sourceMapEndpoint = "/api/v2/srcmap"
44
+ sourceMapEndpoint = "/api/v2/srcmap"
43
45
44
- const symbolCopyTimeout = 10 * time .Second
45
- const uploadTimeout = 15 * time .Second
46
+ symbolCopyTimeout = 10 * time .Second
47
+ uploadTimeout = 15 * time .Second
46
48
47
- const buildIDSectionName = ".note.gnu.build-id"
49
+ buildIDSectionName = ".note.gnu.build-id"
50
+
51
+ maxBytesGoPclntab = 128 * 1024 * 1024
52
+ )
48
53
49
54
var debugStrSectionNames = []string {".debug_str" , ".zdebug_str" , ".debug_str.dwo" }
50
55
var debugInfoSectionNames = []string {".debug_info" , ".zdebug_info" }
@@ -57,13 +62,16 @@ type uploadData struct {
57
62
opener process.FileOpener
58
63
}
59
64
65
+ type goPCLnTabData []byte
66
+
60
67
type DatadogSymbolUploader struct {
61
68
ddAPIKey string
62
69
ddAPPKey string
63
70
intakeURL string
64
71
version string
65
72
dryRun bool
66
73
uploadDynamicSymbols bool
74
+ uploadGoPCLnTab bool
67
75
workerCount int
68
76
69
77
uploadCache * lru.SyncedLRU [libpf.FileID , struct {}]
@@ -112,6 +120,7 @@ func NewDatadogSymbolUploader(cfg SymbolUploaderConfig) (*DatadogSymbolUploader,
112
120
version : cfg .Version ,
113
121
dryRun : cfg .DryRun ,
114
122
uploadDynamicSymbols : cfg .UploadDynamicSymbols ,
123
+ uploadGoPCLnTab : cfg .UploadGoPCLnTab ,
115
124
workerCount : uploadWorkerCount ,
116
125
client : & http.Client {Timeout : uploadTimeout },
117
126
uploadCache : uploadCache ,
@@ -191,7 +200,10 @@ func (d *DatadogSymbolUploader) upload(ctx context.Context, uploadData uploadDat
191
200
}
192
201
defer elfWrapper .Close ()
193
202
194
- debugElf , symbolSource := elfWrapper .findSymbols ()
203
+ debugElf , symbolSource , goPCLnTabInfo := elfWrapper .findSymbols ()
204
+ if goPCLnTabInfo != nil {
205
+ log .Infof ("Found GoPCLnTab symbols in %s" , filePath )
206
+ }
195
207
if debugElf == nil {
196
208
log .Debugf ("Skipping symbol upload for executable %s: no debug symbols found" , filePath )
197
209
return false
@@ -225,7 +237,7 @@ func (d *DatadogSymbolUploader) upload(ctx context.Context, uploadData uploadDat
225
237
return true
226
238
}
227
239
228
- err = d .handleSymbols (ctx , symbolPath , e )
240
+ err = d .handleSymbols (ctx , symbolPath , e , goPCLnTabInfo )
229
241
if err != nil {
230
242
log .Errorf ("Failed to handle symbols: %v for executable: %s" , err , e )
231
243
return false
@@ -316,7 +328,7 @@ func (e *executableMetadata) String() string {
316
328
}
317
329
318
330
func (d * DatadogSymbolUploader ) handleSymbols (ctx context.Context , symbolPath string ,
319
- e * executableMetadata ) error {
331
+ e * executableMetadata , goPCLnTabInfo goPCLnTabData ) error {
320
332
symbolFile , err := os .CreateTemp ("" , "objcopy-debug" )
321
333
if err != nil {
322
334
return fmt .Errorf ("failed to create temp file to extract symbols: %w" , err )
@@ -326,9 +338,16 @@ func (d *DatadogSymbolUploader) handleSymbols(ctx context.Context, symbolPath st
326
338
327
339
ctx , cancel := context .WithTimeout (ctx , symbolCopyTimeout )
328
340
defer cancel ()
329
- err = d .copySymbols (ctx , symbolPath , symbolFile .Name ())
330
- if err != nil {
331
- return fmt .Errorf ("failed to copy symbols: %w" , err )
341
+ if goPCLnTabInfo != nil {
342
+ err = copySymbolsAndGoPCLnTab (ctx , symbolPath , symbolFile .Name (), goPCLnTabInfo )
343
+ if err != nil {
344
+ return fmt .Errorf ("failed to copy GoPCLnTab: %w" , err )
345
+ }
346
+ } else {
347
+ err = copySymbols (ctx , symbolPath , symbolFile .Name ())
348
+ if err != nil {
349
+ return fmt .Errorf ("failed to copy symbols: %w" , err )
350
+ }
332
351
}
333
352
334
353
err = d .uploadSymbols (ctx , symbolFile , e )
@@ -339,7 +358,38 @@ func (d *DatadogSymbolUploader) handleSymbols(ctx context.Context, symbolPath st
339
358
return nil
340
359
}
341
360
342
- func (d * DatadogSymbolUploader ) copySymbols (ctx context.Context , inputPath , outputPath string ) error {
361
+ func copySymbolsAndGoPCLnTab (ctx context.Context , inputPath , outputPath string ,
362
+ goPCLnTabInfo goPCLnTabData ) error {
363
+ gopclntabFile , err := os .CreateTemp ("" , "gopclntab" )
364
+ if err != nil {
365
+ return fmt .Errorf ("failed to create temp file to extract GoPCLnTab: %w" , err )
366
+ }
367
+ defer os .Remove (gopclntabFile .Name ())
368
+ defer gopclntabFile .Close ()
369
+
370
+ _ , err = gopclntabFile .Write (goPCLnTabInfo )
371
+ if err != nil {
372
+ return fmt .Errorf ("failed to write GoPCLnTab: %w" , err )
373
+ }
374
+
375
+ args := []string {
376
+ "--only-keep-debug" ,
377
+ "--remove-section=.gdb_index" ,
378
+ "--remove-section=.gopclntab" ,
379
+ "--remove-section=.data.rel.ro.gopclntab" ,
380
+ "--add-section" , ".gopclntab=" + gopclntabFile .Name (),
381
+ "--set-section-flags" , ".gopclntab=readonly" ,
382
+ inputPath ,
383
+ outputPath ,
384
+ }
385
+ _ , err = exec .CommandContext (ctx , "objcopy" , args ... ).Output ()
386
+ if err != nil {
387
+ return fmt .Errorf ("failed to extract debug symbols: %w" , cleanCmdError (err ))
388
+ }
389
+ return nil
390
+ }
391
+
392
+ func copySymbols (ctx context.Context , inputPath , outputPath string ) error {
343
393
args := []string {
344
394
"--only-keep-debug" ,
345
395
"--remove-section=.gdb_index" ,
@@ -487,6 +537,56 @@ func HasDWARFData(f *pfelf.File) bool {
487
537
return len (f .Progs ) == 0 && hasBuildID && hasDebugStr
488
538
}
489
539
540
+ func findGoPCLnTab (ef * pfelf.File ) (goPCLnTabData , error ) {
541
+ var err error
542
+ var data []byte
543
+
544
+ if s := ef .Section (".gopclntab" ); s != nil {
545
+ if data , err = s .Data (maxBytesGoPclntab ); err != nil {
546
+ return nil , fmt .Errorf ("failed to load .gopclntab: %w" , err )
547
+ }
548
+ } else if s := ef .Section (".data.rel.ro.gopclntab" ); s != nil {
549
+ if data , err = s .Data (maxBytesGoPclntab ); err != nil {
550
+ return nil , fmt .Errorf ("failed to load .data.rel.ro.gopclntab: %w" , err )
551
+ }
552
+ } else if s := ef .Section (".go.buildinfo" ); s != nil {
553
+ symtab , err := ef .ReadSymbols ()
554
+ if err != nil {
555
+ // It seems the Go binary was stripped. So we use the heuristic approach
556
+ // to get the stack deltas.
557
+ if data , err = elfunwindinfo .SearchGoPclntab (ef ); err != nil {
558
+ return nil , fmt .Errorf ("failed to search .gopclntab: %w" , err )
559
+ }
560
+ } else {
561
+ start , err := symtab .LookupSymbolAddress ("runtime.pclntab" )
562
+ if err != nil {
563
+ return nil , fmt .Errorf ("failed to load .gopclntab via symbols: %w" , err )
564
+ }
565
+ end , err := symtab .LookupSymbolAddress ("runtime.epclntab" )
566
+ if err != nil {
567
+ return nil , fmt .Errorf ("failed to load .gopclntab via symbols: %w" , err )
568
+ }
569
+ if start >= end {
570
+ return nil , fmt .Errorf ("invalid .gopclntab symbols: %v-%v" , start , end )
571
+ }
572
+ data = make ([]byte , end - start )
573
+ if _ , err := ef .ReadVirtualMemory (data , int64 (start )); err != nil {
574
+ return nil , fmt .Errorf ("failed to load .gopclntab via symbols: %w" , err )
575
+ }
576
+ }
577
+ }
578
+
579
+ if data == nil {
580
+ return nil , nil
581
+ }
582
+
583
+ if len (data ) < 16 {
584
+ return nil , fmt .Errorf (".gopclntab is too short (%v)" , len (data ))
585
+ }
586
+
587
+ return data , nil
588
+ }
589
+
490
590
type elfWrapper struct {
491
591
reader process.ReadAtCloser
492
592
elfFile * pfelf.File
@@ -523,9 +623,19 @@ func openELF(filePath string, opener process.FileOpener) (*elfWrapper, error) {
523
623
524
624
// findSymbols attempts to find a symbol source for the elf file, it returns an elfWrapper around the elf file
525
625
// with symbols if found, or nil if no symbols were found.
526
- func (e * elfWrapper ) findSymbols () (* elfWrapper , SymbolSource ) {
626
+ func (e * elfWrapper ) findSymbols () (* elfWrapper , SymbolSource , goPCLnTabData ) {
627
+ // Check if the elf file has a GoPCLnTab
628
+ goPCLnTabInfo , err := findGoPCLnTab (e .elfFile )
629
+ if err == nil {
630
+ if goPCLnTabInfo != nil {
631
+ return e , GoPCLnTab , goPCLnTabInfo
632
+ }
633
+ } else {
634
+ log .Warnf ("Failed to find .gopclntab in %s: %v" , e .filePath , err )
635
+ }
636
+
527
637
if HasDWARFData (e .elfFile ) {
528
- return e , DebugInfo
638
+ return e , DebugInfo , nil
529
639
}
530
640
531
641
log .Debugf ("No debug symbols found in %s" , e .filePath )
@@ -538,7 +648,7 @@ func (e *elfWrapper) findSymbols() (*elfWrapper, SymbolSource) {
538
648
debugElf := e .findDebugSymbolsWithBuildID ()
539
649
if debugElf != nil {
540
650
if HasDWARFData (debugElf .elfFile ) {
541
- return debugElf , DebugInfo
651
+ return debugElf , DebugInfo , nil
542
652
}
543
653
debugElf .Close ()
544
654
log .Debugf ("No debug symbols found in buildID link file %s" , debugElf .filePath )
@@ -548,23 +658,23 @@ func (e *elfWrapper) findSymbols() (*elfWrapper, SymbolSource) {
548
658
debugElf = e .findDebugSymbolsWithDebugLink ()
549
659
if debugElf != nil {
550
660
if HasDWARFData (debugElf .elfFile ) {
551
- return debugElf , DebugInfo
661
+ return debugElf , DebugInfo , nil
552
662
}
553
663
log .Debugf ("No debug symbols found in debug link file %s" , debugElf .filePath )
554
664
debugElf .Close ()
555
665
}
556
666
557
667
// Check if initial elf file has a symbol table
558
668
if e .elfFile .Section (".symtab" ) != nil {
559
- return e , SymbolTable
669
+ return e , SymbolTable , nil
560
670
}
561
671
562
672
// Check if initial elf file has a dynamic symbol table
563
673
if e .elfFile .Section (".dynsym" ) != nil {
564
- return e , DynamicSymbolTable
674
+ return e , DynamicSymbolTable , nil
565
675
}
566
676
567
- return nil , None
677
+ return nil , None , nil
568
678
}
569
679
570
680
func (e * elfWrapper ) findDebugSymbolsWithBuildID () * elfWrapper {
0 commit comments