Skip to content
This repository was archived by the owner on Sep 25, 2024. It is now read-only.
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
9 changes: 2 additions & 7 deletions interpreter/dotnet/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ package dotnet
import (
"fmt"
"hash/fnv"
"os"
"path"
"slices"
"strings"
"sync/atomic"
Expand Down Expand Up @@ -631,11 +629,8 @@ func (i *dotnetInstance) SynchronizeMappings(ebpf interpreter.EbpfHandler,
info.simpleName, info.guid)

if !info.reported {
open := func() (process.ReadAtCloser, error) {
return os.Open(m.Path)
}
symbolReporter.ExecutableMetadata(info.fileID, path.Base(m.Path),
info.guid, libpf.Dotnet, open)
symbolReporter.ExecutableMetadata(info.fileID, m.Path,
info.guid, libpf.Dotnet, pr)
info.reported = true
}

Expand Down
17 changes: 17 additions & 0 deletions libpf/pfelf/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,23 @@ func (f *File) EHFrame() (*Prog, error) {
return nil, errors.New("no PT_LOAD segment for PT_GNU_EH_FRAME found")
}

// GetGoBuildID returns the Go BuildID if present
func (f *File) GetGoBuildID() (string, error) {
s := f.Section(".note.go.buildid")
if s == nil {
s = f.Section(".notes")
}
if s == nil {
return "", ErrNoBuildID
}
data, err := s.Data(maxBytesSmallSection)
if err != nil {
return "", err
}

return getGoBuildIDFromNotes(data)
}

// GetBuildID returns the ELF BuildID if present
func (f *File) GetBuildID() (string, error) {
s := f.Section(".note.gnu.build-id")
Expand Down
76 changes: 67 additions & 9 deletions libpf/pfelf/pfelf.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,38 @@ func GetBuildID(elfFile *elf.File) (string, error) {
return getBuildIDFromNotes(sectionData)
}

// GetBuildID extracts the Go build ID from the provided ELF file. This is read from
// the .note.go.buildid or .notes section of the ELF, and may not exist. If no build ID is present
// an ErrNoBuildID is returned.
func GetGoBuildID(elfFile *elf.File) (string, error) {
sectionData, err := getSectionData(elfFile, ".note.go.buildid")
if err != nil {
sectionData, err = getSectionData(elfFile, ".notes")
if err != nil {
return "", ErrNoBuildID
}
}

return getGoBuildIDFromNotes(sectionData)
}

// getGoBuildIDFromNotes returns the Go build ID from an ELF notes section data.
func getGoBuildIDFromNotes(notes []byte) (string, error) {
// 0x4 is the "Go Build ID" type. Not sure where this is standardized.
buildID, found, err := getNoteString(notes, "Go", 0x4)
if err != nil {
return "", fmt.Errorf("could not determine BuildID: %v", err)
}
//nolint:lll
// When building Go binaries, Bazel explicitly sets their build ID to "redacted"
// see https://github.com/bazelbuild/rules_go/blob/199d8e4827f87d382a85febd0148c1b42fa949cc/go/private/actions/link.bzl#L174.
// In that case, we don't want to associate the build ID with the binary in the mapping.
if !found || buildID == "redacted" {
return "", ErrNoBuildID
}
return buildID, nil
}

// GetBuildIDFromNotesFile returns the build ID contained in a file with the format of an ELF notes
// section.
func GetBuildIDFromNotesFile(filePath string) (string, error) {
Expand Down Expand Up @@ -261,10 +293,10 @@ func GetSectionAddress(e *elf.File, sectionName string) (
return section.Addr, true, nil
}

// getNoteHexString returns the hex string contents of an ELF note from a note section, as described
// getNoteDescBytes returns the bytes contents of an ELF note from a note section, as described
// in the ELF standard in Figure 2-3.
func getNoteHexString(sectionBytes []byte, name string, noteType uint32) (
noteHexString string, found bool, err error) {
func getNoteDescBytes(sectionBytes []byte, name string, noteType uint32) (
noteBytes []byte, found bool, err error) {
// The data stored inside ELF notes is made of one or multiple structs, containing the
// following fields:
// - namesz // 32-bit, size of "name"
Expand All @@ -284,10 +316,10 @@ func getNoteHexString(sectionBytes []byte, name string, noteType uint32) (
// Try to find the note in the section
idx := bytes.Index(sectionBytes, noteHeader)
if idx == -1 {
return "", false, nil
return nil, false, nil
}
if idx < 4 { // there needs to be room for descsz
return "", false, errors.New("could not read note data size")
return nil, false, errors.New("could not read note data size")
}

idxDataStart := idx + len(noteHeader)
Expand All @@ -297,13 +329,39 @@ func getNoteHexString(sectionBytes []byte, name string, noteType uint32) (
dataSize := binary.LittleEndian.Uint32(sectionBytes[idx-4 : idx])
idxDataEnd := uint64(idxDataStart) + uint64(dataSize)

// Check sanity (64 is totally arbitrary, as we only use it for Linux ID and Build ID)
if idxDataEnd > uint64(len(sectionBytes)) || dataSize > 64 {
return "", false, fmt.Errorf(
// Check sanity (84 is totally arbitrary, as we only use it for Linux ID and (Go) Build ID)
if idxDataEnd > uint64(len(sectionBytes)) || dataSize > 84 {
return nil, false, fmt.Errorf(
"non-sensical note: %d start index: %d, %v end index %d, size %d, section size %d",
idx, idxDataStart, noteHeader, idxDataEnd, dataSize, len(sectionBytes))
}
return hex.EncodeToString(sectionBytes[idxDataStart:idxDataEnd]), true, nil
return sectionBytes[idxDataStart:idxDataEnd], true, nil
}

// getNoteHexString returns the hex string contents of an ELF note from a note section, as described
// in the ELF standard in Figure 2-3.
func getNoteHexString(sectionBytes []byte, name string, noteType uint32) (
noteHexString string, found bool, err error) {
noteBytes, found, err := getNoteDescBytes(sectionBytes, name, noteType)
if err != nil {
return "", false, err
}
if !found {
return "", false, nil
}
return hex.EncodeToString(noteBytes), true, nil
}

func getNoteString(sectionBytes []byte, name string, noteType uint32) (
noteString string, found bool, err error) {
noteBytes, found, err := getNoteDescBytes(sectionBytes, name, noteType)
if err != nil {
return "", false, err
}
if !found {
return "", false, nil
}
return string(noteBytes), true, nil
}

// GetLinuxBuildSalt extracts the linux kernel build salt from the provided ELF path.
Expand Down
25 changes: 25 additions & 0 deletions libpf/pfelf/pfelf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,31 @@ func TestGetBuildID(t *testing.T) {
assert.Equal(t, "6920fd217a8416131f4377ef018a2c932f311b6d", buildID)
}

func TestGetGoBuildID(t *testing.T) {
elfFile := getELF("testdata/go-buildid", t)
defer elfFile.Close()

buildID, err := pfelf.GetGoBuildID(elfFile)
if err != nil {
t.Fatalf("GetGoBuildID failed with error: %s", err)
}

if buildID !=
"tUhrGOwxi48kXlLhYlY3/WlmPekR2qonrFvofssLt/8beXJbt0rDaHhn3I6x8D/IA6Zd8Qc8Rsh_bFKoPVn" {
t.Fatalf("Invalid build-id: %s", buildID)
}
}

func TestGetGoBuildIDError(t *testing.T) {
elfFile := getELF("testdata/go-buildid-bazel", t)
defer elfFile.Close()

buildID, err := pfelf.GetGoBuildID(elfFile)
if assert.ErrorIs(t, pfelf.ErrNoBuildID, err) {
assert.Equal(t, "", buildID)
}
}

func TestGetDebugLink(t *testing.T) {
debugExePath, err := testsupport.WriteTestExecutable1()
require.NoError(t, err)
Expand Down
Binary file added libpf/pfelf/testdata/go-buildid
Binary file not shown.
Binary file added libpf/pfelf/testdata/go-buildid-bazel
Binary file not shown.
5 changes: 5 additions & 0 deletions process/coredump.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,11 @@ func (cd *CoredumpProcess) OpenELF(path string) (*pfelf.File, error) {
return nil, fmt.Errorf("ELF file `%s` not found", path)
}

// Open implements the FileOpener and Process interfaces
func (cd *CoredumpProcess) Open(path string) (ReadAtCloser, string, error) {
return nil, path, errors.New("coredump does not support opening files")
}

// getFile returns (creating if needed) a matching CoredumpFile for given file name
func (cd *CoredumpProcess) getFile(name string) *CoredumpFile {
if cf, ok := cd.files[name]; ok {
Expand Down
33 changes: 33 additions & 0 deletions process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,36 @@ func (sp *systemProcess) OpenELF(file string) (*pfelf.File, error) {
// Fall back to opening the file using the process specific root
return pfelf.Open(fmt.Sprintf("/proc/%v/root/%s", sp.pid, file))
}

type ReaderWithDummyClose struct {
io.ReaderAt
}

func (r *ReaderWithDummyClose) Close() error {
return nil
}

func (sp *systemProcess) Open(file string) (ReadAtCloser, string, error) {
// Always open via map_files as it can open deleted files if available.
// No fallback is attempted:
// - if the process exited, the fallback will error also (/proc/>PID> is gone)
// - if the error is due to ELF content, same error will occur in both cases
// - if the process unmapped the ELF, its data is no longer needed
if m, ok := sp.fileToMapping[file]; ok {
if m.IsVDSO() {
vdso, err := sp.extractMapping(m)
if err != nil {
return nil, "", fmt.Errorf("failed to extract VDSO: %v", err)
}
return &ReaderWithDummyClose{vdso}, "", nil
}
path := sp.getMappingFile(m)
f, err := os.Open(path)
return f, path, err
}

// Fall back to opening the file using the process specific root
path := fmt.Sprintf("/proc/%v/root/%s", sp.pid, file)
f, err := os.Open(path)
return f, path, err
}
6 changes: 6 additions & 0 deletions process/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ type ReadAtCloser interface {
io.Closer
}

type FileOpener interface {
Open(string) (reader ReadAtCloser, actualPath string, err error)
}

// Process is the interface to inspect ELF coredump/process.
// The current implementations do not allow concurrent access to this interface
// from different goroutines. As an exception the ELFOpener and the returned
Expand Down Expand Up @@ -125,4 +129,6 @@ type Process interface {
io.Closer

pfelf.ELFOpener

FileOpener
}
7 changes: 6 additions & 1 deletion processmanager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ func (d *dummyProcess) Close() error {
return nil
}

func (d *dummyProcess) Open(file string) (process.ReadAtCloser, string, error) {
f, err := os.Open(file)
return f, file, err
}

func newTestProcess(pid libpf.PID) process.Process {
return &dummyProcess{pid: pid}
}
Expand Down Expand Up @@ -255,7 +260,7 @@ type symbolReporterMockup struct{}
func (s *symbolReporterMockup) ReportFallbackSymbol(_ libpf.FrameID, _ string) {}

func (s *symbolReporterMockup) ExecutableMetadata(_ libpf.FileID, _, _ string,
_ libpf.InterpreterType, _ reporter.ExecutableOpener) {
_ libpf.InterpreterType, _ process.FileOpener) {
}

func (s *symbolReporterMockup) FrameMetadata(_ libpf.FileID, _ libpf.AddressOrLineno,
Expand Down
19 changes: 5 additions & 14 deletions processmanager/processinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"errors"
"fmt"
"os"
"path"
"syscall"
"time"

Expand Down Expand Up @@ -291,21 +290,13 @@ func (pm *ProcessManager) getELFInfo(pr process.Process, mapping *process.Mappin
}
pm.FileIDMapper.Set(hostFileID, fileID)

baseName := path.Base(mapping.Path)
if baseName == "/" {
// There are circumstances where there is no filename.
// E.g. kernel module 'bpfilter_umh' before Linux 5.9-rc1 uses
// fork_usermode_blob() and launches process with a blob without
// filename mapped in as the executable.
baseName = "<anonymous-blob>"
}

buildID, _ := ef.GetBuildID()
mapping2 := *mapping // copy to avoid races if callee saves the closure
open := func() (process.ReadAtCloser, error) {
return pr.OpenMappingFile(&mapping2)
if buildID == "" {
// If the buildID is empty, try to get Go buildID.
buildID, _ = ef.GetGoBuildID()
}
pm.reporter.ExecutableMetadata(fileID, baseName, buildID, libpf.Native, open)

pm.reporter.ExecutableMetadata(fileID, mapping.Path, buildID, libpf.Native, pr)

return info
}
Expand Down
4 changes: 1 addition & 3 deletions reporter/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ type TraceReporter interface {
SupportsReportTraceEvent() bool
}

type ExecutableOpener = func() (process.ReadAtCloser, error)

type SymbolReporter interface {
// ReportFallbackSymbol enqueues a fallback symbol for reporting, for a given frame.
ReportFallbackSymbol(frameID libpf.FrameID, symbol string)
Expand All @@ -67,7 +65,7 @@ type SymbolReporter interface {
// wish to upload executables should NOT block this function to do so and instead just
// open the file and then enqueue the upload in the background.
ExecutableMetadata(fileID libpf.FileID, fileName, buildID string,
interp libpf.InterpreterType, open ExecutableOpener)
interp libpf.InterpreterType, opener process.FileOpener)

// FrameMetadata accepts metadata associated with a frame and caches this information before
// a periodic reporting to the backend.
Expand Down
3 changes: 2 additions & 1 deletion reporter/otlp_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf/xsync"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/process"
)

var (
Expand Down Expand Up @@ -206,7 +207,7 @@ func (r *OTLPReporter) ReportFallbackSymbol(frameID libpf.FrameID, symbol string
// ExecutableMetadata accepts a fileID with the corresponding filename
// and caches this information.
func (r *OTLPReporter) ExecutableMetadata(fileID libpf.FileID, fileName,
buildID string, _ libpf.InterpreterType, _ ExecutableOpener) {
buildID string, _ libpf.InterpreterType, _ process.FileOpener) {
r.executables.Add(fileID, execInfo{
fileName: fileName,
buildID: buildID,
Expand Down
3 changes: 1 addition & 2 deletions tools/coredump/coredump.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/open-telemetry/opentelemetry-ebpf-profiler/nativeunwind/elfunwindinfo"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/process"
pm "github.com/open-telemetry/opentelemetry-ebpf-profiler/processmanager"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/reporter"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/support"
tracertypes "github.com/open-telemetry/opentelemetry-ebpf-profiler/tracer/types"
)
Expand Down Expand Up @@ -65,7 +64,7 @@ func newSymbolizationCache() *symbolizationCache {
}

func (c *symbolizationCache) ExecutableMetadata(fileID libpf.FileID,
fileName, _ string, _ libpf.InterpreterType, _ reporter.ExecutableOpener) {
fileName, _ string, _ libpf.InterpreterType, _ process.FileOpener) {
c.files[fileID] = fileName
}

Expand Down
4 changes: 2 additions & 2 deletions tracer/ebpf_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (

"github.com/open-telemetry/opentelemetry-ebpf-profiler/host"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/reporter"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/process"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/rlimit"
"github.com/open-telemetry/opentelemetry-ebpf-profiler/support"
tracertypes "github.com/open-telemetry/opentelemetry-ebpf-profiler/tracer/types"
Expand Down Expand Up @@ -95,7 +95,7 @@ func (f mockIntervals) PIDCleanupInterval() time.Duration { return 1 * time.Seco
type mockReporter struct{}

func (f mockReporter) ExecutableMetadata(_ libpf.FileID, _, _ string,
_ libpf.InterpreterType, _ reporter.ExecutableOpener) {
_ libpf.InterpreterType, _ process.FileOpener) {
}
func (f mockReporter) ReportFallbackSymbol(_ libpf.FrameID, _ string) {}
func (f mockReporter) FrameMetadata(_ libpf.FileID, _ libpf.AddressOrLineno, _ libpf.SourceLineno,
Expand Down