diff --git a/libpf/pfelf/file.go b/libpf/pfelf/file.go index 5229eee49..75d8e4c6e 100644 --- a/libpf/pfelf/file.go +++ b/libpf/pfelf/file.go @@ -21,6 +21,7 @@ package pfelf // import "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" import ( "bytes" + "debug/buildinfo" "debug/elf" "errors" "fmt" @@ -28,6 +29,7 @@ import ( "io" "os" "path/filepath" + "runtime/debug" "sort" "syscall" "unsafe" @@ -119,6 +121,9 @@ type File struct { debuglinkPath string // Whether we have checked for a debuglink debuglinkChecked bool + + // Contains the Go build information if present + goBuildInfo *debug.BuildInfo } var _ libpf.SymbolFinder = &File{} @@ -489,6 +494,26 @@ func (f *File) GetBuildID() (string, error) { return getBuildIDFromNotes(data) } +// GoVersion returns the Go version if present and empty string otherwise. This will delegate +// to buildinfo.Read for any binaries where IsGolang is true which will scan the binary with +// debug/elf. This will incur additional CPU/IO overhead but the libpf.readbufat buffer and +// OS file buffers should ameliorate most of that. +func (f *File) GoVersion() (string, error) { + if f.goBuildInfo != nil { + return f.goBuildInfo.GoVersion, nil + } + if !f.IsGolang() { + return "", nil + } + bi, err := buildinfo.Read(f.elfReader) + if err != nil { + return "", err + } + f.goBuildInfo = bi + + return bi.GoVersion, nil +} + // DebuglinkFileName returns the debug file linked by .gnu_debuglink if any func (f *File) DebuglinkFileName(elfFilePath string, elfOpener ELFOpener) string { if f.debuglinkChecked { diff --git a/libpf/pfelf/file_test.go b/libpf/pfelf/file_test.go index 5da840197..9e8f4a95f 100644 --- a/libpf/pfelf/file_test.go +++ b/libpf/pfelf/file_test.go @@ -4,7 +4,9 @@ package pfelf import ( + "go/version" "os" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -78,3 +80,18 @@ func TestPFELFIsGolang(t *testing.T) { testPFELFIsGolang(t, "testdata/go-binary", true) testPFELFIsGolang(t, "testdata/without-debug-syms", false) } + +func TestGoVersion(t *testing.T) { + ef := getPFELF("testdata/go-binary", t) + defer ef.Close() + + vers, err := ef.GoVersion() + require.NoError(t, err) + assert.GreaterOrEqual(t, version.Compare(vers, "go1.23.6"), 0) + + testEF := getPFELF("/proc/self/exe", t) + defer testEF.Close() + testVersion, err := testEF.GoVersion() + require.NoError(t, err) + assert.Equal(t, runtime.Version(), testVersion) +} diff --git a/libpf/pfelf/testdata/Makefile b/libpf/pfelf/testdata/Makefile index 57231c121..757c2801a 100644 --- a/libpf/pfelf/testdata/Makefile +++ b/libpf/pfelf/testdata/Makefile @@ -44,7 +44,6 @@ kernel-image: test.c ubuntu-kernel-image: test.c $(CC) $< -s -o $@ -DLINUX_VERSION="\"Linux version 1.2.3 (Ubuntu 4.5.6)\\n\"" -# A fake go binary (with a .gopclntab section) -go-binary: without-debug-syms - $(OBJCOPY) --add-section .gopclntab=/dev/null $< $@ +go-binary: gotest.go + go build -o go-binary -ldflags "-w -s" gotest.go diff --git a/libpf/pfelf/testdata/gotest.go b/libpf/pfelf/testdata/gotest.go new file mode 100644 index 000000000..da29a2cad --- /dev/null +++ b/libpf/pfelf/testdata/gotest.go @@ -0,0 +1,4 @@ +package main + +func main() { +}