Skip to content
This repository has been archived by the owner on Sep 25, 2024. It is now read-only.

Commit

Permalink
[PROF-9872] Try to use Go buildID if GNU buildID is not present (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
nsavoire committed Sep 6, 2024
1 parent 6270dc8 commit e3174a4
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 9 deletions.
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
15 changes: 15 additions & 0 deletions libpf/pfelf/pfelf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ 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 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.
5 changes: 5 additions & 0 deletions processmanager/processinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ 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()
}

mapping2 := *mapping // copy to avoid races if callee saves the closure
open := func() (process.ReadAtCloser, error) {
return pr.OpenMappingFile(&mapping2)
Expand Down

0 comments on commit e3174a4

Please sign in to comment.