Skip to content
Merged
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
44 changes: 34 additions & 10 deletions nativeunwind/elfunwindinfo/elfgopclntab.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ package elfunwindinfo // import "go.opentelemetry.io/ebpf-profiler/nativeunwind/
import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"go/version"
"io"
Expand Down Expand Up @@ -50,6 +52,12 @@ const (
go1_16 = 16
go1_18 = 18
go1_20 = 20

// Offset of the text field in moduledata struct for Go 1.16+
// https://github.com/golang/go/blob/release-branch.go1.16/src/runtime/symtab.go#L370
textOffset = 22 * 8
// section name for the module data for Go 1.26+
moduleDataSectionName = ".go.module"
)

func goMagicToVersion(magic uint32) uint8 {
Expand Down Expand Up @@ -469,16 +477,13 @@ func NewGopclntab(ef *pfelf.File) (*Gopclntab, error) {
g.funcdata = g.functab
g.textStart = hdr118.textStart
if g.textStart == 0 {
// Starting with Go 1.26 the field textStart is set to 0 but moduledata.text
// which contains the same value is unaffected.
// The following logic assumes that .text matches moduledata.text
// which might not always be true (in which case we need to switch to moduledata.text
// which can be found through runtime.firstmoduledata).
//
//nolint:lll
// See https://github.com/golang/go/commit/0e1bd8b5f17e337df0ffb57af03419b96c695fe4
if sec := ef.Section(".text"); sec != nil {
g.textStart = uintptr(sec.Addr)
// Starting from Go 1.26, textStart address in pclntab is always set to 0.
// Therefore we need to get it from either `runtime.text` symbol or moduledata.
// Note that it does not always match the address of `.text` section
// (for example with cgo binaries or when built with -linkmode=external).
g.textStart, err = findTextStart(ef)
if err != nil {
return nil, fmt.Errorf("failed to find text start: %w", err)
}
}
// With the change of the type of the first field of _func in Go 1.18, this
Expand Down Expand Up @@ -590,6 +595,25 @@ func (g *Gopclntab) Symbolize(pc uintptr) (sourceFile string, line uint, funcNam
return sourceFile, line, funcName
}

func findTextStart(ef *pfelf.File) (uintptr, error) {
// Get textstart from moduledata
// Starting from Go 1.26, moduledata has its own `.go.module` section.
// Since this function is expected to be called only for Go 1.26+ binaries,
// we can expect that the section exists and error out if it does not.
moduleDataSection := ef.Section(moduleDataSectionName)
if moduleDataSection == nil || moduleDataSection.Type == elf.SHT_NOBITS {
return 0, errors.New("could not find .go.module section or it is empty")
}

var textBytes [8]byte
_, err := moduleDataSection.ReadAt(textBytes[:], textOffset)
if err != nil {
return 0, fmt.Errorf("could not read .go.module section at offset %v: %w", textOffset, err)
}

return uintptr(binary.LittleEndian.Uint64(textBytes[:])), nil
}

type strategy int

const (
Expand Down
35 changes: 35 additions & 0 deletions nativeunwind/elfunwindinfo/elfgopclntab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"debug/elf"
"testing"

"go.opentelemetry.io/ebpf-profiler/libpf"
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes"

Expand Down Expand Up @@ -96,3 +97,37 @@ func TestParseGoPclntab(t *testing.T) {
})
}
}

func TestTextStart(t *testing.T) {
ef, err := pfelf.Open("testdata/helloworld.linkexternal")
require.NoError(t, err)
defer ef.Close()

var runtimeTextAddr uintptr
ef.VisitSymbols(func(sym libpf.Symbol) bool {
if sym.Name == "runtime.text" {
runtimeTextAddr = uintptr(sym.Address)
return false
}
return true
})
require.NotZero(t, runtimeTextAddr)

g, err := NewGopclntab(ef)
require.NoError(t, err)
require.NotNil(t, g)
defer g.Close()

require.Equal(t, runtimeTextAddr, g.textStart)

// stripped binary should have the same text start
efStripped, err := pfelf.Open("testdata/helloworld.linkexternal.stripped")
require.NoError(t, err)
defer efStripped.Close()
gStripped, err := NewGopclntab(efStripped)
require.NoError(t, err)
require.NotNil(t, gStripped)
defer gStripped.Close()

require.Equal(t, runtimeTextAddr, gStripped.textStart)
}
10 changes: 9 additions & 1 deletion nativeunwind/elfunwindinfo/testdata/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
BINARIES=helloworld \
helloworld.pie \
helloworld.stripped.pie \
helloworld.arm64
helloworld.arm64 \
helloworld.linkexternal \
helloworld.linkexternal.stripped

# Use the default go executable if it is not specified otherwise.
GO_BINARY ?= go
Expand All @@ -24,3 +26,9 @@ helloworld.stripped.pie:

helloworld.arm64:
GOARCH=arm64 $(GO_BINARY) build -o $@ helloworld.go

helloworld.linkexternal:
CGO_ENABLED=1 GOTOOLCHAIN=go1.26rc2 $(GO_BINARY) build -ldflags="-linkmode=external" -o $@ helloworld.go

helloworld.linkexternal.stripped: helloworld.linkexternal
objcopy -S $< $@
Loading