1 package link
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "os"
8 "path/filepath"
9 "runtime"
10 "strconv"
11 "strings"
12 "unsafe"
13
14 "github.com/cilium/ebpf"
15 "github.com/cilium/ebpf/asm"
16 "github.com/cilium/ebpf/internal"
17 "github.com/cilium/ebpf/internal/sys"
18 "github.com/cilium/ebpf/internal/unix"
19 )
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 var (
45 tracefsPath = "/sys/kernel/debug/tracing"
46
47 errInvalidInput = errors.New("invalid input")
48 )
49
50 const (
51 perfAllThreads = -1
52 )
53
54 type perfEventType uint8
55
56 const (
57 tracepointEvent perfEventType = iota
58 kprobeEvent
59 kretprobeEvent
60 uprobeEvent
61 uretprobeEvent
62 )
63
64
65
66
67 type perfEvent struct {
68
69 typ perfEventType
70
71
72 group string
73 name string
74
75
76 pmuID uint64
77
78 tracefsID uint64
79
80
81 cookie uint64
82
83
84 fd *sys.FD
85 }
86
87 func (pe *perfEvent) Close() error {
88 if err := pe.fd.Close(); err != nil {
89 return fmt.Errorf("closing perf event fd: %w", err)
90 }
91
92 switch pe.typ {
93 case kprobeEvent, kretprobeEvent:
94
95 if pe.tracefsID != 0 {
96 return closeTraceFSProbeEvent(kprobeType, pe.group, pe.name)
97 }
98 case uprobeEvent, uretprobeEvent:
99
100 if pe.tracefsID != 0 {
101 return closeTraceFSProbeEvent(uprobeType, pe.group, pe.name)
102 }
103 case tracepointEvent:
104
105 return nil
106 }
107
108 return nil
109 }
110
111
112 type perfEventLink struct {
113 RawLink
114 pe *perfEvent
115 }
116
117 func (pl *perfEventLink) isLink() {}
118
119
120
121
122
123
124
125
126
127
128
129
130 func (pl *perfEventLink) Pin(string) error {
131 return fmt.Errorf("perf event link pin: %w", ErrNotSupported)
132 }
133
134 func (pl *perfEventLink) Unpin() error {
135 return fmt.Errorf("perf event link unpin: %w", ErrNotSupported)
136 }
137
138 func (pl *perfEventLink) Close() error {
139 if err := pl.pe.Close(); err != nil {
140 return fmt.Errorf("perf event link close: %w", err)
141 }
142 return pl.fd.Close()
143 }
144
145 func (pl *perfEventLink) Update(prog *ebpf.Program) error {
146 return fmt.Errorf("perf event link update: %w", ErrNotSupported)
147 }
148
149
150
151 type perfEventIoctl struct {
152 *perfEvent
153 }
154
155 func (pi *perfEventIoctl) isLink() {}
156
157
158
159
160
161
162
163
164
165
166
167 func (pi *perfEventIoctl) Update(prog *ebpf.Program) error {
168 return fmt.Errorf("perf event ioctl update: %w", ErrNotSupported)
169 }
170
171 func (pi *perfEventIoctl) Pin(string) error {
172 return fmt.Errorf("perf event ioctl pin: %w", ErrNotSupported)
173 }
174
175 func (pi *perfEventIoctl) Unpin() error {
176 return fmt.Errorf("perf event ioctl unpin: %w", ErrNotSupported)
177 }
178
179 func (pi *perfEventIoctl) Info() (*Info, error) {
180 return nil, fmt.Errorf("perf event ioctl info: %w", ErrNotSupported)
181 }
182
183
184
185
186 func attachPerfEvent(pe *perfEvent, prog *ebpf.Program) (Link, error) {
187 if prog == nil {
188 return nil, errors.New("cannot attach a nil program")
189 }
190 if prog.FD() < 0 {
191 return nil, fmt.Errorf("invalid program: %w", sys.ErrClosedFd)
192 }
193
194 switch pe.typ {
195 case kprobeEvent, kretprobeEvent, uprobeEvent, uretprobeEvent:
196 if t := prog.Type(); t != ebpf.Kprobe {
197 return nil, fmt.Errorf("invalid program type (expected %s): %s", ebpf.Kprobe, t)
198 }
199 case tracepointEvent:
200 if t := prog.Type(); t != ebpf.TracePoint {
201 return nil, fmt.Errorf("invalid program type (expected %s): %s", ebpf.TracePoint, t)
202 }
203 default:
204 return nil, fmt.Errorf("unknown perf event type: %d", pe.typ)
205 }
206
207 if err := haveBPFLinkPerfEvent(); err == nil {
208 return attachPerfEventLink(pe, prog)
209 }
210 return attachPerfEventIoctl(pe, prog)
211 }
212
213 func attachPerfEventIoctl(pe *perfEvent, prog *ebpf.Program) (*perfEventIoctl, error) {
214 if pe.cookie != 0 {
215 return nil, fmt.Errorf("cookies are not supported: %w", ErrNotSupported)
216 }
217
218
219 err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_SET_BPF, prog.FD())
220 if err != nil {
221 return nil, fmt.Errorf("setting perf event bpf program: %w", err)
222 }
223
224
225 if err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_ENABLE, 0); err != nil {
226 return nil, fmt.Errorf("enable perf event: %s", err)
227 }
228
229 pi := &perfEventIoctl{pe}
230
231
232 runtime.SetFinalizer(pi, (*perfEventIoctl).Close)
233 return pi, nil
234 }
235
236
237
238
239 func attachPerfEventLink(pe *perfEvent, prog *ebpf.Program) (*perfEventLink, error) {
240 fd, err := sys.LinkCreatePerfEvent(&sys.LinkCreatePerfEventAttr{
241 ProgFd: uint32(prog.FD()),
242 TargetFd: pe.fd.Uint(),
243 AttachType: sys.BPF_PERF_EVENT,
244 BpfCookie: pe.cookie,
245 })
246 if err != nil {
247 return nil, fmt.Errorf("cannot create bpf perf link: %v", err)
248 }
249
250 pl := &perfEventLink{RawLink{fd: fd}, pe}
251
252
253 runtime.SetFinalizer(pl, (*perfEventLink).Close)
254 return pl, nil
255 }
256
257
258 func unsafeStringPtr(str string) (unsafe.Pointer, error) {
259 p, err := unix.BytePtrFromString(str)
260 if err != nil {
261 return nil, err
262 }
263 return unsafe.Pointer(p), nil
264 }
265
266
267
268
269
270
271 func getTraceEventID(group, name string) (uint64, error) {
272 name = sanitizeSymbol(name)
273 tid, err := uint64FromFile(tracefsPath, "events", group, name, "id")
274 if errors.Is(err, os.ErrNotExist) {
275 return 0, fmt.Errorf("trace event %s/%s: %w", group, name, os.ErrNotExist)
276 }
277 if err != nil {
278 return 0, fmt.Errorf("reading trace event ID of %s/%s: %w", group, name, err)
279 }
280
281 return tid, nil
282 }
283
284
285
286
287
288 func getPMUEventType(typ probeType) (uint64, error) {
289 et, err := uint64FromFile("/sys/bus/event_source/devices", typ.String(), "type")
290 if errors.Is(err, os.ErrNotExist) {
291 return 0, fmt.Errorf("pmu type %s: %w", typ, ErrNotSupported)
292 }
293 if err != nil {
294 return 0, fmt.Errorf("reading pmu type %s: %w", typ, err)
295 }
296
297 return et, nil
298 }
299
300
301
302
303 func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) {
304 attr := unix.PerfEventAttr{
305 Type: unix.PERF_TYPE_TRACEPOINT,
306 Config: tid,
307 Sample_type: unix.PERF_SAMPLE_RAW,
308 Sample: 1,
309 Wakeup: 1,
310 }
311
312 fd, err := unix.PerfEventOpen(&attr, pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
313 if err != nil {
314 return nil, fmt.Errorf("opening tracepoint perf event: %w", err)
315 }
316
317 return sys.NewFD(fd)
318 }
319
320
321
322
323 func uint64FromFile(base string, path ...string) (uint64, error) {
324 l := filepath.Join(path...)
325 p := filepath.Join(base, l)
326 if !strings.HasPrefix(p, base) {
327 return 0, fmt.Errorf("path '%s' attempts to escape base path '%s': %w", l, base, errInvalidInput)
328 }
329
330 data, err := os.ReadFile(p)
331 if err != nil {
332 return 0, fmt.Errorf("reading file %s: %w", p, err)
333 }
334
335 et := bytes.TrimSpace(data)
336 return strconv.ParseUint(string(et), 10, 64)
337 }
338
339
340
341
342
343 var haveBPFLinkPerfEvent = internal.FeatureTest("bpf_link_perf_event", "5.15", func() error {
344 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
345 Name: "probe_bpf_perf_link",
346 Type: ebpf.Kprobe,
347 Instructions: asm.Instructions{
348 asm.Mov.Imm(asm.R0, 0),
349 asm.Return(),
350 },
351 License: "MIT",
352 })
353 if err != nil {
354 return err
355 }
356 defer prog.Close()
357
358 _, err = sys.LinkCreatePerfEvent(&sys.LinkCreatePerfEventAttr{
359 ProgFd: uint32(prog.FD()),
360 AttachType: sys.BPF_PERF_EVENT,
361 })
362 if errors.Is(err, unix.EINVAL) {
363 return internal.ErrNotSupported
364 }
365 if errors.Is(err, unix.EBADF) {
366 return nil
367 }
368 return err
369 })
370
371
372
373
374
375
376
377 func isValidTraceID(s string) bool {
378 if len(s) < 1 {
379 return false
380 }
381 for i, c := range []byte(s) {
382 switch {
383 case c >= 'a' && c <= 'z':
384 case c >= 'A' && c <= 'Z':
385 case c == '_':
386 case i > 0 && c >= '0' && c <= '9':
387
388 default:
389 return false
390 }
391 }
392
393 return true
394 }
395
View as plain text