From 662e2e2ec7a65952e6dc31b89f818432fc5878a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Thu, 6 Nov 2025 17:31:40 +0200 Subject: [PATCH 1/6] Intern ebpf comm and labels (#921) --- host/host.go | 4 ++-- .../integrationtests/golabels_integration_test.go | 5 +++-- libpf/trace.go | 2 +- reporter/internal/pdata/generate.go | 6 +++--- reporter/internal/pdata/generate_test.go | 4 ++-- reporter/samples/attrmgr_test.go | 13 +++++++------ reporter/samples/samples.go | 6 +++--- tracer/ebpf_integration_test.go | 7 ++++--- tracer/tracer.go | 6 +++--- 9 files changed, 28 insertions(+), 25 deletions(-) diff --git a/host/host.go b/host/host.go index b74d9c788..1adc80f07 100644 --- a/host/host.go +++ b/host/host.go @@ -42,7 +42,7 @@ type Frame struct { } type Trace struct { - Comm string + Comm libpf.String ProcessName libpf.String ExecutablePath libpf.String ContainerID libpf.String @@ -56,6 +56,6 @@ type Trace struct { APMTransactionID libpf.APMTransactionID CPU int EnvVars map[libpf.String]libpf.String - CustomLabels map[string]string + CustomLabels map[libpf.String]libpf.String KernelFrames libpf.Frames } diff --git a/interpreter/golabels/integrationtests/golabels_integration_test.go b/interpreter/golabels/integrationtests/golabels_integration_test.go index 6953b64c9..dab18c58f 100644 --- a/interpreter/golabels/integrationtests/golabels_integration_test.go +++ b/interpreter/golabels/integrationtests/golabels_integration_test.go @@ -134,8 +134,9 @@ func Test_Golabels(t *testing.T) { } if len(trace.CustomLabels) > 0 { hits := 0 - for k, v := range trace.CustomLabels { - t.Logf("Received label %v=%v", k, v) + for ks, vs := range trace.CustomLabels { + k := ks.String() + v := vs.String() if strings.HasPrefix(k, "l1") { require.Len(t, v, 22) require.True(t, strings.HasPrefix(v, "label1")) diff --git a/libpf/trace.go b/libpf/trace.go index 0a6032bb2..0a71838a8 100644 --- a/libpf/trace.go +++ b/libpf/trace.go @@ -76,5 +76,5 @@ func (frames *Frames) Append(frame *Frame) { type Trace struct { Frames Frames Hash TraceHash - CustomLabels map[string]string + CustomLabels map[String]String } diff --git a/reporter/internal/pdata/generate.go b/reporter/internal/pdata/generate.go index 8eb224235..97919722b 100644 --- a/reporter/internal/pdata/generate.go +++ b/reporter/internal/pdata/generate.go @@ -251,7 +251,7 @@ func (p *Pdata) setProfile( } attrMgr.AppendOptionalString(sample.AttributeIndices(), - semconv.ThreadNameKey, traceKey.Comm) + semconv.ThreadNameKey, traceKey.Comm.String()) attrMgr.AppendOptionalString(sample.AttributeIndices(), semconv.ProcessExecutableNameKey, exeName) @@ -278,8 +278,8 @@ func (p *Pdata) setProfile( // reached an agreement, use the actual OTel SemConv attribute. attrMgr.AppendOptionalString( sample.AttributeIndices(), - attribute.Key("process.context.label."+key), - value) + attribute.Key("process.context.label."+key.String()), + value.String()) } if p.ExtraSampleAttrProd != nil { diff --git a/reporter/internal/pdata/generate_test.go b/reporter/internal/pdata/generate_test.go index 5f89386e8..448bf430c 100644 --- a/reporter/internal/pdata/generate_test.go +++ b/reporter/internal/pdata/generate_test.go @@ -281,7 +281,7 @@ func TestGenerate_SingleContainerSingleOrigin(t *testing.T) { traceKey := samples.TraceAndMetaKey{ ExecutablePath: filePath, - Comm: "testproc", + Comm: libpf.Intern("testproc"), Pid: 123, Tid: 456, ApmServiceName: "svc", @@ -475,7 +475,7 @@ func TestGenerate_NativeFrame(t *testing.T) { traceKey := samples.TraceAndMetaKey{ ExecutablePath: filePath, - Comm: "native_app", + Comm: libpf.Intern("native_app"), Pid: 789, Tid: 1011, } diff --git a/reporter/samples/attrmgr_test.go b/reporter/samples/attrmgr_test.go index ec075c7ac..6917eeaab 100644 --- a/reporter/samples/attrmgr_test.go +++ b/reporter/samples/attrmgr_test.go @@ -22,6 +22,8 @@ type attributeStruct struct { } func TestAttrTableManager(t *testing.T) { + comm1 := libpf.Intern("comm1") + comm2 := libpf.Intern("comm2") tests := map[string]struct { k []TraceAndMetaKey expectedIndices [][]int32 @@ -31,7 +33,6 @@ func TestAttrTableManager(t *testing.T) { k: []TraceAndMetaKey{ { Hash: libpf.TraceHash{}, - Comm: "", ApmServiceName: "", Pid: 0, }, @@ -45,13 +46,13 @@ func TestAttrTableManager(t *testing.T) { k: []TraceAndMetaKey{ { Hash: libpf.TraceHash{}, - Comm: "comm1", + Comm: comm1, ApmServiceName: "apmServiceName1", Pid: 1234, }, { Hash: libpf.TraceHash{}, - Comm: "comm1", + Comm: comm1, ApmServiceName: "apmServiceName1", Pid: 1234, }, @@ -67,13 +68,13 @@ func TestAttrTableManager(t *testing.T) { k: []TraceAndMetaKey{ { Hash: libpf.TraceHash{}, - Comm: "comm1", + Comm: comm1, ApmServiceName: "apmServiceName1", Pid: 1234, }, { Hash: libpf.TraceHash{}, - Comm: "comm2", + Comm: comm2, ApmServiceName: "apmServiceName2", Pid: 6789, }, @@ -98,7 +99,7 @@ func TestAttrTableManager(t *testing.T) { indices := make([][]int32, 0) for _, k := range tc.k { inner := pcommon.NewInt32Slice() - mgr.AppendOptionalString(inner, semconv.ThreadNameKey, k.Comm) + mgr.AppendOptionalString(inner, semconv.ThreadNameKey, k.Comm.String()) mgr.AppendOptionalString(inner, semconv.ServiceNameKey, k.ApmServiceName) mgr.AppendInt(inner, semconv.ProcessPIDKey, k.Pid) indices = append(indices, inner.AsRaw()) diff --git a/reporter/samples/samples.go b/reporter/samples/samples.go index 4ef54add0..4e1559a87 100644 --- a/reporter/samples/samples.go +++ b/reporter/samples/samples.go @@ -9,7 +9,7 @@ import ( type TraceEventMeta struct { Timestamp libpf.UnixTime64 - Comm string + Comm libpf.String ProcessName libpf.String ExecutablePath libpf.String APMServiceName string @@ -27,7 +27,7 @@ type TraceEvents struct { Timestamps []uint64 // in nanoseconds OffTimes []int64 // in nanoseconds EnvVars map[libpf.String]libpf.String - Labels map[string]string + Labels map[libpf.String]libpf.String } // TraceAndMetaKey is the deduplication key for samples. This **must always** @@ -38,7 +38,7 @@ type TraceAndMetaKey struct { // to not aggregate difference traces. Hash libpf.TraceHash // comm and apmServiceName are provided by the eBPF programs - Comm string + Comm libpf.String ApmServiceName string Pid int64 Tid int64 diff --git a/tracer/ebpf_integration_test.go b/tracer/ebpf_integration_test.go index 5eb92effc..f6e6849ee 100644 --- a/tracer/ebpf_integration_test.go +++ b/tracer/ebpf_integration_test.go @@ -144,9 +144,10 @@ Loop: case <-timeout.C: break Loop case trace := <-traceChan: - require.GreaterOrEqual(t, len(trace.Comm), 4) - require.Equal(t, "\xAA\xBB\xCC", trace.Comm[0:3]) - traces[trace.Comm[3]] = trace + comm := trace.Comm.String() + require.GreaterOrEqual(t, len(comm), 4) + require.Equal(t, "\xAA\xBB\xCC", comm[0:3]) + traces[comm[3]] = trace } } diff --git a/tracer/tracer.go b/tracer/tracer.go index ddbd86c37..c1eb5b884 100644 --- a/tracer/tracer.go +++ b/tracer/tracer.go @@ -178,12 +178,12 @@ type progLoaderHelper struct { } // Convert a C-string to Go string. -func goString(cstr []byte) string { +func goString(cstr []byte) libpf.String { index := bytes.IndexByte(cstr, byte(0)) if index < 0 { index = len(cstr) } - return strings.Clone(pfunsafe.ToString(cstr[:index])) + return libpf.Intern(pfunsafe.ToString(cstr[:index])) } // schedProcessFreeHookName returns the name of the tracepoint hook to use. @@ -912,7 +912,7 @@ func (t *Tracer) loadBpfTrace(raw []byte, cpu int) *host.Trace { } if ptr.Custom_labels.Len > 0 { - trace.CustomLabels = make(map[string]string, int(ptr.Custom_labels.Len)) + trace.CustomLabels = make(map[libpf.String]libpf.String, int(ptr.Custom_labels.Len)) for i := 0; i < int(ptr.Custom_labels.Len); i++ { lbl := ptr.Custom_labels.Labels[i] key := goString(lbl.Key[:]) From a47b46abb4337e36bd85966fe7fb220fce296bc8 Mon Sep 17 00:00:00 2001 From: Olivier G <52180542+ogaca-dd@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:23:43 +0100 Subject: [PATCH 2/6] Move config validate (#897) Co-authored-by: Mackenzie Co-authored-by: Florian Lehner --- cli_flags.go | 3 +- collector/config/config.go | 81 ++++++++++++++++++++++++++++++++- collector/config/config_test.go | 20 ++++++++ go.mod | 10 ++++ go.sum | 20 ++++++++ internal/controller/config.go | 76 +------------------------------ 6 files changed, 133 insertions(+), 77 deletions(-) create mode 100644 collector/config/config_test.go diff --git a/cli_flags.go b/cli_flags.go index f743de45e..7ab7d7327 100644 --- a/cli_flags.go +++ b/cli_flags.go @@ -11,6 +11,7 @@ import ( "github.com/peterbourgon/ff/v3" + "go.opentelemetry.io/ebpf-profiler/collector/config" "go.opentelemetry.io/ebpf-profiler/internal/controller" "go.opentelemetry.io/ebpf-profiler/tracer" ) @@ -42,7 +43,7 @@ var ( mapScaleFactorHelp = fmt.Sprintf("Scaling factor for eBPF map sizes. "+ "Every increase by 1 doubles the map size. Increase if you see eBPF map size errors. "+ "Default is %d corresponding to 4GB of executable address space, max is %d.", - defaultArgMapScaleFactor, controller.MaxArgMapScaleFactor) + defaultArgMapScaleFactor, config.MaxArgMapScaleFactor) disableTLSHelp = "Disable encryption for data in transit." bpfVerifierLogLevelHelp = "Log level of the eBPF verifier output (0,1,2). Default is 0." versionHelp = "Show version." diff --git a/collector/config/config.go b/collector/config/config.go index 97d24c3f0..b9f46f8f1 100644 --- a/collector/config/config.go +++ b/collector/config/config.go @@ -3,7 +3,19 @@ package config // import "go.opentelemetry.io/ebpf-profiler/collector/config" -import "time" +import ( + "errors" + "fmt" + "runtime" + "time" + + "go.opentelemetry.io/ebpf-profiler/tracer" +) + +const ( + // 1TB of executable address space + MaxArgMapScaleFactor = 8 +) // Config is the configuration for the collector. type Config struct { @@ -27,3 +39,70 @@ type Config struct { MaxGRPCRetries uint32 `mapstructure:"max_grpc_retries"` MaxRPCMsgSize int `mapstructure:"max_rpc_msg_size"` } + +// Validate validates the config. +// This is automatically called by the config parser as it implements the xconfmap.Validator interface. +func (cfg *Config) Validate() error { + if cfg.SamplesPerSecond < 1 { + return fmt.Errorf("invalid sampling frequency: %d", cfg.SamplesPerSecond) + } + + if cfg.MapScaleFactor > MaxArgMapScaleFactor { + return fmt.Errorf( + "eBPF map scaling factor %d exceeds limit (max: %d)", + cfg.MapScaleFactor, MaxArgMapScaleFactor, + ) + } + + if cfg.BPFVerifierLogLevel > 2 { + return fmt.Errorf("invalid eBPF verifier log level: %d", cfg.BPFVerifierLogLevel) + } + + if cfg.ProbabilisticInterval < 1*time.Minute || cfg.ProbabilisticInterval > 5*time.Minute { + return errors.New( + "invalid argument for probabilistic-interval: use " + + "a duration between 1 and 5 minutes", + ) + } + + if cfg.ProbabilisticThreshold < 1 || + cfg.ProbabilisticThreshold > tracer.ProbabilisticThresholdMax { + return fmt.Errorf( + "invalid argument for probabilistic-threshold. Value "+ + "should be between 1 and %d", + tracer.ProbabilisticThresholdMax, + ) + } + + if cfg.OffCPUThreshold < 0.0 || cfg.OffCPUThreshold > 1.0 { + return errors.New( + "invalid argument for off-cpu-threshold. The value " + + "should be in the range [0..1]. 0 disables off-cpu profiling") + } + + if !cfg.NoKernelVersionCheck { + major, minor, patch, err := tracer.GetCurrentKernelVersion() + if err != nil { + return fmt.Errorf("failed to get kernel version: %v", err) + } + + var minMajor, minMinor uint32 + switch runtime.GOARCH { + case "amd64": + minMajor, minMinor = 5, 2 + case "arm64": + // Older ARM64 kernel versions have broken bpf_probe_read. + // https://github.com/torvalds/linux/commit/6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47 + minMajor, minMinor = 5, 5 + default: + return fmt.Errorf("unsupported architecture: %s", runtime.GOARCH) + } + + if major < minMajor || (major == minMajor && minor < minMinor) { + return fmt.Errorf("host Agent requires kernel version "+ + "%d.%d or newer but got %d.%d.%d", minMajor, minMinor, major, minor, patch) + } + } + + return nil +} diff --git a/collector/config/config_test.go b/collector/config/config_test.go new file mode 100644 index 000000000..63b27b237 --- /dev/null +++ b/collector/config/config_test.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package config // import "go.opentelemetry.io/ebpf-profiler/collector/config" + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/confmap/xconfmap" +) + +func TestValidate(t *testing.T) { + cfg := &Config{ + SamplesPerSecond: 0, + } + err := xconfmap.Validate(cfg) + require.Error(t, err) + require.Equal(t, "invalid sampling frequency: 0", err.Error()) +} diff --git a/go.mod b/go.mod index ca81d3317..c2a7f3ba4 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/zeebo/xxh3 v1.0.2 go.opentelemetry.io/collector/component v1.45.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.138.0 go.opentelemetry.io/collector/consumer/consumertest v0.139.0 go.opentelemetry.io/collector/consumer/xconsumer v0.139.0 go.opentelemetry.io/collector/pdata v1.45.0 @@ -62,19 +63,27 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/jsimonetti/rtnetlink/v2 v2.0.3 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/knadh/koanf/maps v0.1.2 // indirect + github.com/knadh/koanf/providers/confmap v1.0.0 // indirect + github.com/knadh/koanf/v2 v2.3.0 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/collector/component/componenttest v0.139.0 // indirect + go.opentelemetry.io/collector/confmap v1.44.0 // indirect go.opentelemetry.io/collector/consumer v1.45.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.139.0 // indirect go.opentelemetry.io/collector/featuregate v1.45.0 // indirect @@ -84,6 +93,7 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.46.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect diff --git a/go.sum b/go.sum index d97be4002..2a7bbd145 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,10 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -75,6 +79,12 @@ github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3J github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= +github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE= +github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A= +github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= +github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -90,6 +100,10 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -116,6 +130,10 @@ go.opentelemetry.io/collector/component v1.45.0 h1:gGFfVdbQ+1YuyUkJjWo85I7euu3H/ go.opentelemetry.io/collector/component v1.45.0/go.mod h1:xoNFnRKE8Iv6gmlqAKgjayWraRnDcYLLgrPt9VgyO2g= go.opentelemetry.io/collector/component/componenttest v0.139.0 h1:x9Yu2eYhrHxdZ7sFXWtAWVjQ3UIraje557LgNurDC2I= go.opentelemetry.io/collector/component/componenttest v0.139.0/go.mod h1:S9cj+qkf9FgHMzjvlYsLwQKd9BiS7B7oLZvxvlENM/c= +go.opentelemetry.io/collector/confmap v1.44.0 h1:CIK4jAk6H3KTKza4nvWQkqLqrudLkYGz3evu5163uxg= +go.opentelemetry.io/collector/confmap v1.44.0/go.mod h1:w37Xiu/PK3nTdqKb7YEvQECHYkuW7QnmdS7b9iRjOGo= +go.opentelemetry.io/collector/confmap/xconfmap v0.138.0 h1:0b/h3LXBAcHFKPE9eVjZ4KRTaj9ImdOBK2z9hBlmoyA= +go.opentelemetry.io/collector/confmap/xconfmap v0.138.0/go.mod h1:rk8hjMqoHX2KYUjGUPaiWo3qapj4o8UpQWWsdEqvorg= go.opentelemetry.io/collector/consumer v1.45.0 h1:TtqXxgW+1GSCwdoohq0fzqnfqrZBKbfo++1XRj8mrEA= go.opentelemetry.io/collector/consumer v1.45.0/go.mod h1:pJzqTWBubwLt8mVou+G4/Hs23b3m425rVmld3LqOYpY= go.opentelemetry.io/collector/consumer/consumererror v0.139.0 h1:vp4MQ6pKpnS242hE+tuvp0e2OEKhY1Enb0Dpk0fYLkY= @@ -164,6 +182,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/internal/controller/config.go b/internal/controller/config.go index 4efedec33..2d13e6521 100644 --- a/internal/controller/config.go +++ b/internal/controller/config.go @@ -1,18 +1,14 @@ package controller // import "go.opentelemetry.io/ebpf-profiler/internal/controller" import ( - "errors" "flag" "fmt" - "runtime" - "time" "go.opentelemetry.io/ebpf-profiler/internal/log" "go.opentelemetry.io/collector/consumer/xconsumer" "go.opentelemetry.io/ebpf-profiler/collector/config" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tracer" ) type Config struct { @@ -34,11 +30,6 @@ type Config struct { Fs *flag.FlagSet } -const ( - // 1TB of executable address space - MaxArgMapScaleFactor = 8 -) - // Dump visits all flag sets, and dumps them all to debug // Used for verbose mode logging. func (cfg *Config) Dump() { @@ -51,70 +42,5 @@ func (cfg *Config) Dump() { // Validate runs validations on the provided configuration, and returns errors // if invalid values were provided. func (cfg *Config) Validate() error { - if cfg.SamplesPerSecond < 1 { - return fmt.Errorf("invalid sampling frequency: %d", cfg.SamplesPerSecond) - } - - if cfg.MapScaleFactor > MaxArgMapScaleFactor { - return fmt.Errorf( - "eBPF map scaling factor %d exceeds limit (max: %d)", - cfg.MapScaleFactor, MaxArgMapScaleFactor, - ) - } - - if cfg.BPFVerifierLogLevel > 2 { - return fmt.Errorf("invalid eBPF verifier log level: %d", cfg.BPFVerifierLogLevel) - } - - if cfg.ProbabilisticInterval < 1*time.Minute || cfg.ProbabilisticInterval > 5*time.Minute { - return errors.New( - "invalid argument for probabilistic-interval: use " + - "a duration between 1 and 5 minutes", - ) - } - - if cfg.ProbabilisticThreshold < 1 || - cfg.ProbabilisticThreshold > tracer.ProbabilisticThresholdMax { - return fmt.Errorf( - "invalid argument for probabilistic-threshold. Value "+ - "should be between 1 and %d", - tracer.ProbabilisticThresholdMax, - ) - } - - if cfg.OffCPUThreshold < 0.0 || cfg.OffCPUThreshold > 1.0 { - return errors.New( - "invalid argument for off-cpu-threshold. The value " + - "should be in the range [0..1]. 0 disables off-cpu profiling") - } - - if !cfg.NoKernelVersionCheck { - major, minor, patch, err := tracer.GetCurrentKernelVersion() - if err != nil { - return fmt.Errorf("failed to get kernel version: %v", err) - } - - var minMajor, minMinor uint32 - switch runtime.GOARCH { - case "amd64": - if cfg.VerboseMode { - minMajor, minMinor = 5, 2 - } else { - minMajor, minMinor = 4, 19 - } - case "arm64": - // Older ARM64 kernel versions have broken bpf_probe_read. - // https://github.com/torvalds/linux/commit/6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47 - minMajor, minMinor = 5, 5 - default: - return fmt.Errorf("unsupported architecture: %s", runtime.GOARCH) - } - - if major < minMajor || (major == minMajor && minor < minMinor) { - return fmt.Errorf("host Agent requires kernel version "+ - "%d.%d or newer but got %d.%d.%d", minMajor, minMinor, major, minor, patch) - } - } - - return nil + return cfg.Config.Validate() } From 6c2b7292ab3729bad88624a0d0634e1566e84280 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:12:32 +0200 Subject: [PATCH 3/6] fix(deps): update go dependencies (#947) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index c2a7f3ba4..b24df0bf0 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ go 1.24.0 require ( github.com/aws/aws-sdk-go-v2 v1.39.6 - github.com/aws/aws-sdk-go-v2/config v1.31.17 + github.com/aws/aws-sdk-go-v2/config v1.31.18 github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 github.com/cilium/ebpf v0.20.0 github.com/elastic/go-freelru v0.16.0 @@ -25,7 +25,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/zeebo/xxh3 v1.0.2 go.opentelemetry.io/collector/component v1.45.0 - go.opentelemetry.io/collector/confmap/xconfmap v0.138.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.139.0 go.opentelemetry.io/collector/consumer/consumertest v0.139.0 go.opentelemetry.io/collector/consumer/xconsumer v0.139.0 go.opentelemetry.io/collector/pdata v1.45.0 @@ -36,17 +36,17 @@ require ( go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel/metric v1.38.0 go.uber.org/zap/exp v0.3.0 - golang.org/x/arch v0.22.0 + golang.org/x/arch v0.23.0 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 golang.org/x/mod v0.29.0 - golang.org/x/sync v0.17.0 - golang.org/x/sys v0.37.0 + golang.org/x/sync v0.18.0 + golang.org/x/sys v0.38.0 google.golang.org/grpc v1.76.0 ) require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.22 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect @@ -58,7 +58,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.40.0 // indirect github.com/aws/smithy-go v1.23.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -83,7 +83,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/collector/component/componenttest v0.139.0 // indirect - go.opentelemetry.io/collector/confmap v1.44.0 // indirect + go.opentelemetry.io/collector/confmap v1.45.0 // indirect go.opentelemetry.io/collector/consumer v1.45.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.139.0 // indirect go.opentelemetry.io/collector/featuregate v1.45.0 // indirect diff --git a/go.sum b/go.sum index 2a7bbd145..b78e40c17 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,10 @@ github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+X github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= -github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y= -github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA= +github.com/aws/aws-sdk-go-v2/config v1.31.18 h1:RouG3AcF2fLFhw+Z0qbnuIl9HZ0Kh4E/U9sKwTMRpMI= +github.com/aws/aws-sdk-go-v2/config v1.31.18/go.mod h1:aXZ13mSQC8S2VEHwGfL1COMuJ1Zty6pX5xU7hyqjvCg= +github.com/aws/aws-sdk-go-v2/credentials v1.18.22 h1:hyIVGBHhQPaNP9D4BaVRwpjLMCwMMdAkHqB3gGMiykU= +github.com/aws/aws-sdk-go-v2/credentials v1.18.22/go.mod h1:B9E2qHs3/YGfeQZ4jrIE/nPvqxtyafZrJ5EQiZBG6pk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= @@ -30,8 +30,8 @@ github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= +github.com/aws/aws-sdk-go-v2/service/sts v1.40.0 h1:ZGDJVmlpPFiNFCb/I42nYVKUanJAdFUiSmUo/32AqPQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.40.0/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/cilium/ebpf v0.20.0 h1:atwWj9d3NffHyPZzVlx3hmw1on5CLe9eljR8VuHTwhM= @@ -130,10 +130,10 @@ go.opentelemetry.io/collector/component v1.45.0 h1:gGFfVdbQ+1YuyUkJjWo85I7euu3H/ go.opentelemetry.io/collector/component v1.45.0/go.mod h1:xoNFnRKE8Iv6gmlqAKgjayWraRnDcYLLgrPt9VgyO2g= go.opentelemetry.io/collector/component/componenttest v0.139.0 h1:x9Yu2eYhrHxdZ7sFXWtAWVjQ3UIraje557LgNurDC2I= go.opentelemetry.io/collector/component/componenttest v0.139.0/go.mod h1:S9cj+qkf9FgHMzjvlYsLwQKd9BiS7B7oLZvxvlENM/c= -go.opentelemetry.io/collector/confmap v1.44.0 h1:CIK4jAk6H3KTKza4nvWQkqLqrudLkYGz3evu5163uxg= -go.opentelemetry.io/collector/confmap v1.44.0/go.mod h1:w37Xiu/PK3nTdqKb7YEvQECHYkuW7QnmdS7b9iRjOGo= -go.opentelemetry.io/collector/confmap/xconfmap v0.138.0 h1:0b/h3LXBAcHFKPE9eVjZ4KRTaj9ImdOBK2z9hBlmoyA= -go.opentelemetry.io/collector/confmap/xconfmap v0.138.0/go.mod h1:rk8hjMqoHX2KYUjGUPaiWo3qapj4o8UpQWWsdEqvorg= +go.opentelemetry.io/collector/confmap v1.45.0 h1:7M7TTlpzX4r+mIzP/ARdxZBAvI4N+1V96phDane+akU= +go.opentelemetry.io/collector/confmap v1.45.0/go.mod h1:AE1dnkjv0T9gptsh5+mTX0XFGdXx0n7JS4b7CcPfJ6Q= +go.opentelemetry.io/collector/confmap/xconfmap v0.139.0 h1:uQGpFuWnTCXqdMbI3gDSvkwU66/kF/aoC0kVMrit1EM= +go.opentelemetry.io/collector/confmap/xconfmap v0.139.0/go.mod h1:d0ucaeNq2rojFRSQsCHF/gkT3cgBx5H2bVkPQMj57ck= go.opentelemetry.io/collector/consumer v1.45.0 h1:TtqXxgW+1GSCwdoohq0fzqnfqrZBKbfo++1XRj8mrEA= go.opentelemetry.io/collector/consumer v1.45.0/go.mod h1:pJzqTWBubwLt8mVou+G4/Hs23b3m425rVmld3LqOYpY= go.opentelemetry.io/collector/consumer/consumererror v0.139.0 h1:vp4MQ6pKpnS242hE+tuvp0e2OEKhY1Enb0Dpk0fYLkY= @@ -184,8 +184,8 @@ go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= -golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= @@ -197,8 +197,8 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -206,8 +206,8 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= From 40664e605831a3cc6a88a341f0b87172d30a694f Mon Sep 17 00:00:00 2001 From: Roger Coll Date: Tue, 11 Nov 2025 08:13:26 +0100 Subject: [PATCH 4/6] fix: do not incorrectly symbolize addresses beyong gopclntab end (#950) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timo Teräs --- nativeunwind/elfunwindinfo/elfgopclntab.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nativeunwind/elfunwindinfo/elfgopclntab.go b/nativeunwind/elfunwindinfo/elfgopclntab.go index dcc4199c5..9faa21a84 100644 --- a/nativeunwind/elfunwindinfo/elfgopclntab.go +++ b/nativeunwind/elfunwindinfo/elfgopclntab.go @@ -541,11 +541,17 @@ func (g *Gopclntab) mapPcval(offs int32, startPc, pc uint) (int32, bool) { // Symbolize returns the file, line and function information for given PC func (g *Gopclntab) Symbolize(pc uintptr) (sourceFile string, line uint, funcName string) { - index := sort.Search(g.numFuncs, func(i int) bool { + // Binary search for the matching go function maps entry. The search + // lambda makes 'sort.Search' return the first entry that is larger + // than the pc. Thus -1 is needed to get index for the first entry + // which is equal or less than pc. The gopclntab has an extra entry in + // the end to indicate the end of Go code, use that to determine + // if the pc is higher than any Go function address. + index := sort.Search(g.numFuncs+1, func(i int) bool { funcPc, _ := g.getFuncMapEntry(i) return funcPc > pc }) - 1 - if index < 0 { + if index >= g.numFuncs || index < 0 { return "", 0, "" } From 82f72fe9a2ddd0deba7be8bdf383d3d031381c70 Mon Sep 17 00:00:00 2001 From: Dale Hamel Date: Tue, 11 Nov 2025 06:25:15 -0500 Subject: [PATCH 5/6] Rename tpbase package to libc, update hooks and calls accordingly (#952) --- interpreter/instancestubs.go | 4 +-- interpreter/multi.go | 8 ++--- interpreter/perl/instance.go | 13 ++++---- interpreter/python/python.go | 13 ++++---- interpreter/types.go | 6 ++-- {tpbase => libc}/assembly_decode_aarch64.go | 2 +- {tpbase => libc}/assembly_decode_test.go | 2 +- {tpbase => libc}/assembly_decode_x86.go | 2 +- {tpbase => libc}/libc.go | 21 +++++++++++-- {tpbase => libc}/libc_aarch64.go | 2 +- {tpbase => libc}/libc_test.go | 2 +- {tpbase => libc}/libc_x86.go | 3 +- {tpbase => libc}/tpbase.go | 4 +-- processmanager/execinfomanager/manager.go | 16 +++++----- processmanager/processinfo.go | 34 ++++++++++----------- processmanager/types.go | 4 +-- tracer/tpbase.go | 4 +-- 17 files changed, 78 insertions(+), 62 deletions(-) rename {tpbase => libc}/assembly_decode_aarch64.go (97%) rename {tpbase => libc}/assembly_decode_test.go (99%) rename {tpbase => libc}/assembly_decode_x86.go (97%) rename {tpbase => libc}/libc.go (88%) rename {tpbase => libc}/libc_aarch64.go (98%) rename {tpbase => libc}/libc_test.go (99%) rename {tpbase => libc}/libc_x86.go (96%) rename {tpbase => libc}/tpbase.go (88%) diff --git a/interpreter/instancestubs.go b/interpreter/instancestubs.go index 6b503280e..75af5fb35 100644 --- a/interpreter/instancestubs.go +++ b/interpreter/instancestubs.go @@ -5,11 +5,11 @@ package interpreter // import "go.opentelemetry.io/ebpf-profiler/interpreter" import ( "go.opentelemetry.io/ebpf-profiler/host" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tpbase" ) // InstanceStubs provides empty implementations of Instance hooks that are @@ -22,7 +22,7 @@ func (is *InstanceStubs) SynchronizeMappings(EbpfHandler, reporter.ExecutableRep return nil } -func (is *InstanceStubs) UpdateTSDInfo(EbpfHandler, libpf.PID, tpbase.TSDInfo) error { +func (is *InstanceStubs) UpdateLibcInfo(EbpfHandler, libpf.PID, libc.LibcInfo) error { return nil } diff --git a/interpreter/multi.go b/interpreter/multi.go index 34f426758..b6372fc1e 100644 --- a/interpreter/multi.go +++ b/interpreter/multi.go @@ -8,12 +8,12 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/internal/log" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tpbase" ) // MultiData implements the Data interface for multiple interpreters. @@ -104,11 +104,11 @@ func (m *MultiInstance) SynchronizeMappings(ebpf EbpfHandler, return errors.Join(errs...) } -// UpdateTSDInfo updates TSD info for all interpreter instances. -func (m *MultiInstance) UpdateTSDInfo(ebpf EbpfHandler, pid libpf.PID, info tpbase.TSDInfo) error { +// UpdateLibcInfo updates libc info for all interpreter instances. +func (m *MultiInstance) UpdateLibcInfo(ebpf EbpfHandler, pid libpf.PID, info libc.LibcInfo) error { var errs []error for _, instance := range m.instances { - if err := instance.UpdateTSDInfo(ebpf, pid, info); err != nil { + if err := instance.UpdateLibcInfo(ebpf, pid, info); err != nil { errs = append(errs, err) } } diff --git a/interpreter/perl/instance.go b/interpreter/perl/instance.go index 026d7dbfa..d3fda9fa0 100644 --- a/interpreter/perl/instance.go +++ b/interpreter/perl/instance.go @@ -15,6 +15,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfunsafe" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -22,7 +23,6 @@ import ( "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/successfailurecounter" "go.opentelemetry.io/ebpf-profiler/support" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -73,9 +73,8 @@ func hashCOPKey(k copKey) uint32 { return uint32(h ^ xxh3.HashString128(k.funcName.String()).Lo) } -func (i *perlInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, - tsdInfo tpbase.TSDInfo, -) error { +func (i *perlInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, + libcInfo libc.LibcInfo) error { d := i.d stateInTSD := uint8(0) if d.stateInTSD { @@ -88,9 +87,9 @@ func (i *perlInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.PID StateInTSD: stateInTSD, TsdInfo: support.TSDInfo{ - Offset: tsdInfo.Offset, - Multiplier: tsdInfo.Multiplier, - Indirect: tsdInfo.Indirect, + Offset: libcInfo.TSDInfo.Offset, + Multiplier: libcInfo.TSDInfo.Multiplier, + Indirect: libcInfo.TSDInfo.Indirect, }, Interpreter_curcop: uint16(vms.interpreter.curcop), diff --git a/interpreter/python/python.go b/interpreter/python/python.go index a440de2c4..23ca245c1 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -26,6 +26,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -33,7 +34,6 @@ import ( "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/successfailurecounter" "go.opentelemetry.io/ebpf-profiler/support" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -371,9 +371,8 @@ func (p *pythonInstance) GetAndResetMetrics() ([]metrics.Metric, error) { }, nil } -func (p *pythonInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, - tsdInfo tpbase.TSDInfo, -) error { +func (p *pythonInstance) UpdateLibcInfo(ebpf interpreter.EbpfHandler, pid libpf.PID, + libcInfo libc.LibcInfo) error { d := p.d vm := &d.vmStructs cdata := support.PyProcInfo{ @@ -381,9 +380,9 @@ func (p *pythonInstance) UpdateTSDInfo(ebpf interpreter.EbpfHandler, pid libpf.P Version: d.version, TsdInfo: support.TSDInfo{ - Offset: tsdInfo.Offset, - Multiplier: tsdInfo.Multiplier, - Indirect: tsdInfo.Indirect, + Offset: libcInfo.TSDInfo.Offset, + Multiplier: libcInfo.TSDInfo.Multiplier, + Indirect: libcInfo.TSDInfo.Indirect, }, PyThreadState_frame: uint8(vm.PyThreadState.Frame), diff --git a/interpreter/types.go b/interpreter/types.go index a619c865b..2577fa1f0 100644 --- a/interpreter/types.go +++ b/interpreter/types.go @@ -8,13 +8,13 @@ import ( "unsafe" "go.opentelemetry.io/ebpf-profiler/host" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/lpm" "go.opentelemetry.io/ebpf-profiler/metrics" "go.opentelemetry.io/ebpf-profiler/process" "go.opentelemetry.io/ebpf-profiler/remotememory" "go.opentelemetry.io/ebpf-profiler/reporter" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -143,9 +143,9 @@ type Instance interface { SynchronizeMappings(ebpf EbpfHandler, exeReporter reporter.ExecutableReporter, pr process.Process, mappings []process.Mapping) error - // UpdateTSDInfo is called when the process C-library Thread Specific Data related + // UpdateLibcInfo is called when the process C-library related // introspection data has been updated. - UpdateTSDInfo(ebpf EbpfHandler, pid libpf.PID, info tpbase.TSDInfo) error + UpdateLibcInfo(ebpf EbpfHandler, pid libpf.PID, info libc.LibcInfo) error // Symbolize converts one ebpf frame to one or more (if inlining was expanded) libpf.Frame. // The resulting libpf.Frame values are appended to frames. diff --git a/tpbase/assembly_decode_aarch64.go b/libc/assembly_decode_aarch64.go similarity index 97% rename from tpbase/assembly_decode_aarch64.go rename to libc/assembly_decode_aarch64.go index 31de50e97..50bb706a6 100644 --- a/tpbase/assembly_decode_aarch64.go +++ b/libc/assembly_decode_aarch64.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "errors" diff --git a/tpbase/assembly_decode_test.go b/libc/assembly_decode_test.go similarity index 99% rename from tpbase/assembly_decode_test.go rename to libc/assembly_decode_test.go index 426bbada4..de62cf847 100644 --- a/tpbase/assembly_decode_test.go +++ b/libc/assembly_decode_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase +package libc import ( "debug/elf" diff --git a/tpbase/assembly_decode_x86.go b/libc/assembly_decode_x86.go similarity index 97% rename from tpbase/assembly_decode_x86.go rename to libc/assembly_decode_x86.go index 6742aa504..d2cca06fc 100644 --- a/tpbase/assembly_decode_x86.go +++ b/libc/assembly_decode_x86.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "bytes" diff --git a/tpbase/libc.go b/libc/libc.go similarity index 88% rename from tpbase/libc.go rename to libc/libc.go index 7e9b2a7a1..d979eb0bb 100644 --- a/tpbase/libc.go +++ b/libc/libc.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "debug/elf" @@ -11,6 +11,12 @@ import ( "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" ) +// LibcInfo contains introspection information extracted from the C-library +type LibcInfo struct { + // TSDInfo is the TSDInfo extracted for this C-library + TSDInfo TSDInfo +} + // TSDInfo contains information to access C-library's Thread Specific Data from eBPF type TSDInfo struct { // Offset is the pointer difference from "tpbase" pointer to the C-library @@ -84,8 +90,19 @@ func IsPotentialTSDDSO(filename string) bool { return libcRegex.MatchString(filename) } +func ExtractLibcInfo(ef *pfelf.File) (*LibcInfo, error) { + tsdinfo, err := extractTSDInfo(ef) + if err != nil { + return nil, err + } + + return &LibcInfo{ + TSDInfo: *tsdinfo, + }, nil +} + // ExtractTSDInfo extracts the introspection data for pthread thread specific data. -func ExtractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { +func extractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { _, code, err := ef.SymbolData("__pthread_getspecific", 2048) if err != nil { _, code, err = ef.SymbolData("pthread_getspecific", 2048) diff --git a/tpbase/libc_aarch64.go b/libc/libc_aarch64.go similarity index 98% rename from tpbase/libc_aarch64.go rename to libc/libc_aarch64.go index 585879126..fc5ceb76f 100644 --- a/tpbase/libc_aarch64.go +++ b/libc/libc_aarch64.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "errors" diff --git a/tpbase/libc_test.go b/libc/libc_test.go similarity index 99% rename from tpbase/libc_test.go rename to libc/libc_test.go index 8f5f48be7..bc7f108d5 100644 --- a/tpbase/libc_test.go +++ b/libc/libc_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "debug/elf" diff --git a/tpbase/libc_x86.go b/libc/libc_x86.go similarity index 96% rename from tpbase/libc_x86.go rename to libc/libc_x86.go index 8d1c839f8..161deb8a9 100644 --- a/tpbase/libc_x86.go +++ b/libc/libc_x86.go @@ -1,7 +1,8 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" + import ( "errors" diff --git a/tpbase/tpbase.go b/libc/tpbase.go similarity index 88% rename from tpbase/tpbase.go rename to libc/tpbase.go index 11aa1c035..8994c7dbe 100644 --- a/tpbase/tpbase.go +++ b/libc/tpbase.go @@ -7,14 +7,14 @@ // relative to the 'struct task_struct'. This is needed to support Thread Local // Storage access in eBPF. -package tpbase // import "go.opentelemetry.io/ebpf-profiler/tpbase" +package libc // import "go.opentelemetry.io/ebpf-profiler/libc" import ( "fmt" "runtime" ) -func GetAnalyzers() ([]Analyzer, error) { +func GetTpBaseAnalyzers() ([]Analyzer, error) { switch runtime.GOARCH { case "amd64": return getAnalyzersX86(), nil diff --git a/processmanager/execinfomanager/manager.go b/processmanager/execinfomanager/manager.go index e3952c238..2699c3878 100644 --- a/processmanager/execinfomanager/manager.go +++ b/processmanager/execinfomanager/manager.go @@ -24,6 +24,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/interpreter/php" "go.opentelemetry.io/ebpf-profiler/interpreter/python" "go.opentelemetry.io/ebpf-profiler/interpreter/ruby" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/libpf/xsync" @@ -32,7 +33,6 @@ import ( sdtypes "go.opentelemetry.io/ebpf-profiler/nativeunwind/stackdeltatypes" pmebpf "go.opentelemetry.io/ebpf-profiler/processmanager/ebpfapi" "go.opentelemetry.io/ebpf-profiler/support" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/tracer/types" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -61,8 +61,8 @@ type ExecutableInfo struct { // instance belongs to was previously identified as an interpreter. Otherwise, // this field is nil. Data interpreter.Data - // TSDInfo stores TSD information if the executable is libc, otherwise nil. - TSDInfo *tpbase.TSDInfo + // LibcInfo stores libc information if the executable is libc, otherwise nil. + LibcInfo *libc.LibcInfo } // ExecutableInfoManager manages all per-executable (FileID) information that we require to @@ -164,7 +164,7 @@ func (mgr *ExecutableInfoManager) AddOrIncRef(fileID host.FileID, } var ( intervalData sdtypes.IntervalData - tsdInfo *tpbase.TSDInfo + libcInfo *libc.LibcInfo ref mapRef gaps []util.Range err error @@ -198,9 +198,9 @@ func (mgr *ExecutableInfoManager) AddOrIncRef(fileID host.FileID, } // Also gather TSD info if applicable. - if tpbase.IsPotentialTSDDSO(elfRef.FileName()) { + if libc.IsPotentialTSDDSO(elfRef.FileName()) { if ef, errx := elfRef.GetELF(); errx == nil { - tsdInfo, _ = tpbase.ExtractTSDInfo(ef) + libcInfo, _ = libc.ExtractLibcInfo(ef) } } @@ -226,8 +226,8 @@ func (mgr *ExecutableInfoManager) AddOrIncRef(fileID host.FileID, // Insert a corresponding record into our map. info = &entry{ ExecutableInfo: ExecutableInfo{ - Data: state.detectAndLoadInterpData(loaderInfo), - TSDInfo: tsdInfo, + Data: state.detectAndLoadInterpData(loaderInfo), + LibcInfo: libcInfo, }, mapRef: ref, rc: 1, diff --git a/processmanager/processinfo.go b/processmanager/processinfo.go index 836c4c9f5..792ca3ce0 100644 --- a/processmanager/processinfo.go +++ b/processmanager/processinfo.go @@ -24,6 +24,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/lpm" @@ -31,7 +32,6 @@ import ( eim "go.opentelemetry.io/ebpf-profiler/processmanager/execinfomanager" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -69,38 +69,38 @@ func isPIDLive(pid libpf.PID) (bool, error) { return true, err } -// assignTSDInfo updates the TSDInfo for the Interpreters on given PID. +// assignLibcInfo updates the LibcInfo for the Interpreters on given PID. // Caller must hold pm.mu write lock. -func (pm *ProcessManager) assignTSDInfo(pid libpf.PID, tsdInfo *tpbase.TSDInfo) { - if tsdInfo == nil { +func (pm *ProcessManager) assignLibcInfo(pid libpf.PID, libcInfo *libc.LibcInfo) { + if libcInfo == nil { return } info, ok := pm.pidToProcessInfo[pid] if !ok { - // This is guaranteed not to happen since assignTSDInfo is always called after + // This is guaranteed not to happen since assignLibcInfo is always called after // pm.updatePidInformation - but to avoid a possible panic we just return here. return - } else if info.tsdInfo != nil { + } else if info.libcInfo != nil { return } - info.tsdInfo = tsdInfo + info.libcInfo = libcInfo // Update the tsdInfo to interpreters that are already attached for _, instance := range pm.interpreters[pid] { - if err := instance.UpdateTSDInfo(pm.ebpf, pid, *tsdInfo); err != nil { - log.Errorf("Failed to update PID %v TSDInfo: %v", + if err := instance.UpdateLibcInfo(pm.ebpf, pid, *libcInfo); err != nil { + log.Errorf("Failed to update PID %v LibcInfo: %v", pid, err) } } } -// getTSDInfo retrieves the TSDInfo of given PID +// getLibcInfo retrieves the LibcInfo of given PID // Caller must hold pm.mu read lock. -func (pm *ProcessManager) getTSDInfo(pid libpf.PID) *tpbase.TSDInfo { +func (pm *ProcessManager) getLibcInfo(pid libpf.PID) *libc.LibcInfo { if info, ok := pm.pidToProcessInfo[pid]; ok { - return info.tsdInfo + return info.libcInfo } return nil } @@ -121,7 +121,7 @@ func (pm *ProcessManager) updatePidInformation(pr process.Process, m *Mapping) ( meta: pr.GetProcessMeta(process.MetaConfig{IncludeEnvVars: pm.includeEnvVars}), mappings: make(map[libpf.Address]*Mapping), mappingsByFileID: make(map[host.FileID]map[libpf.Address]*Mapping), - tsdInfo: nil, + libcInfo: nil, } pm.pidToProcessInfo[pid] = info @@ -238,10 +238,10 @@ func (pm *ProcessManager) handleNewInterpreter(pr process.Process, m *Mapping, log.Debugf("Attached to %v interpreter in PID %v", ei.Data, pid) pm.assignInterpreter(pid, key, instance) - if tsdInfo := pm.getTSDInfo(pid); tsdInfo != nil { - err = instance.UpdateTSDInfo(pm.ebpf, pid, *tsdInfo) + if libcInfo := pm.getLibcInfo(pid); libcInfo != nil { + err = instance.UpdateLibcInfo(pm.ebpf, pid, *libcInfo) if err != nil { - log.Errorf("Failed to update PID %v TSDInfo: %v", pid, err) + log.Errorf("Failed to update PID %v LibcInfo: %v", pid, err) } } @@ -270,7 +270,7 @@ func (pm *ProcessManager) handleNewMapping(pr process.Process, m *Mapping, return err } - pm.assignTSDInfo(pr.PID(), ei.TSDInfo) + pm.assignLibcInfo(pr.PID(), ei.LibcInfo) if ei.Data != nil { return pm.handleNewInterpreter(pr, m, &ei) diff --git a/processmanager/types.go b/processmanager/types.go index d445d02da..4a739e127 100644 --- a/processmanager/types.go +++ b/processmanager/types.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -19,7 +20,6 @@ import ( eim "go.opentelemetry.io/ebpf-profiler/processmanager/execinfomanager" "go.opentelemetry.io/ebpf-profiler/reporter" "go.opentelemetry.io/ebpf-profiler/times" - "go.opentelemetry.io/ebpf-profiler/tpbase" "go.opentelemetry.io/ebpf-profiler/util" ) @@ -168,7 +168,7 @@ type processInfo struct { // executable mappings keyed by host file ID. mappingsByFileID map[host.FileID]map[libpf.Address]*Mapping // C-library Thread Specific Data information - tsdInfo *tpbase.TSDInfo + libcInfo *libc.LibcInfo } // addMapping adds a mapping to the internal indices. diff --git a/tracer/tpbase.go b/tracer/tpbase.go index 45c652d0d..5c0897981 100644 --- a/tracer/tpbase.go +++ b/tracer/tpbase.go @@ -12,8 +12,8 @@ import ( "go.opentelemetry.io/ebpf-profiler/internal/log" "go.opentelemetry.io/ebpf-profiler/kallsyms" + "go.opentelemetry.io/ebpf-profiler/libc" "go.opentelemetry.io/ebpf-profiler/libpf" - "go.opentelemetry.io/ebpf-profiler/tpbase" ) // This file contains code to extract the offset of the thread pointer base variable in @@ -39,7 +39,7 @@ func loadTPBaseOffset(coll *cebpf.CollectionSpec, maps map[string]*cebpf.Map, kmod *kallsyms.Module, ) (uint64, error) { var tpbaseOffset uint32 - analyzers, err := tpbase.GetAnalyzers() + analyzers, err := libc.GetTpBaseAnalyzers() if err != nil { return 0, err } From 23da4c0efd97105b5b672b2d9ce6079a22dbcf0e Mon Sep 17 00:00:00 2001 From: Dale Hamel Date: Tue, 4 Nov 2025 17:59:58 -0500 Subject: [PATCH 6/6] Add DTV extraction support to libc package The DTV variables read from extracted __tls_get_addr can be used to read TLS variables. If this symbol is missing, empty introspection data is returned. --- libc/libc.go | 50 ++++++++ libc/libc_aarch64.go | 69 ++++++++++ libc/libc_test.go | 290 +++++++++++++++++++++++++++++++++++++++++++ libc/libc_x86.go | 109 ++++++++++++++++ 4 files changed, 518 insertions(+) diff --git a/libc/libc.go b/libc/libc.go index d979eb0bb..1b8655e35 100644 --- a/libc/libc.go +++ b/libc/libc.go @@ -8,6 +8,8 @@ import ( "fmt" "regexp" + "go.opentelemetry.io/ebpf-profiler/internal/log" + "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" ) @@ -15,6 +17,8 @@ import ( type LibcInfo struct { // TSDInfo is the TSDInfo extracted for this C-library TSDInfo TSDInfo + // TODO comment + DTVInfo DTVInfo } // TSDInfo contains information to access C-library's Thread Specific Data from eBPF @@ -35,6 +39,16 @@ type TSDInfo struct { Indirect uint8 } +// TODO comment +type DTVInfo struct { + // Offset is the offset of DTV from FS base (or from thread pointer) + Offset int64 + // EntryWidth is the size of each DTV entry in bytes + EntryWidth uint32 + // Indirect is 0 if DTV is at FS+offset, 1 if at [FS+0]+offset + Indirect uint8 +} + // This code analyzes the C-library provided POSIX defined function which is used // to read thread-specific data (TSD): // void *pthread_getspecific(pthread_key_t key); @@ -96,8 +110,14 @@ func ExtractLibcInfo(ef *pfelf.File) (*LibcInfo, error) { return nil, err } + dtvinfo, err := extractDTVInfo(ef) + if err != nil { + return &LibcInfo{}, err + } + return &LibcInfo{ TSDInfo: *tsdinfo, + DTVInfo: *dtvinfo, }, nil } @@ -128,3 +148,33 @@ func extractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { } return &info, nil } + +// extractDTVInfo extracts the introspection data for the DTV to access TLS vars +func extractDTVInfo(ef *pfelf.File) (*DTVInfo, error) { + var info DTVInfo + _, code, err := ef.SymbolData("__tls_get_addr", 2048) + if err != nil { + // Only error out reading DTV if we have the symbol, but fail to parse it + // if the symbol is not exported, failing to read it is not a critical error + // and empty DTV introspection data is returned + log.Warnf("unable to read '__tls_get_addr': %s, libc DTV introspection data is unavailable", err) + return &info, nil + } + + if len(code) < 8 { + return nil, fmt.Errorf("__tls_get_addr function size is %d", len(code)) + } + + switch ef.Machine { + case elf.EM_AARCH64: + info, err = extractDTVInfoARM(code) + case elf.EM_X86_64: + info, err = extractDTVInfoX86(code) + default: + return &info, fmt.Errorf("unsupported arch %s", ef.Machine.String()) + } + if err != nil { + return &info, fmt.Errorf("failed to extract DTV data: %s", err) + } + return &info, nil +} diff --git a/libc/libc_aarch64.go b/libc/libc_aarch64.go index fc5ceb76f..bca161d94 100644 --- a/libc/libc_aarch64.go +++ b/libc/libc_aarch64.go @@ -268,3 +268,72 @@ func extractTSDInfoARM(code []byte) (TSDInfo, error) { Indirect: indirect, }, nil } + +func extractDTVInfoARM(code []byte) (DTVInfo, error) { + dtvOffset := int64(0) + entryWidth := uint32(0) + foundThreadPtr := false + foundDTVLoad := false + + // Scan entire function + for offs := 0; offs < len(code); offs += 4 { + if offs+4 > len(code) { + break + } + + inst, err := aa.Decode(code[offs:]) + if err != nil { + return DTVInfo{}, err + } + + switch inst.Op { + + case aa.MRS: + foundThreadPtr = true + + case aa.LDUR: + if len(inst.Args) >= 2 { + if m, ok := inst.Args[1].(aa.MemImmediate); ok { + imm, ok := ah.DecodeImmediate(m) + if ok { + dtvOffset = imm + foundDTVLoad = true + } + } + } + + case aa.LDR: + if len(inst.Args) >= 2 { + // Check what type of LDR this is + switch m := inst.Args[1].(type) { + case aa.MemImmediate: + if foundThreadPtr && !foundDTVLoad { + imm, ok := ah.DecodeImmediate(m) + if ok { + dtvOffset = imm + foundDTVLoad = true + } + } + + case aa.MemExtend: + if m.Amount > 0 { + entryWidth = uint32(1 << m.Amount) + } + } + } + + case aa.LSL: + if len(inst.Args) >= 3 { + if imm, ok := inst.Args[2].(aa.Imm); ok { + entryWidth = uint32(1 << imm.Imm) + } + } + } + } + + return DTVInfo{ + Offset: dtvOffset, + EntryWidth: entryWidth, + Indirect: 1, + }, nil +} diff --git a/libc/libc_test.go b/libc/libc_test.go index bc7f108d5..48022940a 100644 --- a/libc/libc_test.go +++ b/libc/libc_test.go @@ -263,3 +263,293 @@ func TestExtractTSDInfo(t *testing.T) { }) } } + +func TestExtractDTVOffset(t *testing.T) { + testCases := map[string]struct { + machine elf.Machine + code []byte + info DTVInfo + }{ + "glibc 2.36 / debian 12 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x8,%rdx + // mov 0x1fc48(%rip),%rax # 0x7ffff7ffe0b8 <_rtld_global+4248> + // cmp %rax,(%rdx) + // jne 0x7ffff7fde48b <__tls_get_addr+43> + // mov (%rdi),%rax + // shl $0x4,%rax + // mov (%rdx,%rax,1),%rax + // cmp $0xffffffffffffffff,%rax + // je 0x7ffff7fde48b <__tls_get_addr+43> + // add 0x8(%rdi),%rax + // ret + // push %rbp + // mov %rsp,%rbp + // and $0xfffffffffffffff0,%rsp + // call 0x7ffff7fdbd40 <__tls_get_addr_slow> + // mov %rbp,%rsp + // pop %rbp + // ret + 0x64, 0x48, 0x8b, 0x14, 0x25, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x05, 0x48, 0xfc, 0x01, 0x00, + 0x48, 0x39, 0x02, + 0x75, 0x16, + 0x48, 0x8b, 0x07, + 0x48, 0xc1, 0xe0, 0x04, + 0x48, 0x8b, 0x04, 0x02, + 0x48, 0x83, 0xf8, 0xff, + 0x74, 0x05, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + 0x55, + 0x48, 0x89, 0xe5, + 0x48, 0x83, 0xe4, 0xf0, + 0xe8, 0xa8, 0xd8, 0xff, 0xff, + 0x48, 0x89, 0xec, + 0x5d, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 16, + Indirect: 0, + }, + }, + "glibc 2.32 / Fedora 33 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // endbr64 + // mov %fs:0x8,%rdx + // mov 0x1394c(%rip),%rax # 0x7f48d90c2ff0 <_rtld_local+4080> + // cmp %rax,(%rdx) + // jne 0x7f48d90af6bf <__tls_get_addr+47> + // mov (%rdi),%rax + // shl $0x4,%rax + // mov (%rdx,%rax,1),%rax + // cmp $0xffffffffffffffff,%rax + // je 0x7f48d90af6bf <__tls_get_addr+47> + // add 0x8(%rdi),%rax + // ret + // push %rbp + // mov %rsp,%rbp + // and $0xfffffffffffffff0,%rsp + // call 0x7f48d90a9ed0 <__tls_get_addr_slow> + // mov %rbp,%rsp + // pop %rbp + // ret + 0xf3, 0x0f, 0x1e, 0xfa, + 0x64, 0x48, 0x8b, 0x14, 0x25, 0x08, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x05, 0x4c, 0x39, 0x01, 0x00, + 0x48, 0x39, 0x02, + 0x75, 0x16, + 0x48, 0x8b, 0x07, + 0x48, 0xc1, 0xe0, 0x04, + 0x48, 0x8b, 0x04, 0x02, + 0x48, 0x83, 0xf8, 0xff, + 0x74, 0x05, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + 0x55, + 0x48, 0x89, 0xe5, + 0x48, 0x83, 0xe4, 0xf0, + 0xe8, 0x04, 0xa8, 0xff, 0xff, + 0x48, 0x89, 0xec, + 0x5d, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 16, + Indirect: 0, + }, + }, + "musl 1.2.5 / alpine 3.22.2 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x0,%rax + // mov (%rdi),%rcx + // mov 0x8(%rax),%rdx + // mov 0x8(%rdi),%rax + // add (%rdx,%rcx,8),%rax + // ret + 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x0f, + 0x48, 0x8b, 0x50, 0x08, + 0x48, 0x8b, 0x47, 0x08, + 0x48, 0x03, 0x04, 0xca, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 8, + Indirect: 1, + }, + }, + "musl 1.1.5 / alpine 3.1 / x86_64": { + machine: elf.EM_X86_64, + code: []byte{ + // mov %fs:0x0,%rax + // mov 0x8(%rax),%rax + // mov (%rdi),%rdx + // cmp %rdx,(%rax) + // jae 0x7f824da49ef3 <__tls_get_addr+26> + // jmpq 0x7f824da191d5 + // mov (%rax,%rdx,8),%rax + // add 0x8(%rdi),%rax + // retq + 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x8b, 0x40, 0x08, + 0x48, 0x8b, 0x17, + 0x48, 0x39, 0x10, + 0x73, 0x05, + 0xe9, 0xe2, 0xf2, 0xfc, 0xff, + 0x48, 0x8b, 0x04, 0xd0, + 0x48, 0x03, 0x47, 0x08, + 0xc3, + }, + info: DTVInfo{ + Offset: 8, + EntryWidth: 8, + Indirect: 1, + }, + }, + "glibc 2.39 / ubuntu 24.04 / aarch64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0xfd, 0x7b, 0xbf, 0xa9, // stp x29, x30, [sp, #-16]! + 0x83, 0x01, 0x00, 0xb0, // adrp x3, 0xfffff7fff000 <_rtld_global+4096> + 0xfd, 0x03, 0x00, 0x91, // mov x29, sp + 0x63, 0x00, 0x05, 0x91, // add x3, x3, #0x140 + 0x21, 0x00, 0x40, 0xf9, // ldr x1, [x1] + 0x64, 0x00, 0x40, 0xf9, // ldr x4, [x3] + 0x25, 0x00, 0x40, 0xf9, // ldr x5, [x1] + 0xbf, 0x00, 0x04, 0xeb, // cmp x5, x4 + 0x41, 0x01, 0x00, 0x54, // b.ne 0xfffff7fce2bc <__GI___tls_get_addr+76> // b.any + 0x03, 0x00, 0x40, 0xf9, // ldr x3, [x0] + 0x63, 0xec, 0x7c, 0xd3, // lsl x3, x3, #4 + 0x23, 0x68, 0x63, 0xf8, // ldr x3, [x1, x3] + 0x7f, 0x04, 0x00, 0xb1, // cmn x3, #0x1 + 0x00, 0x01, 0x00, 0x54, // b.eq 0xfffff7fce2c8 <__GI___tls_get_addr+88> // b.none + 0x00, 0x04, 0x40, 0xf9, // ldr x0, [x0, #8] + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0x60, 0x00, 0x00, 0x8b, // add x0, x3, x0 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0x61, 0xfc, 0xdf, 0xc8, // ldar x1, [x3] + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xd3, 0xff, 0xff, 0x17, // b 0xfffff7fce210 + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0x02, 0x00, 0x80, 0xd2, // mov x2, #0x0 // #0 + 0xa9, 0xfc, 0xff, 0x17, // b 0xfffff7fcd574 + + }, + info: DTVInfo{ + Offset: 0, + EntryWidth: 16, + Indirect: 1, + }, + }, + "glibc / Fedora 39 / aarch64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x5f, 0x24, 0x03, 0xd5, // bti c + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0x3f, 0x23, 0x03, 0xd5, // paciasp + 0xfd, 0x7b, 0xbf, 0xa9, // stp x29, x30, [sp, #-16]! + 0x83, 0x01, 0x00, 0xb0, // adrp x3, 0xeebc52855000 <_rtld_local+4096> + 0x63, 0x40, 0x05, 0x91, // add x3, x3, #0x150 + 0xfd, 0x03, 0x00, 0x91, // mov x29, sp + 0x21, 0x00, 0x40, 0xf9, // ldr x1, [x1] + 0x63, 0x00, 0x40, 0xf9, // ldr x3, [x3] + 0x24, 0x00, 0x40, 0xf9, // ldr x4, [x1] + 0x9f, 0x00, 0x03, 0xeb, // cmp x4, x3 + 0x61, 0x01, 0x00, 0x54, // b.ne 0xeebc52824278 <__GI___tls_get_addr+88> // b.any + 0x03, 0x00, 0x40, 0xf9, // ldr x3, [x0] + 0x63, 0xec, 0x7c, 0xd3, // lsl x3, x3, #4 + 0x23, 0x68, 0x63, 0xf8, // ldr x3, [x1, x3] + 0x7f, 0x04, 0x00, 0xb1, // cmn x3, #0x1 + 0x20, 0x01, 0x00, 0x54, // b.eq 0xeebc52824284 <__GI___tls_get_addr+100> // b.none + 0x00, 0x04, 0x40, 0xf9, // ldr x0, [x0, #8] + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0x60, 0x00, 0x00, 0x8b, // add x0, x3, x0 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0xcc, 0xff, 0xff, 0x17, // b 0xeebc528241b0 + 0xfd, 0x7b, 0xc1, 0xa8, // ldp x29, x30, [sp], #16 + 0xbf, 0x23, 0x03, 0xd5, // autiasp + 0x02, 0x00, 0x80, 0xd2, // mov x2, #0x0 // #0 + 0x70, 0xfc, 0xff, 0x17, // b 0xeebc52823450 + }, + info: DTVInfo{ + Offset: 0, + EntryWidth: 16, + Indirect: 1, + }, + }, + "glibc / Fedora 33 / aarch64": { + machine: elf.EM_AARCH64, + code: []byte{ + 0x5f, 0x24, 0x03, 0xd5, //bti c + 0x41, 0xd0, 0x3b, 0xd5, //mrs x1, tpidr_el0 + 0x62, 0x01, 0x00, 0xf0, //adrp x2, 0xf58e5fcc1000 <_rtld_local+4072> + 0x43, 0x60, 0x40, 0xf9, //ldr x3, [x2, #192] + 0x21, 0x00, 0x40, 0xf9, //ldr x1, [x1] + 0x24, 0x00, 0x40, 0xf9, //ldr x4, [x1] + 0x9f, 0x00, 0x03, 0xeb, //cmp x4, x3 + 0x21, 0x01, 0x00, 0x54, //b.ne 0xf58e5fc921e0 <__GI___tls_get_addr+64> // b.any + 0x03, 0x00, 0x40, 0xf9, //ldr x3, [x0] + 0x63, 0xec, 0x7c, 0xd3, //lsl x3, x3, #4 + 0x23, 0x68, 0x63, 0xf8, //ldr x3, [x1, x3] + 0x7f, 0x04, 0x00, 0xb1, //cmn x3, #0x1 + 0xa0, 0x00, 0x00, 0x54, //b.eq 0xf58e5fc921e4 <__GI___tls_get_addr+68> // b.none + 0x00, 0x04, 0x40, 0xf9, //ldr x0, [x0, #8] + 0x60, 0x00, 0x00, 0x8b, //add x0, x3, x0 + 0xc0, 0x03, 0x5f, 0xd6, //ret + 0xd4, 0xff, 0xff, 0x17, //b 0xf58e5fc92130 + 0x02, 0x00, 0x80, 0xd2, //mov x2, #0x0 // #0 + 0x7e, 0xfc, 0xff, 0x17, //b 0xf58e5fc913e0 + }, + info: DTVInfo{ + Offset: 192, + EntryWidth: 16, + Indirect: 1, + }, + }, + + "musl 1.2.5 / alpine 3.22 / aarch64": { // same as alpine 3.13, oldest on dockerhub + machine: elf.EM_AARCH64, + code: []byte{ + 0x02, 0x00, 0x40, 0xa9, // ldp x2, x0, [x0] + 0x41, 0xd0, 0x3b, 0xd5, // mrs x1, tpidr_el0 + 0x21, 0x80, 0x5f, 0xf8, // ldur x1, [x1, #-8] + 0x21, 0x78, 0x62, 0xf8, // ldr x1, [x1, x2, lsl #3] + 0x20, 0x00, 0x00, 0x8b, // add x0, x1, x0 + 0xc0, 0x03, 0x5f, 0xd6, // ret + }, + info: DTVInfo{ + Offset: -8, + EntryWidth: 8, + Indirect: 1, + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + var info DTVInfo + var err error + switch test.machine { + case elf.EM_X86_64: + info, err = extractDTVInfoX86(test.code) + case elf.EM_AARCH64: + info, err = extractDTVInfoARM(test.code) + } + if assert.NoError(t, err) { + assert.Equal(t, test.info, info) + } + }) + } +} diff --git a/libc/libc_x86.go b/libc/libc_x86.go index 161deb8a9..b71d1a369 100644 --- a/libc/libc_x86.go +++ b/libc/libc_x86.go @@ -81,3 +81,112 @@ func extractTSDInfoX86(code []byte) (TSDInfo, error) { } return TSDInfo{}, errors.New("could not extract tsdInfo amd") } + +// extractDTVInfoX86 analyzes __tls_get_addr to find the DTV offset from FS base +func extractDTVInfoX86(code []byte) (DTVInfo, error) { + it := amd.NewInterpreterWithCode(code) + + // __tls_get_addr takes a tls_index struct in RDI + tls_index := it.Regs.Get(amd.RDI) + module_id := e.Mem8(tls_index) + tls_offset := e.Mem8(e.Add(tls_index, e.Imm(8))) + + // Execute until RET + _, err := it.LoopWithBreak(func(op x86asm.Inst) bool { + return op.Op == x86asm.RET + }) + if err != nil { + return DTVInfo{}, err + } + + result := it.Regs.Get(amd.RAX) + + // Capture variables + var ( + dtvOffset = e.NewImmediateCapture("dtvOffset") + entryWidth = e.NewImmediateCapture("entryWidth") + ) + + // Pattern 1: glibc - Direct DTV access + expected := e.Add( + e.Mem8( + e.Add( + e.MemWithSegment8(x86asm.FS, dtvOffset), + e.Multiply(module_id, entryWidth), + ), + ), + tls_offset, + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int64(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 0, + }, nil + } + + // Pattern 2: musl - The thread pointer itself might be represented differently + // Since FS:0 is the thread pointer, and DTV is at offset from it + thread_ptr := e.MemWithSegment8(x86asm.FS, e.Imm(0)) + dtv_ptr := e.Mem8(e.Add(thread_ptr, dtvOffset)) + + expected = e.Add( + tls_offset, + e.Mem8( + e.Add( + dtv_ptr, + e.Multiply(module_id, entryWidth), + ), + ), + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int64(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + // Pattern 3: Reverse addition order + expected = e.Add( + e.Mem8( + e.Add( + dtv_ptr, + e.Multiply(module_id, entryWidth), + ), + ), + tls_offset, + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int64(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + // Pattern 4: Maybe the scale is encoded in the memory operand differently + // Try without explicit multiply + expected = e.Add( + tls_offset, + e.Mem8( + e.Add( + e.Mem8(e.Add(thread_ptr, dtvOffset)), + e.Multiply(e.ZeroExtend32(module_id), entryWidth), + ), + ), + ) + + if result.Match(expected) { + return DTVInfo{ + Offset: int64(dtvOffset.CapturedValue()), + EntryWidth: uint32(entryWidth.CapturedValue()), + Indirect: 1, + }, nil + } + + return DTVInfo{}, errors.New("could not extract DTV info: no matching pattern found") +}