1 package link
2
3 import (
4 "bytes"
5 "crypto/rand"
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10 "runtime"
11 "strings"
12 "sync"
13 "syscall"
14 "unsafe"
15
16 "github.com/cilium/ebpf"
17 "github.com/cilium/ebpf/internal/sys"
18 "github.com/cilium/ebpf/internal/unix"
19 )
20
21 var (
22 kprobeEventsPath = filepath.Join(tracefsPath, "kprobe_events")
23
24 kprobeRetprobeBit = struct {
25 once sync.Once
26 value uint64
27 err error
28 }{}
29 )
30
31 type probeType uint8
32
33 type probeArgs struct {
34 symbol, group, path string
35 offset, refCtrOffset, cookie uint64
36 pid int
37 ret bool
38 }
39
40
41
42 type KprobeOptions struct {
43
44
45
46
47 Cookie uint64
48
49
50
51 Offset uint64
52 }
53
54 const (
55 kprobeType probeType = iota
56 uprobeType
57 )
58
59 func (pt probeType) String() string {
60 if pt == kprobeType {
61 return "kprobe"
62 }
63 return "uprobe"
64 }
65
66 func (pt probeType) EventsPath() string {
67 if pt == kprobeType {
68 return kprobeEventsPath
69 }
70 return uprobeEventsPath
71 }
72
73 func (pt probeType) PerfEventType(ret bool) perfEventType {
74 if pt == kprobeType {
75 if ret {
76 return kretprobeEvent
77 }
78 return kprobeEvent
79 }
80 if ret {
81 return uretprobeEvent
82 }
83 return uprobeEvent
84 }
85
86 func (pt probeType) RetprobeBit() (uint64, error) {
87 if pt == kprobeType {
88 return kretprobeBit()
89 }
90 return uretprobeBit()
91 }
92
93
94
95
96
97
98
99
100
101
102 func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
103 k, err := kprobe(symbol, prog, opts, false)
104 if err != nil {
105 return nil, err
106 }
107
108 lnk, err := attachPerfEvent(k, prog)
109 if err != nil {
110 k.Close()
111 return nil, err
112 }
113
114 return lnk, nil
115 }
116
117
118
119
120
121
122
123
124
125
126 func Kretprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
127 k, err := kprobe(symbol, prog, opts, true)
128 if err != nil {
129 return nil, err
130 }
131
132 lnk, err := attachPerfEvent(k, prog)
133 if err != nil {
134 k.Close()
135 return nil, err
136 }
137
138 return lnk, nil
139 }
140
141
142
143 func isValidKprobeSymbol(s string) bool {
144 if len(s) < 1 {
145 return false
146 }
147
148 for i, c := range []byte(s) {
149 switch {
150 case c >= 'a' && c <= 'z':
151 case c >= 'A' && c <= 'Z':
152 case c == '_':
153 case i > 0 && c >= '0' && c <= '9':
154
155
156
157
158 case i > 0 && c == '.':
159
160 default:
161 return false
162 }
163 }
164
165 return true
166 }
167
168
169
170 func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*perfEvent, error) {
171 if symbol == "" {
172 return nil, fmt.Errorf("symbol name cannot be empty: %w", errInvalidInput)
173 }
174 if prog == nil {
175 return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
176 }
177 if !isValidKprobeSymbol(symbol) {
178 return nil, fmt.Errorf("symbol '%s' must be a valid symbol in /proc/kallsyms: %w", symbol, errInvalidInput)
179 }
180 if prog.Type() != ebpf.Kprobe {
181 return nil, fmt.Errorf("eBPF program type %s is not a Kprobe: %w", prog.Type(), errInvalidInput)
182 }
183
184 args := probeArgs{
185 pid: perfAllThreads,
186 symbol: symbol,
187 ret: ret,
188 }
189
190 if opts != nil {
191 args.cookie = opts.Cookie
192 args.offset = opts.Offset
193 }
194
195
196 tp, err := pmuKprobe(args)
197 if errors.Is(err, os.ErrNotExist) {
198 args.symbol = platformPrefix(symbol)
199 tp, err = pmuKprobe(args)
200 }
201 if err == nil {
202 return tp, nil
203 }
204 if err != nil && !errors.Is(err, ErrNotSupported) {
205 return nil, fmt.Errorf("creating perf_kprobe PMU: %w", err)
206 }
207
208
209 args.symbol = symbol
210 tp, err = tracefsKprobe(args)
211 if errors.Is(err, os.ErrNotExist) {
212 args.symbol = platformPrefix(symbol)
213 tp, err = tracefsKprobe(args)
214 }
215 if err != nil {
216 return nil, fmt.Errorf("creating trace event '%s' in tracefs: %w", symbol, err)
217 }
218
219 return tp, nil
220 }
221
222
223
224 func pmuKprobe(args probeArgs) (*perfEvent, error) {
225 return pmuProbe(kprobeType, args)
226 }
227
228
229
230
231
232
233
234
235 func pmuProbe(typ probeType, args probeArgs) (*perfEvent, error) {
236
237
238 et, err := getPMUEventType(typ)
239 if err != nil {
240 return nil, err
241 }
242
243 var config uint64
244 if args.ret {
245 bit, err := typ.RetprobeBit()
246 if err != nil {
247 return nil, err
248 }
249 config |= 1 << bit
250 }
251
252 var (
253 attr unix.PerfEventAttr
254 sp unsafe.Pointer
255 )
256 switch typ {
257 case kprobeType:
258
259 sp, err = unsafeStringPtr(args.symbol)
260 if err != nil {
261 return nil, err
262 }
263
264 attr = unix.PerfEventAttr{
265
266
267 Size: unix.PERF_ATTR_SIZE_VER1,
268 Type: uint32(et),
269 Ext1: uint64(uintptr(sp)),
270 Ext2: args.offset,
271 Config: config,
272 }
273 case uprobeType:
274 sp, err = unsafeStringPtr(args.path)
275 if err != nil {
276 return nil, err
277 }
278
279 if args.refCtrOffset != 0 {
280 config |= args.refCtrOffset << uprobeRefCtrOffsetShift
281 }
282
283 attr = unix.PerfEventAttr{
284
285
286
287
288 Size: unix.PERF_ATTR_SIZE_VER1,
289 Type: uint32(et),
290 Ext1: uint64(uintptr(sp)),
291 Ext2: args.offset,
292 Config: config,
293 }
294 }
295
296 rawFd, err := unix.PerfEventOpen(&attr, args.pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
297
298
299
300
301 if errors.Is(err, unix.EINVAL) && strings.Contains(args.symbol, ".") {
302 return nil, fmt.Errorf("symbol '%s+%#x': older kernels don't accept dots: %w", args.symbol, args.offset, ErrNotSupported)
303 }
304
305
306
307 if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
308 return nil, fmt.Errorf("symbol '%s+%#x' not found: %w", args.symbol, args.offset, os.ErrNotExist)
309 }
310
311
312 if errors.Is(err, syscall.EILSEQ) {
313 return nil, fmt.Errorf("symbol '%s+%#x' not found (bad insn boundary): %w", args.symbol, args.offset, os.ErrNotExist)
314 }
315
316
317 if errors.Is(err, unix.ENOTSUPP) {
318 return nil, fmt.Errorf("failed setting uprobe on offset %#x (possible trap insn): %w", args.offset, err)
319 }
320 if err != nil {
321 return nil, fmt.Errorf("opening perf event: %w", err)
322 }
323
324
325 runtime.KeepAlive(sp)
326
327 fd, err := sys.NewFD(rawFd)
328 if err != nil {
329 return nil, err
330 }
331
332
333 return &perfEvent{
334 typ: typ.PerfEventType(args.ret),
335 name: args.symbol,
336 pmuID: et,
337 cookie: args.cookie,
338 fd: fd,
339 }, nil
340 }
341
342
343 func tracefsKprobe(args probeArgs) (*perfEvent, error) {
344 return tracefsProbe(kprobeType, args)
345 }
346
347
348
349
350
351
352
353 func tracefsProbe(typ probeType, args probeArgs) (_ *perfEvent, err error) {
354
355
356
357 group, err := randomGroup("ebpf")
358 if err != nil {
359 return nil, fmt.Errorf("randomizing group name: %w", err)
360 }
361 args.group = group
362
363
364
365
366
367 _, err = getTraceEventID(group, args.symbol)
368 if err == nil {
369 return nil, fmt.Errorf("trace event already exists: %s/%s", group, args.symbol)
370 }
371 if err != nil && !errors.Is(err, os.ErrNotExist) {
372 return nil, fmt.Errorf("checking trace event %s/%s: %w", group, args.symbol, err)
373 }
374
375
376 if err := createTraceFSProbeEvent(typ, args); err != nil {
377 return nil, fmt.Errorf("creating probe entry on tracefs: %w", err)
378 }
379 defer func() {
380 if err != nil {
381
382
383
384
385 _ = closeTraceFSProbeEvent(typ, args.group, args.symbol)
386 }
387 }()
388
389
390 tid, err := getTraceEventID(group, args.symbol)
391 if err != nil {
392 return nil, fmt.Errorf("getting trace event id: %w", err)
393 }
394
395
396 fd, err := openTracepointPerfEvent(tid, args.pid)
397 if err != nil {
398 return nil, err
399 }
400
401 return &perfEvent{
402 typ: typ.PerfEventType(args.ret),
403 group: group,
404 name: args.symbol,
405 tracefsID: tid,
406 cookie: args.cookie,
407 fd: fd,
408 }, nil
409 }
410
411
412
413
414
415 func createTraceFSProbeEvent(typ probeType, args probeArgs) error {
416
417 f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
418 if err != nil {
419 return fmt.Errorf("error opening '%s': %w", typ.EventsPath(), err)
420 }
421 defer f.Close()
422
423 var pe, token string
424 switch typ {
425 case kprobeType:
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 token = kprobeToken(args)
441 pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, sanitizeSymbol(args.symbol), token)
442 case uprobeType:
443
444
445
446
447
448
449
450
451
452
453 token = uprobeToken(args)
454 pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.ret), args.group, args.symbol, token)
455 }
456 _, err = f.WriteString(pe)
457
458
459
460
461
462 if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
463 return fmt.Errorf("token %s: %w", token, os.ErrNotExist)
464 }
465
466
467 if errors.Is(err, syscall.EILSEQ) {
468 return fmt.Errorf("token %s: bad insn boundary: %w", token, os.ErrNotExist)
469 }
470
471
472 if errors.Is(err, syscall.ERANGE) {
473 return fmt.Errorf("token %s: offset too big: %w", token, os.ErrNotExist)
474 }
475 if err != nil {
476 return fmt.Errorf("writing '%s' to '%s': %w", pe, typ.EventsPath(), err)
477 }
478
479 return nil
480 }
481
482
483
484 func closeTraceFSProbeEvent(typ probeType, group, symbol string) error {
485 f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
486 if err != nil {
487 return fmt.Errorf("error opening %s: %w", typ.EventsPath(), err)
488 }
489 defer f.Close()
490
491
492
493 pe := fmt.Sprintf("-:%s/%s", group, sanitizeSymbol(symbol))
494 if _, err = f.WriteString(pe); err != nil {
495 return fmt.Errorf("writing '%s' to '%s': %w", pe, typ.EventsPath(), err)
496 }
497
498 return nil
499 }
500
501
502
503
504
505 func randomGroup(prefix string) (string, error) {
506 if !isValidTraceID(prefix) {
507 return "", fmt.Errorf("prefix '%s' must be alphanumeric or underscore: %w", prefix, errInvalidInput)
508 }
509
510 b := make([]byte, 8)
511 if _, err := rand.Read(b); err != nil {
512 return "", fmt.Errorf("reading random bytes: %w", err)
513 }
514
515 group := fmt.Sprintf("%s_%x", prefix, b)
516 if len(group) > 63 {
517 return "", fmt.Errorf("group name '%s' cannot be longer than 63 characters: %w", group, errInvalidInput)
518 }
519
520 return group, nil
521 }
522
523 func probePrefix(ret bool) string {
524 if ret {
525 return "r"
526 }
527 return "p"
528 }
529
530
531
532 func determineRetprobeBit(typ probeType) (uint64, error) {
533 p := filepath.Join("/sys/bus/event_source/devices/", typ.String(), "/format/retprobe")
534
535 data, err := os.ReadFile(p)
536 if err != nil {
537 return 0, err
538 }
539
540 var rp uint64
541 n, err := fmt.Sscanf(string(bytes.TrimSpace(data)), "config:%d", &rp)
542 if err != nil {
543 return 0, fmt.Errorf("parse retprobe bit: %w", err)
544 }
545 if n != 1 {
546 return 0, fmt.Errorf("parse retprobe bit: expected 1 item, got %d", n)
547 }
548
549 return rp, nil
550 }
551
552 func kretprobeBit() (uint64, error) {
553 kprobeRetprobeBit.once.Do(func() {
554 kprobeRetprobeBit.value, kprobeRetprobeBit.err = determineRetprobeBit(kprobeType)
555 })
556 return kprobeRetprobeBit.value, kprobeRetprobeBit.err
557 }
558
559
560 func kprobeToken(args probeArgs) string {
561 po := args.symbol
562
563 if args.offset != 0 {
564 po += fmt.Sprintf("+%#x", args.offset)
565 }
566
567 return po
568 }
569
View as plain text