diff --git a/nativeunwind/elfunwindinfo/stackdeltaextraction.go b/nativeunwind/elfunwindinfo/stackdeltaextraction.go index 43cc73a92..619d639d9 100644 --- a/nativeunwind/elfunwindinfo/stackdeltaextraction.go +++ b/nativeunwind/elfunwindinfo/stackdeltaextraction.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes" + "go.opentelemetry.io/ebpf-profiler/util" ) const ( @@ -20,6 +21,18 @@ const ( numIntervalsToOmitDebugLink = 20 ) +type Option func(*elfExtractor, *extractionFilter) + +// WithCaptureFDEHook configures ExtractELF to capture FDE information for a specific address. +// When an FDE contains the given address 'at', the hook function is called with the address +// range covered by that FDE. +func WithCaptureFDEHook(at uintptr, hook func(p util.Range)) Option { + return func(ee *elfExtractor, f *extractionFilter) { + f.captureFDEAt = at + f.capturedFDEHook = hook + } +} + // extractionFilter is used to filter in .eh_frame data when a better source // is available (.gopclntab). type extractionFilter struct { @@ -35,12 +48,23 @@ type extractionFilter struct { // unsortedFrames is set if stack deltas from unsorted source are found unsortedFrames bool + + captureFDEAt uintptr + capturedFDEHook func(util.Range) } var _ ehframeHooks = &extractionFilter{} // fdeHook filters out .eh_frame data that is superseded by .gopclntab data func (f *extractionFilter) fdeHook(_ *cieInfo, fde *fdeInfo) bool { + if f.captureFDEAt != 0 && + f.captureFDEAt >= fde.ipStart && + f.captureFDEAt < fde.ipStart+fde.ipLen { + f.capturedFDEHook(util.Range{ + Start: uint64(fde.ipStart), + End: uint64(fde.ipStart + fde.ipLen), + }) + } if !fde.sorted { // Seems .debug_frame sometimes has broken FDEs for zero address if fde.ipStart == 0 { @@ -117,7 +141,7 @@ func Extract(filename string, interval *sdtypes.IntervalData) error { // ExtractELF takes a pfelf.Reference and provides the stack delta // intervals for it in the interval parameter. -func ExtractELF(elfRef *pfelf.Reference, interval *sdtypes.IntervalData) error { +func ExtractELF(elfRef *pfelf.Reference, interval *sdtypes.IntervalData, opt ...Option) error { elfFile, err := elfRef.GetELF() if err != nil { return err @@ -133,6 +157,9 @@ func ExtractELF(elfRef *pfelf.Reference, interval *sdtypes.IntervalData) error { hooks: &filter, allowGenericRegs: isLibCrypto(elfFile), } + for _, o := range opt { + o(&ee, &filter) + } if err = ee.parseGoPclntab(); err != nil { return fmt.Errorf("failure to parse golang stack deltas: %v", err) diff --git a/nativeunwind/elfunwindinfo/stackdeltaextraction_test.go b/nativeunwind/elfunwindinfo/stackdeltaextraction_test.go index 04738ce83..594937bc4 100644 --- a/nativeunwind/elfunwindinfo/stackdeltaextraction_test.go +++ b/nativeunwind/elfunwindinfo/stackdeltaextraction_test.go @@ -6,9 +6,12 @@ package elfunwindinfo import ( "encoding/base64" "os" + "path/filepath" "testing" + "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes" + "go.opentelemetry.io/ebpf-profiler/util" "github.com/stretchr/testify/require" ) @@ -157,3 +160,22 @@ func TestExtractStackDeltasFromFilename(t *testing.T) { } require.Equal(t, data.Deltas[:len(firstDeltas)], firstDeltas) } + +func TestCaptureFDE(t *testing.T) { + buffer, err := base64.StdEncoding.DecodeString(usrBinVolname) + require.NoError(t, err) + filename := filepath.Join(t.TempDir(), "dwarf_extract_elf_") + err = os.WriteFile(filename, buffer, 0o600) + require.NoError(t, err) + elfRef := pfelf.NewReference(filename, pfelf.SystemOpener) + defer elfRef.Close() + expected := util.Range{Start: 0x8d0, End: 0x9EF} + actual := util.Range{} + err = ExtractELF(elfRef, new(sdtypes.IntervalData), + WithCaptureFDEHook(0x973, func(p util.Range) { + actual = p + }), + ) + require.NoError(t, err) + require.Equal(t, expected, actual) +}