Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion nativeunwind/elfunwindinfo/stackdeltaextraction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions nativeunwind/elfunwindinfo/stackdeltaextraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}