1 package link
2
3 import (
4 "debug/elf"
5 "errors"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "sync"
11
12 "github.com/cilium/ebpf"
13 "github.com/cilium/ebpf/internal"
14 )
15
16 var (
17 uprobeEventsPath = filepath.Join(tracefsPath, "uprobe_events")
18
19 uprobeRetprobeBit = struct {
20 once sync.Once
21 value uint64
22 err error
23 }{}
24
25 uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset"
26
27 uprobeRefCtrOffsetShift = 32
28 haveRefCtrOffsetPMU = internal.FeatureTest("RefCtrOffsetPMU", "4.20", func() error {
29 _, err := os.Stat(uprobeRefCtrOffsetPMUPath)
30 if err != nil {
31 return internal.ErrNotSupported
32 }
33 return nil
34 })
35
36
37
38 ErrNoSymbol = errors.New("not found")
39 )
40
41
42 type Executable struct {
43
44 path string
45
46 addresses map[string]uint64
47 }
48
49
50
51 type UprobeOptions struct {
52
53
54 Address uint64
55
56
57
58
59
60 Offset uint64
61
62
63 PID int
64
65
66
67
68
69
70
71
72
73
74 RefCtrOffset uint64
75
76
77
78
79 Cookie uint64
80 }
81
82
83
84
85
86
87 func OpenExecutable(path string) (*Executable, error) {
88 if path == "" {
89 return nil, fmt.Errorf("path cannot be empty")
90 }
91
92 f, err := os.Open(path)
93 if err != nil {
94 return nil, fmt.Errorf("open file '%s': %w", path, err)
95 }
96 defer f.Close()
97
98 se, err := internal.NewSafeELFFile(f)
99 if err != nil {
100 return nil, fmt.Errorf("parse ELF file: %w", err)
101 }
102
103 if se.Type != elf.ET_EXEC && se.Type != elf.ET_DYN {
104
105 return nil, errors.New("the given file is not an executable or a shared object")
106 }
107
108 ex := Executable{
109 path: path,
110 addresses: make(map[string]uint64),
111 }
112
113 if err := ex.load(se); err != nil {
114 return nil, err
115 }
116
117 return &ex, nil
118 }
119
120 func (ex *Executable) load(f *internal.SafeELFFile) error {
121 syms, err := f.Symbols()
122 if err != nil && !errors.Is(err, elf.ErrNoSymbols) {
123 return err
124 }
125
126 dynsyms, err := f.DynamicSymbols()
127 if err != nil && !errors.Is(err, elf.ErrNoSymbols) {
128 return err
129 }
130
131 syms = append(syms, dynsyms...)
132
133 for _, s := range syms {
134 if elf.ST_TYPE(s.Info) != elf.STT_FUNC {
135
136 continue
137 }
138
139 address := s.Value
140
141
142 for _, prog := range f.Progs {
143
144 if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 {
145 continue
146 }
147
148 if prog.Vaddr <= s.Value && s.Value < (prog.Vaddr+prog.Memsz) {
149
150
151
152
153
154
155 address = s.Value - prog.Vaddr + prog.Off
156 break
157 }
158 }
159
160 ex.addresses[s.Name] = address
161 }
162
163 return nil
164 }
165
166
167
168
169 func (ex *Executable) address(symbol string, opts *UprobeOptions) (uint64, error) {
170 if opts.Address > 0 {
171 return opts.Address + opts.Offset, nil
172 }
173
174 address, ok := ex.addresses[symbol]
175 if !ok {
176 return 0, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol)
177 }
178
179
180
181
182
183
184
185 if address == 0 {
186 return 0, fmt.Errorf("cannot resolve %s library call '%s': %w "+
187 "(consider providing UprobeOptions.Address)", ex.path, symbol, ErrNotSupported)
188 }
189
190 return address + opts.Offset, nil
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 func (ex *Executable) Uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
214 u, err := ex.uprobe(symbol, prog, opts, false)
215 if err != nil {
216 return nil, err
217 }
218
219 lnk, err := attachPerfEvent(u, prog)
220 if err != nil {
221 u.Close()
222 return nil, err
223 }
224
225 return lnk, nil
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247 func (ex *Executable) Uretprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions) (Link, error) {
248 u, err := ex.uprobe(symbol, prog, opts, true)
249 if err != nil {
250 return nil, err
251 }
252
253 lnk, err := attachPerfEvent(u, prog)
254 if err != nil {
255 u.Close()
256 return nil, err
257 }
258
259 return lnk, nil
260 }
261
262
263
264 func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOptions, ret bool) (*perfEvent, error) {
265 if prog == nil {
266 return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
267 }
268 if prog.Type() != ebpf.Kprobe {
269 return nil, fmt.Errorf("eBPF program type %s is not Kprobe: %w", prog.Type(), errInvalidInput)
270 }
271 if opts == nil {
272 opts = &UprobeOptions{}
273 }
274
275 offset, err := ex.address(symbol, opts)
276 if err != nil {
277 return nil, err
278 }
279
280 pid := opts.PID
281 if pid == 0 {
282 pid = perfAllThreads
283 }
284
285 if opts.RefCtrOffset != 0 {
286 if err := haveRefCtrOffsetPMU(); err != nil {
287 return nil, fmt.Errorf("uprobe ref_ctr_offset: %w", err)
288 }
289 }
290
291 args := probeArgs{
292 symbol: symbol,
293 path: ex.path,
294 offset: offset,
295 pid: pid,
296 refCtrOffset: opts.RefCtrOffset,
297 ret: ret,
298 cookie: opts.Cookie,
299 }
300
301
302 tp, err := pmuUprobe(args)
303 if err == nil {
304 return tp, nil
305 }
306 if err != nil && !errors.Is(err, ErrNotSupported) {
307 return nil, fmt.Errorf("creating perf_uprobe PMU: %w", err)
308 }
309
310
311 args.symbol = sanitizeSymbol(symbol)
312 tp, err = tracefsUprobe(args)
313 if err != nil {
314 return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
315 }
316
317 return tp, nil
318 }
319
320
321 func pmuUprobe(args probeArgs) (*perfEvent, error) {
322 return pmuProbe(uprobeType, args)
323 }
324
325
326 func tracefsUprobe(args probeArgs) (*perfEvent, error) {
327 return tracefsProbe(uprobeType, args)
328 }
329
330
331
332 func sanitizeSymbol(s string) string {
333 var b strings.Builder
334 b.Grow(len(s))
335 var skip bool
336 for _, c := range []byte(s) {
337 switch {
338 case c >= 'a' && c <= 'z',
339 c >= 'A' && c <= 'Z',
340 c >= '0' && c <= '9':
341 skip = false
342 b.WriteByte(c)
343
344 default:
345 if !skip {
346 b.WriteByte('_')
347 skip = true
348 }
349 }
350 }
351
352 return b.String()
353 }
354
355
356 func uprobeToken(args probeArgs) string {
357 po := fmt.Sprintf("%s:%#x", args.path, args.offset)
358
359 if args.refCtrOffset != 0 {
360
361
362 po += fmt.Sprintf("(%#x)", args.refCtrOffset)
363 }
364
365 return po
366 }
367
368 func uretprobeBit() (uint64, error) {
369 uprobeRetprobeBit.once.Do(func() {
370 uprobeRetprobeBit.value, uprobeRetprobeBit.err = determineRetprobeBit(uprobeType)
371 })
372 return uprobeRetprobeBit.value, uprobeRetprobeBit.err
373 }
374
View as plain text