Skip to content

Commit 89d71c9

Browse files
authored
Merge pull request #154 from parca-dev/rtld-improve
Improvements to AttachUSDTProbe
2 parents 56a4e6e + e12519d commit 89d71c9

File tree

15 files changed

+1233
-206
lines changed

15 files changed

+1233
-206
lines changed

.github/workflows/unit-test-on-pull-request.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,33 @@ jobs:
232232
run: |
233233
uname -a
234234
sudo go test ./interpreter/... -v -run TestIntegration
235+
236+
rtld-qemu-tests:
237+
name: RTLD QEMU tests (kernel ${{ matrix.kernel }})
238+
runs-on: ubuntu-24.04
239+
timeout-minutes: 15
240+
strategy:
241+
matrix:
242+
kernel:
243+
- 5.10.217 # Pre-6.6, uses single-shot mode
244+
- 6.8.10 # Post-6.6, supports multi-uprobe
245+
steps:
246+
- name: Clone code
247+
uses: actions/checkout@v4
248+
- name: Set up Go
249+
uses: actions/setup-go@v5
250+
with:
251+
go-version-file: go.mod
252+
cache-dependency-path: go.sum
253+
- name: Install dependencies
254+
run: |
255+
sudo apt-get update -y
256+
sudo apt-get install -y qemu-system-x86 debootstrap
257+
- name: Download kernel
258+
run: |
259+
cd test/rtld-qemu
260+
./download-kernel.sh ${{ matrix.kernel }}
261+
- name: Run RTLD tests in QEMU
262+
run: |
263+
cd test/rtld-qemu
264+
./build-and-run.sh ${{ matrix.kernel }}

interpreter/instancestubs.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@
44
package interpreter // import "go.opentelemetry.io/ebpf-profiler/interpreter"
55

66
import (
7+
"unsafe"
8+
79
"go.opentelemetry.io/ebpf-profiler/host"
810
"go.opentelemetry.io/ebpf-profiler/libpf"
11+
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
12+
"go.opentelemetry.io/ebpf-profiler/lpm"
913
"go.opentelemetry.io/ebpf-profiler/metrics"
1014
"go.opentelemetry.io/ebpf-profiler/process"
1115
"go.opentelemetry.io/ebpf-profiler/reporter"
1216
"go.opentelemetry.io/ebpf-profiler/tpbase"
17+
"go.opentelemetry.io/ebpf-profiler/util"
1318
)
1419

1520
// InstanceStubs provides empty implementations of Instance hooks that are
@@ -37,3 +42,44 @@ func (is *InstanceStubs) GetAndResetMetrics() ([]metrics.Metric, error) {
3742
func (is *InstanceStubs) Symbolize(*host.Frame, *libpf.Frames) error {
3843
return ErrMismatchInterpreterType
3944
}
45+
46+
type EbpfHandlerStubs struct{}
47+
48+
func (m *EbpfHandlerStubs) UpdatePidInterpreterMapping(_ libpf.PID,
49+
pfx lpm.Prefix, _ uint8, _ host.FileID, _ uint64) error {
50+
return nil
51+
}
52+
53+
func (m *EbpfHandlerStubs) DeletePidInterpreterMapping(_ libpf.PID, _ lpm.Prefix) error {
54+
return nil
55+
}
56+
57+
func (m *EbpfHandlerStubs) CoredumpTest() bool {
58+
return false
59+
}
60+
61+
func (m *EbpfHandlerStubs) UpdateInterpreterOffsets(uint16, host.FileID,
62+
[]util.Range) error {
63+
return nil
64+
}
65+
66+
func (m *EbpfHandlerStubs) UpdateProcData(libpf.InterpreterType, libpf.PID,
67+
unsafe.Pointer) error {
68+
return nil
69+
}
70+
71+
func (m *EbpfHandlerStubs) DeleteProcData(libpf.InterpreterType, libpf.PID) error {
72+
return nil
73+
}
74+
75+
func (mockup *EbpfHandlerStubs) AttachUSDTProbes(libpf.PID, string, string, []pfelf.USDTProbe,
76+
[]uint64, []string, bool) (LinkCloser, error) {
77+
return nil, nil
78+
}
79+
80+
func (mockup *EbpfHandlerStubs) TriggerProcessSync(libpf.PID) error {
81+
return nil
82+
}
83+
84+
func (mockup *EbpfHandlerStubs) SetProcessSyncTrigger(func(pid libpf.PID)) {
85+
}

interpreter/luajit/mappings_test.go

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@ package luajit
1313
import (
1414
"debug/elf"
1515
"testing"
16-
"unsafe"
1716

18-
"github.com/cilium/ebpf/link"
1917
"github.com/stretchr/testify/require"
2018
"go.opentelemetry.io/ebpf-profiler/host"
2119
"go.opentelemetry.io/ebpf-profiler/interpreter"
2220
"go.opentelemetry.io/ebpf-profiler/libpf"
23-
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
2421
"go.opentelemetry.io/ebpf-profiler/lpm"
2522
"go.opentelemetry.io/ebpf-profiler/process"
26-
"go.opentelemetry.io/ebpf-profiler/util"
2723
)
2824

2925
type prefixKey struct {
@@ -33,15 +29,12 @@ type prefixKey struct {
3329

3430
// ebpfMapsMockup implements the ebpf interface as test mockup
3531
type ebpfMapsMockup struct {
32+
interpreter.EbpfHandlerStubs
3633
prefixes map[prefixKey]lpm.Prefix
3734
}
3835

3936
var _ interpreter.EbpfHandler = &ebpfMapsMockup{}
4037

41-
func (m *ebpfMapsMockup) CoredumpTest() bool {
42-
return false
43-
}
44-
4538
func (m *ebpfMapsMockup) UpdatePidInterpreterMapping(pid libpf.PID,
4639
pfx lpm.Prefix, _ uint8, _ host.FileID, _ uint64) error {
4740
m.prefixes[prefixKey{pid: pid, pfx: pfx}] = pfx
@@ -53,25 +46,6 @@ func (m *ebpfMapsMockup) DeletePidInterpreterMapping(pid libpf.PID, pfx lpm.Pref
5346
return nil
5447
}
5548

56-
func (m *ebpfMapsMockup) UpdateInterpreterOffsets(uint16, host.FileID,
57-
[]util.Range) error {
58-
return nil
59-
}
60-
61-
func (m *ebpfMapsMockup) UpdateProcData(libpf.InterpreterType, libpf.PID,
62-
unsafe.Pointer) error {
63-
return nil
64-
}
65-
66-
func (m *ebpfMapsMockup) DeleteProcData(libpf.InterpreterType, libpf.PID) error {
67-
return nil
68-
}
69-
70-
func (mockup *ebpfMapsMockup) AttachUSDTProbe(_ libpf.PID, _ string, _ pfelf.USDTProbe,
71-
_ string) (link.Link, error) {
72-
return nil, nil
73-
}
74-
7549
// TestSynchronizeMappings tests that if a mapping is realloc'd we do the right thing.
7650
func TestSynchronizeMappings(t *testing.T) {
7751
for _, tc := range []struct {

interpreter/rtld/rtld.go

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,44 @@ package rtld // import "go.opentelemetry.io/ebpf-profiler/interpreter/rtld"
55

66
import (
77
"fmt"
8-
"strings"
8+
"path"
9+
"regexp"
910

10-
"github.com/cilium/ebpf/link"
1111
log "github.com/sirupsen/logrus"
1212
"go.opentelemetry.io/ebpf-profiler/interpreter"
1313
"go.opentelemetry.io/ebpf-profiler/libpf"
1414
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
1515
"go.opentelemetry.io/ebpf-profiler/remotememory"
1616
)
1717

18+
// LdsoRegexp matches ld.so filenames across different distributions
19+
var LdsoRegexp = regexp.MustCompile(
20+
`^ld(?:-linux)?(?:-x86-64|-aarch64)?\.so\.\d+$|` +
21+
`^ld\.so\.\d+$|` +
22+
`^ld-\d+\.\d+\.so$|` +
23+
`^ld-musl-[^/]+\.so\.\d+$`)
24+
1825
// data holds the Uprobe link to keep it in memory
1926
type data struct {
20-
probe pfelf.USDTProbe
2127
path string
22-
probeLink link.Link
28+
usePoller bool
29+
probe pfelf.USDTProbe
30+
lc interpreter.LinkCloser
2331
}
2432

2533
// instance represents a per-PID instance of the rtld interpreter
2634
type instance struct {
2735
interpreter.InstanceStubs
28-
link link.Link
36+
usePoller bool
37+
lc interpreter.LinkCloser
2938
}
3039

3140
// Loader detects if the ELF file contains the rtld:map_complete USDT probe
32-
func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) {
33-
// Check if this is ld.so by examining the filename
41+
func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) {
42+
// Check if this is ld.so by examining just the basename
3443
fileName := info.FileName()
35-
if !strings.Contains(fileName, "ld-") && !strings.Contains(fileName, "ld.so") {
44+
baseName := path.Base(fileName)
45+
if !LdsoRegexp.MatchString(baseName) {
3646
return nil, nil
3747
}
3848

@@ -44,55 +54,87 @@ func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interprete
4454
// Look for .note.stapsdt section which contains USDT probes
4555
sec := ef.Section(".note.stapsdt")
4656
if sec == nil {
47-
log.Debugf("No .note.stapsdt section found in %s", fileName)
48-
return nil, nil
57+
log.Debugf("No .note.stapsdt section found in %s, will use poller fallback", fileName)
58+
return &data{
59+
path: fileName,
60+
usePoller: true,
61+
}, nil
4962
}
5063

5164
// Parse USDT probes from the section
5265
probes, err := pfelf.ParseUSDTProbes(sec)
5366
if err != nil {
5467
return nil, fmt.Errorf("failed to parse USDT probes: %w", err)
5568
}
56-
5769
// Look for rtld:map_complete probe
5870
for _, probe := range probes {
59-
if probe.Provider == "rtld" && probe.Name == "map_complete" {
60-
log.Infof("Found rtld:map_complete USDT probe in %s at 0x%x",
61-
fileName, probe.Location)
62-
return &data{
63-
path: fileName,
64-
probe: probe,
65-
}, nil
71+
if probe.Provider != "rtld" || probe.Name != "map_complete" {
72+
continue
6673
}
74+
log.Debugf("Found rtld:map_complete USDT probe in %s at 0x%x",
75+
fileName, probe.Location)
76+
77+
return &data{
78+
probe: probe,
79+
path: fileName,
80+
}, nil
6781
}
6882

69-
return nil, nil
83+
// No rtld:map_complete probe found, use poller fallback
84+
log.Debugf("No rtld:map_complete probe found in %s, will use poller fallback", fileName)
85+
return &data{
86+
path: fileName,
87+
usePoller: true,
88+
}, nil
7089
}
7190

72-
// Attach attaches the uprobe to the rtld:map_complete USDT probe
91+
// Attach attaches the uprobe to the rtld:map_complete USDT probe or registers with poller
7392
func (d *data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, _ libpf.Address,
7493
_ remotememory.RemoteMemory) (interpreter.Instance, error) {
75-
link, err := ebpf.AttachUSDTProbe(pid, d.path, d.probe, "usdt_rtld_map_complete")
76-
if err != nil {
77-
return nil, fmt.Errorf("failed to attach uprobe to rtld:map_complete usdt: %w", err)
94+
if d.usePoller {
95+
// Register this PID with the global poller
96+
// Create a trigger function that calls TriggerProcessSync on the ebpf handler
97+
triggerFunc := func(triggerPID libpf.PID) {
98+
if err := ebpf.TriggerProcessSync(triggerPID); err != nil {
99+
log.Debugf("[rtld] TriggerProcessSync failed for PID %d: %v", triggerPID, err)
100+
}
101+
}
102+
getPoller(triggerFunc).registerPID(pid)
103+
log.Debugf("[rtld] Registered PID %d with poller for %s", pid, d.path)
104+
105+
return &instance{usePoller: true}, nil
106+
} else {
107+
prog := "usdt_rtld_map_complete"
108+
lc, err := ebpf.AttachUSDTProbes(
109+
pid, d.path, prog, []pfelf.USDTProbe{d.probe}, nil, nil, false)
110+
if err != nil {
111+
return nil, fmt.Errorf("failed to attach uprobe to rtld:map_complete usdt: %w", err)
112+
}
113+
log.Debugf("[rtld] Using USDT probe for PID %d on %s", pid, d.path)
114+
d.lc = lc
115+
return &instance{lc: lc}, nil
78116
}
79-
log.Infof("Attached uprobe to rtld:map_complete usdt in PID %d", pid)
80-
return &instance{link: link}, nil
81117
}
82118

83-
// Detach removes the uprobe
119+
// Detach removes the uprobe or deregisters from poller
84120
func (i *instance) Detach(_ interpreter.EbpfHandler, pid libpf.PID) error {
85121
log.Debugf("[rtld] Detach called for PID %d", pid)
122+
if i.usePoller {
123+
// Deregister this PID from the global poller
124+
// Pass a nil trigger function since we're just deregistering
125+
getPoller(nil).deregisterPID(pid)
126+
log.Debugf("[rtld] Deregistered PID %d from poller", pid)
127+
}
86128
return nil
87129
}
88130

89131
// Unload cleans up the uprobe link
90132
func (d *data) Unload(_ interpreter.EbpfHandler) {
91-
if d.probeLink != nil {
92-
if err := d.probeLink.Close(); err != nil {
93-
log.Errorf("[rtld] Failed to close uprobe link: %v", err)
133+
if d.lc != nil {
134+
if err := d.lc.Unload(); err != nil {
135+
log.Errorf("[rtld] Failed to unload uprobe link: %v", err)
94136
}
95-
d.probeLink = nil
137+
d.lc = nil
96138
}
97139
log.Debugf("[rtld] Unloaded uprobe for %s", d.path)
98140
}

0 commit comments

Comments
 (0)