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  })