Skip to content

Commit e8c621a

Browse files
authored
Refactor dlopen tracking to use uprobe instead of USDT/polling (#164)
This commit refactors the dlopen tracking mechanism to use a more efficient uprobe-based approach, eliminating the need for USDT probes and the polling fallback mechanism. Key changes: * **rtld interpreter simplification**: - Store dlopen symbol address in Loader - Remove polling-based fallback mechanism (rtld_poller.go) - Simplify Loader to only detect dlopen symbol presence - Use direct uprobe attachment via AttachUprobe() * **eBPF changes**: - Move uprobe_dlopen handler from usdt.ebpf.c to uprobe.ebpf.c - Remove usdt.ebpf.c (no longer needed) - Consolidate uprobe handlers in single file * **Metrics updates**: - Rename DlopenUSDTHits to DlopenUprobeHits - Update metric descriptions to reflect uprobe usage Benefits: - Avoid the poller overhead and complexity - More reliable dlopen detection (no dependency on USDT probe availability) - Better performance (symbol lookup done once in Loader vs every Attach)
1 parent 348bd9e commit e8c621a

File tree

17 files changed

+121
-466
lines changed

17 files changed

+121
-466
lines changed

interpreter/instancestubs.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ func (mockup *EbpfHandlerStubs) AttachUSDTProbes(libpf.PID, string, string, []pf
7777
return nil, nil
7878
}
7979

80-
func (mockup *EbpfHandlerStubs) TriggerProcessSync(libpf.PID) error {
81-
return nil
82-
}
83-
84-
func (mockup *EbpfHandlerStubs) SetProcessSyncTrigger(func(pid libpf.PID)) {
80+
func (mockup *EbpfHandlerStubs) AttachUprobe(
81+
libpf.PID, string, uint64, string) (LinkCloser, error) {
82+
return nil, nil
8583
}

interpreter/rtld/rtld.go

Lines changed: 32 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -5,125 +5,71 @@ package rtld // import "go.opentelemetry.io/ebpf-profiler/interpreter/rtld"
55

66
import (
77
"fmt"
8-
"path"
9-
"regexp"
108

119
log "github.com/sirupsen/logrus"
1210
"go.opentelemetry.io/ebpf-profiler/interpreter"
1311
"go.opentelemetry.io/ebpf-profiler/libpf"
14-
"go.opentelemetry.io/ebpf-profiler/libpf/pfelf"
1512
"go.opentelemetry.io/ebpf-profiler/remotememory"
1613
)
1714

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-
2515
// data holds the Uprobe link to keep it in memory
2616
type data struct {
27-
path string
28-
usePoller bool
29-
probe pfelf.USDTProbe
30-
lc interpreter.LinkCloser
17+
path string
18+
address uint64
19+
lc interpreter.LinkCloser
3120
}
3221

33-
// instance represents a per-PID instance of the rtld interpreter
22+
// instance represents a per-PID instance of the dlopen interpreter
3423
type instance struct {
3524
interpreter.InstanceStubs
36-
usePoller bool
37-
lc interpreter.LinkCloser
25+
lc interpreter.LinkCloser
3826
}
3927

40-
// Loader detects if the ELF file contains the rtld:map_complete USDT probe
28+
// Loader detects if the ELF file contains the dlopen symbol in its dynamic symbol table
4129
func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) {
42-
// Check if this is ld.so by examining just the basename
4330
fileName := info.FileName()
44-
baseName := path.Base(fileName)
45-
if !LdsoRegexp.MatchString(baseName) {
46-
return nil, nil
47-
}
4831

4932
ef, err := info.GetELF()
5033
if err != nil {
5134
return nil, err
5235
}
5336

54-
// Look for .note.stapsdt section which contains USDT probes
55-
sec := ef.Section(".note.stapsdt")
56-
if sec == 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
37+
// Look for the dlopen symbol in the dynamic symbol table
38+
sym, err := ef.LookupSymbol("dlopen")
39+
if err != nil || sym == nil {
40+
// No dlopen symbol found, this library doesn't support dynamic loading
41+
return nil, nil
6242
}
6343

64-
// Parse USDT probes from the section
65-
probes, err := pfelf.ParseUSDTProbes(sec)
66-
if err != nil {
67-
return nil, fmt.Errorf("failed to parse USDT probes: %w", err)
68-
}
69-
// Look for rtld:map_complete probe
70-
for _, probe := range probes {
71-
if probe.Provider != "rtld" || probe.Name != "map_complete" {
72-
continue
73-
}
74-
log.Debugf("Found rtld:map_complete USDT probe in %s at 0x%x",
75-
fileName, probe.Location)
44+
log.Debugf("Found dlopen symbol in %s at 0x%x", fileName, sym.Address)
7645

77-
return &data{
78-
probe: probe,
79-
path: fileName,
80-
}, nil
81-
}
82-
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)
8546
return &data{
86-
path: fileName,
87-
usePoller: true,
47+
path: fileName,
48+
address: uint64(sym.Address),
8849
}, nil
8950
}
9051

91-
// Attach attaches the uprobe to the rtld:map_complete USDT probe or registers with poller
92-
func (d *data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, _ libpf.Address,
52+
// Attach attaches the uprobe to the dlopen function
53+
func (d *data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias libpf.Address,
9354
_ remotememory.RemoteMemory) (interpreter.Instance, error) {
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
55+
// Attach uprobe to dlopen using the address stored during Loader
56+
lc, err := ebpf.AttachUprobe(pid, d.path, d.address, "uprobe_dlopen")
57+
if err != nil {
58+
return nil, fmt.Errorf("failed to attach uprobe to dlopen: %w", err)
11659
}
60+
61+
log.Debugf("[dlopen] Attached uprobe to dlopen for PID %d on %s at 0x%x",
62+
pid, d.path, d.address)
63+
64+
d.lc = lc
65+
return &instance{lc: lc}, nil
11766
}
11867

119-
// Detach removes the uprobe or deregisters from poller
68+
// Detach removes the uprobe
12069
func (i *instance) Detach(_ interpreter.EbpfHandler, pid libpf.PID) error {
121-
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)
70+
log.Debugf("[dlopen] Detach called for PID %d", pid)
71+
if i.lc != nil {
72+
return i.lc.Detach()
12773
}
12874
return nil
12975
}
@@ -132,9 +78,9 @@ func (i *instance) Detach(_ interpreter.EbpfHandler, pid libpf.PID) error {
13278
func (d *data) Unload(_ interpreter.EbpfHandler) {
13379
if d.lc != nil {
13480
if err := d.lc.Unload(); err != nil {
135-
log.Errorf("[rtld] Failed to unload uprobe link: %v", err)
81+
log.Errorf("[dlopen] Failed to unload uprobe link: %v", err)
13682
}
13783
d.lc = nil
13884
}
139-
log.Debugf("[rtld] Unloaded uprobe for %s", d.path)
85+
log.Debugf("[dlopen] Unloaded uprobe for %s", d.path)
14086
}

interpreter/rtld/rtld_poller.go

Lines changed: 0 additions & 183 deletions
This file was deleted.

0 commit comments

Comments
 (0)