From d0ae7791af5517046db73a43597e7f56fb753dc6 Mon Sep 17 00:00:00 2001 From: Nicolas Savoire Date: Mon, 3 Jun 2024 17:16:27 +0200 Subject: [PATCH] [PROF-9872] Try to use Go buildID if GNU buildID is not present (#7) --- libpf/pfelf/file.go | 17 ++++++++ libpf/pfelf/pfelf.go | 72 ++++++++++++++++++++++++++++---- libpf/pfelf/pfelf_test.go | 16 +++++++ libpf/pfelf/testdata/go-buildid | Bin 0 -> 15584 bytes processmanager/processinfo.go | 6 +++ 5 files changed, 102 insertions(+), 9 deletions(-) create mode 100755 libpf/pfelf/testdata/go-buildid diff --git a/libpf/pfelf/file.go b/libpf/pfelf/file.go index 11c936f..30ab927 100644 --- a/libpf/pfelf/file.go +++ b/libpf/pfelf/file.go @@ -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") diff --git a/libpf/pfelf/pfelf.go b/libpf/pfelf/pfelf.go index f73a684..20adde6 100644 --- a/libpf/pfelf/pfelf.go +++ b/libpf/pfelf/pfelf.go @@ -331,6 +331,34 @@ 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) + } + if !found { + 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) { @@ -371,10 +399,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" @@ -394,10 +422,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, fmt.Errorf("could not read note data size") + return nil, false, fmt.Errorf("could not read note data size") } idxDataStart := idx + len(noteHeader) @@ -407,13 +435,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 } func symbolMapFromELFSymbols(syms []elf.Symbol) *libpf.SymbolMap { diff --git a/libpf/pfelf/pfelf_test.go b/libpf/pfelf/pfelf_test.go index 86a562b..9ac141a 100644 --- a/libpf/pfelf/pfelf_test.go +++ b/libpf/pfelf/pfelf_test.go @@ -53,6 +53,22 @@ func TestGetBuildID(t *testing.T) { } } +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) + } + + // nolint:lll + if buildID != + "tUhrGOwxi48kXlLhYlY3/WlmPekR2qonrFvofssLt/8beXJbt0rDaHhn3I6x8D/IA6Zd8Qc8Rsh_bFKoPVn" { + t.Fatalf("Invalid build-id: %s", buildID) + } +} + func TestGetDebugLink(t *testing.T) { debugExePath, err := testsupport.WriteTestExecutable1() if err != nil { diff --git a/libpf/pfelf/testdata/go-buildid b/libpf/pfelf/testdata/go-buildid new file mode 100755 index 0000000000000000000000000000000000000000..6bb47ab47359a82c3d99509b311e68b8f2dd2f62 GIT binary patch literal 15584 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 literal 0 HcmV?d00001 diff --git a/processmanager/processinfo.go b/processmanager/processinfo.go index 82103cd..c6a534e 100644 --- a/processmanager/processinfo.go +++ b/processmanager/processinfo.go @@ -313,6 +313,12 @@ func (pm *ProcessManager) getELFInfo(pr process.Process, mapping *process.Mappin } buildID, _ := ef.GetBuildID() + + if buildID == "" { + // If the buildID is empty, try to get Go buildID. + buildID, _ = ef.GetGoBuildID() + } + pm.reporter.ExecutableMetadata(context.TODO(), fileID, baseName, buildID) return info