github.com/cilium/ebpf@v0.15.0/link/perf_event.go (about) 1 package link 2 3 import ( 4 "errors" 5 "fmt" 6 "runtime" 7 "unsafe" 8 9 "github.com/cilium/ebpf" 10 "github.com/cilium/ebpf/asm" 11 "github.com/cilium/ebpf/internal" 12 "github.com/cilium/ebpf/internal/sys" 13 "github.com/cilium/ebpf/internal/tracefs" 14 "github.com/cilium/ebpf/internal/unix" 15 ) 16 17 // Getting the terminology right is usually the hardest part. For posterity and 18 // for staying sane during implementation: 19 // 20 // - trace event: Representation of a kernel runtime hook. Filesystem entries 21 // under <tracefs>/events. Can be tracepoints (static), kprobes or uprobes. 22 // Can be instantiated into perf events (see below). 23 // - tracepoint: A predetermined hook point in the kernel. Exposed as trace 24 // events in (sub)directories under <tracefs>/events. Cannot be closed or 25 // removed, they are static. 26 // - k(ret)probe: Ephemeral trace events based on entry or exit points of 27 // exported kernel symbols. kprobe-based (tracefs) trace events can be 28 // created system-wide by writing to the <tracefs>/kprobe_events file, or 29 // they can be scoped to the current process by creating PMU perf events. 30 // - u(ret)probe: Ephemeral trace events based on user provides ELF binaries 31 // and offsets. uprobe-based (tracefs) trace events can be 32 // created system-wide by writing to the <tracefs>/uprobe_events file, or 33 // they can be scoped to the current process by creating PMU perf events. 34 // - perf event: An object instantiated based on an existing trace event or 35 // kernel symbol. Referred to by fd in userspace. 36 // Exactly one eBPF program can be attached to a perf event. Multiple perf 37 // events can be created from a single trace event. Closing a perf event 38 // stops any further invocations of the attached eBPF program. 39 40 var ( 41 errInvalidInput = tracefs.ErrInvalidInput 42 ) 43 44 const ( 45 perfAllThreads = -1 46 ) 47 48 // A perfEvent represents a perf event kernel object. Exactly one eBPF program 49 // can be attached to it. It is created based on a tracefs trace event or a 50 // Performance Monitoring Unit (PMU). 51 type perfEvent struct { 52 // Trace event backing this perfEvent. May be nil. 53 tracefsEvent *tracefs.Event 54 55 // This is the perf event FD. 56 fd *sys.FD 57 } 58 59 func newPerfEvent(fd *sys.FD, event *tracefs.Event) *perfEvent { 60 pe := &perfEvent{event, fd} 61 // Both event and fd have their own finalizer, but we want to 62 // guarantee that they are closed in a certain order. 63 runtime.SetFinalizer(pe, (*perfEvent).Close) 64 return pe 65 } 66 67 func (pe *perfEvent) Close() error { 68 runtime.SetFinalizer(pe, nil) 69 70 if err := pe.fd.Close(); err != nil { 71 return fmt.Errorf("closing perf event fd: %w", err) 72 } 73 74 if pe.tracefsEvent != nil { 75 return pe.tracefsEvent.Close() 76 } 77 78 return nil 79 } 80 81 // perfEventLink represents a bpf perf link. 82 type perfEventLink struct { 83 RawLink 84 pe *perfEvent 85 } 86 87 func (pl *perfEventLink) isLink() {} 88 89 // Pinning requires the underlying perf event FD to stay open. 90 // 91 // | PerfEvent FD | BpfLink FD | Works | 92 // |--------------|------------|-------| 93 // | Open | Open | Yes | 94 // | Closed | Open | No | 95 // | Open | Closed | No (Pin() -> EINVAL) | 96 // | Closed | Closed | No (Pin() -> EINVAL) | 97 // 98 // There is currently no pretty way to recover the perf event FD 99 // when loading a pinned link, so leave as not supported for now. 100 func (pl *perfEventLink) Pin(string) error { 101 return fmt.Errorf("perf event link pin: %w", ErrNotSupported) 102 } 103 104 func (pl *perfEventLink) Unpin() error { 105 return fmt.Errorf("perf event link unpin: %w", ErrNotSupported) 106 } 107 108 func (pl *perfEventLink) Close() error { 109 if err := pl.fd.Close(); err != nil { 110 return fmt.Errorf("perf link close: %w", err) 111 } 112 113 if err := pl.pe.Close(); err != nil { 114 return fmt.Errorf("perf event close: %w", err) 115 } 116 return nil 117 } 118 119 func (pl *perfEventLink) Update(prog *ebpf.Program) error { 120 return fmt.Errorf("perf event link update: %w", ErrNotSupported) 121 } 122 123 // perfEventIoctl implements Link and handles the perf event lifecycle 124 // via ioctl(). 125 type perfEventIoctl struct { 126 *perfEvent 127 } 128 129 func (pi *perfEventIoctl) isLink() {} 130 131 // Since 4.15 (e87c6bc3852b "bpf: permit multiple bpf attachments for a single perf event"), 132 // calling PERF_EVENT_IOC_SET_BPF appends the given program to a prog_array 133 // owned by the perf event, which means multiple programs can be attached 134 // simultaneously. 135 // 136 // Before 4.15, calling PERF_EVENT_IOC_SET_BPF more than once on a perf event 137 // returns EEXIST. 138 // 139 // Detaching a program from a perf event is currently not possible, so a 140 // program replacement mechanism cannot be implemented for perf events. 141 func (pi *perfEventIoctl) Update(prog *ebpf.Program) error { 142 return fmt.Errorf("perf event ioctl update: %w", ErrNotSupported) 143 } 144 145 func (pi *perfEventIoctl) Pin(string) error { 146 return fmt.Errorf("perf event ioctl pin: %w", ErrNotSupported) 147 } 148 149 func (pi *perfEventIoctl) Unpin() error { 150 return fmt.Errorf("perf event ioctl unpin: %w", ErrNotSupported) 151 } 152 153 func (pi *perfEventIoctl) Info() (*Info, error) { 154 return nil, fmt.Errorf("perf event ioctl info: %w", ErrNotSupported) 155 } 156 157 // attach the given eBPF prog to the perf event stored in pe. 158 // pe must contain a valid perf event fd. 159 // prog's type must match the program type stored in pe. 160 func attachPerfEvent(pe *perfEvent, prog *ebpf.Program, cookie uint64) (Link, error) { 161 if prog == nil { 162 return nil, errors.New("cannot attach a nil program") 163 } 164 if prog.FD() < 0 { 165 return nil, fmt.Errorf("invalid program: %w", sys.ErrClosedFd) 166 } 167 168 if err := haveBPFLinkPerfEvent(); err == nil { 169 return attachPerfEventLink(pe, prog, cookie) 170 } 171 172 if cookie != 0 { 173 return nil, fmt.Errorf("cookies are not supported: %w", ErrNotSupported) 174 } 175 176 return attachPerfEventIoctl(pe, prog) 177 } 178 179 func attachPerfEventIoctl(pe *perfEvent, prog *ebpf.Program) (*perfEventIoctl, error) { 180 // Assign the eBPF program to the perf event. 181 err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_SET_BPF, prog.FD()) 182 if err != nil { 183 return nil, fmt.Errorf("setting perf event bpf program: %w", err) 184 } 185 186 // PERF_EVENT_IOC_ENABLE and _DISABLE ignore their given values. 187 if err := unix.IoctlSetInt(pe.fd.Int(), unix.PERF_EVENT_IOC_ENABLE, 0); err != nil { 188 return nil, fmt.Errorf("enable perf event: %s", err) 189 } 190 191 return &perfEventIoctl{pe}, nil 192 } 193 194 // Use the bpf api to attach the perf event (BPF_LINK_TYPE_PERF_EVENT, 5.15+). 195 // 196 // https://github.com/torvalds/linux/commit/b89fbfbb854c9afc3047e8273cc3a694650b802e 197 func attachPerfEventLink(pe *perfEvent, prog *ebpf.Program, cookie uint64) (*perfEventLink, error) { 198 fd, err := sys.LinkCreatePerfEvent(&sys.LinkCreatePerfEventAttr{ 199 ProgFd: uint32(prog.FD()), 200 TargetFd: pe.fd.Uint(), 201 AttachType: sys.BPF_PERF_EVENT, 202 BpfCookie: cookie, 203 }) 204 if err != nil { 205 return nil, fmt.Errorf("cannot create bpf perf link: %v", err) 206 } 207 208 return &perfEventLink{RawLink{fd: fd}, pe}, nil 209 } 210 211 // unsafeStringPtr returns an unsafe.Pointer to a NUL-terminated copy of str. 212 func unsafeStringPtr(str string) (unsafe.Pointer, error) { 213 p, err := unix.BytePtrFromString(str) 214 if err != nil { 215 return nil, err 216 } 217 return unsafe.Pointer(p), nil 218 } 219 220 // openTracepointPerfEvent opens a tracepoint-type perf event. System-wide 221 // [k,u]probes created by writing to <tracefs>/[k,u]probe_events are tracepoints 222 // behind the scenes, and can be attached to using these perf events. 223 func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) { 224 attr := unix.PerfEventAttr{ 225 Type: unix.PERF_TYPE_TRACEPOINT, 226 Config: tid, 227 Sample_type: unix.PERF_SAMPLE_RAW, 228 Sample: 1, 229 Wakeup: 1, 230 } 231 232 fd, err := unix.PerfEventOpen(&attr, pid, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) 233 if err != nil { 234 return nil, fmt.Errorf("opening tracepoint perf event: %w", err) 235 } 236 237 return sys.NewFD(fd) 238 } 239 240 // Probe BPF perf link. 241 // 242 // https://elixir.bootlin.com/linux/v5.16.8/source/kernel/bpf/syscall.c#L4307 243 // https://github.com/torvalds/linux/commit/b89fbfbb854c9afc3047e8273cc3a694650b802e 244 var haveBPFLinkPerfEvent = internal.NewFeatureTest("bpf_link_perf_event", "5.15", func() error { 245 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 246 Name: "probe_bpf_perf_link", 247 Type: ebpf.Kprobe, 248 Instructions: asm.Instructions{ 249 asm.Mov.Imm(asm.R0, 0), 250 asm.Return(), 251 }, 252 License: "MIT", 253 }) 254 if err != nil { 255 return err 256 } 257 defer prog.Close() 258 259 _, err = sys.LinkCreatePerfEvent(&sys.LinkCreatePerfEventAttr{ 260 ProgFd: uint32(prog.FD()), 261 AttachType: sys.BPF_PERF_EVENT, 262 }) 263 if errors.Is(err, unix.EINVAL) { 264 return internal.ErrNotSupported 265 } 266 if errors.Is(err, unix.EBADF) { 267 return nil 268 } 269 return err 270 })