github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/perf/perf_event.go (about)

     1  package perf
     2  
     3  import (
     4  	"fmt"
     5  	"syscall"
     6  
     7  	"github.com/dylandreimerink/gobpfld/bpfsys"
     8  	bpfSyscall "github.com/dylandreimerink/gobpfld/internal/syscall"
     9  	"golang.org/x/sys/unix"
    10  )
    11  
    12  // Event represents an linux perf event in userspace.
    13  type Event struct {
    14  	Type bpfSyscall.PerfType
    15  
    16  	fd              FD
    17  	attachedProgram bpfsys.BPFfd
    18  	kprobe          *KProbe
    19  	uprobe          *UProbe
    20  }
    21  
    22  // AttachBPFProgram attach a loaded BPF program to the perf event.
    23  func (e *Event) AttachBPFProgram(programFD bpfsys.BPFfd) error {
    24  	// TODO check that fd is set
    25  
    26  	// Attach BPF program
    27  	err := bpfSyscall.IOCtl(int(e.fd), unix.PERF_EVENT_IOC_SET_BPF, uintptr(programFD))
    28  	if err != nil {
    29  		return err
    30  	}
    31  
    32  	// Enable the perf event
    33  	err = bpfSyscall.IOCtl(int(e.fd), unix.PERF_EVENT_IOC_ENABLE, 0)
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	e.attachedProgram = programFD
    39  
    40  	return nil
    41  }
    42  
    43  func (e *Event) DetachBPFProgram() error {
    44  	// disable the perf event
    45  	err := bpfSyscall.IOCtl(int(e.fd), unix.PERF_EVENT_IOC_DISABLE, 0)
    46  	if err != nil {
    47  		return fmt.Errorf("ioctl disable perf event: %w", err)
    48  	}
    49  
    50  	// close the fd of the perf event
    51  	err = syscall.Close(int(e.fd))
    52  	if err != nil {
    53  		return fmt.Errorf("close perf event: %w", err)
    54  	}
    55  
    56  	if e.kprobe != nil {
    57  		// ignore error
    58  		err = e.kprobe.Clear()
    59  		if err != nil {
    60  			return fmt.Errorf("clear kprobe: %w", err)
    61  		}
    62  	}
    63  	if e.uprobe != nil {
    64  		// ignore error
    65  		err = e.uprobe.Clear()
    66  		if err != nil {
    67  			return fmt.Errorf("clear uprobe: %w", err)
    68  		}
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  type FD uint32
    75  
    76  // Close closes a file descriptor
    77  func (fd FD) Close() error {
    78  	err := unix.Close(int(fd))
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // OpenTracepointEvent opens a perf event for an existing tracepoint. Tracepoint perf events can be used to to attach
    87  // BPF_PROG_TYPE_TRACEPOINT applications to.
    88  func OpenTracepointEvent(category, name string) (*Event, error) {
    89  	id, err := getTracepointID(category, name)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("getTracepointID: %w", err)
    92  	}
    93  
    94  	return perfEventOpen(bpfSyscall.PerfEventAttr{
    95  		Type:   bpfSyscall.PERF_TYPE_TRACEPOINT,
    96  		Size:   bpfSyscall.AttrSize,
    97  		Config: uint64(id),
    98  	}, -1, 0, -1, bpfSyscall.PerfEventOpenFDCloseOnExit)
    99  }
   100  
   101  // TODO add open event buffer function
   102  
   103  func OpenKProbeEvent(kprobeOpts KProbeOpts) (*Event, error) {
   104  	kprobe, err := newKProbe(kprobeOpts)
   105  	if err != nil {
   106  		return nil, fmt.Errorf("kprobe: %w", err)
   107  	}
   108  
   109  	// TODO using the debugfs and tracepoint type is apparently legacy, the new way to do it is using
   110  	//   dynamic PMU's. Couldn't get this to work, so in future figure it out and add as preferred method
   111  	//   and keep this one as fallback for older kernels
   112  
   113  	event, err := perfEventOpen(bpfSyscall.PerfEventAttr{
   114  		Type:   bpfSyscall.PERF_TYPE_TRACEPOINT,
   115  		Size:   bpfSyscall.AttrSize,
   116  		Config: uint64(kprobe.ID),
   117  	}, -1, 0, -1, bpfSyscall.PerfEventOpenFDCloseOnExit)
   118  	if err != nil {
   119  		return nil, fmt.Errorf("open perf event: %w", err)
   120  	}
   121  
   122  	event.kprobe = kprobe
   123  
   124  	return event, nil
   125  }
   126  
   127  func OpenUProbeEvent(uprobeOpts UProbeOpts) (*Event, error) {
   128  	uprobe, err := newUProbe(uprobeOpts)
   129  	if err != nil {
   130  		return nil, fmt.Errorf("uprobe: %w", err)
   131  	}
   132  
   133  	// TODO add CPU and PID options since they are allowed for uprobes to trace specific programs
   134  
   135  	// TODO using the debugfs and tracepoint type is apparently legacy, the new way to do it is using
   136  	//   dynamic PMU's. Couldn't get this to work, so in future figure it out and add as preferred method
   137  	//   and keep this one as fallback for older kernels
   138  
   139  	event, err := perfEventOpen(bpfSyscall.PerfEventAttr{
   140  		Type:   bpfSyscall.PERF_TYPE_TRACEPOINT,
   141  		Size:   bpfSyscall.AttrSize,
   142  		Config: uint64(uprobe.ID),
   143  	}, -1, 0, -1, bpfSyscall.PerfEventOpenFDCloseOnExit)
   144  	if err != nil {
   145  		return nil, fmt.Errorf("open perf event: %w", err)
   146  	}
   147  
   148  	event.uprobe = uprobe
   149  
   150  	return event, nil
   151  }
   152  
   153  // perfEventOpen is a wrapper around the perf_event_open syscall.
   154  func perfEventOpen(
   155  	attr bpfSyscall.PerfEventAttr,
   156  	pid,
   157  	cpu,
   158  	groupFD int,
   159  	flags bpfSyscall.PerfEventOpenFlags,
   160  ) (*Event, error) {
   161  	fd, err := bpfSyscall.PerfEventOpen(attr, pid, cpu, groupFD, flags)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	return &Event{
   167  		Type: attr.Type,
   168  		fd:   FD(fd),
   169  	}, nil
   170  }