diff --git a/vendor/github.com/cilium/ebpf/link/cgroup.go b/vendor/github.com/cilium/ebpf/link/cgroup.go index 16a943930..5540bb068 100644 --- a/vendor/github.com/cilium/ebpf/link/cgroup.go +++ b/vendor/github.com/cilium/ebpf/link/cgroup.go @@ -57,13 +57,13 @@ func AttachCgroup(opts CgroupOptions) (Link, error) { } // LoadPinnedCgroup loads a pinned cgroup from a bpffs. -func LoadPinnedCgroup(fileName string) (Link, error) { - link, err := LoadPinnedRawLink(fileName) +func LoadPinnedCgroup(fileName string, opts *ebpf.LoadPinOptions) (Link, error) { + link, err := LoadPinnedRawLink(fileName, CgroupType, opts) if err != nil { return nil, err } - return &linkCgroup{link}, nil + return &linkCgroup{*link}, nil } type progAttachCgroup struct { @@ -147,14 +147,16 @@ func (cg *progAttachCgroup) Pin(string) error { return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported) } +func (cg *progAttachCgroup) Unpin() error { + return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported) +} + type linkCgroup struct { - *RawLink + RawLink } var _ Link = (*linkCgroup)(nil) -func (cg *linkCgroup) isLink() {} - func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) (*linkCgroup, error) { link, err := AttachRawLink(RawLinkOptions{ Target: int(cgroup.Fd()), @@ -165,5 +167,5 @@ func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) return nil, err } - return &linkCgroup{link}, err + return &linkCgroup{*link}, err } diff --git a/vendor/github.com/cilium/ebpf/link/iter.go b/vendor/github.com/cilium/ebpf/link/iter.go index 2b5f2846a..04d24ef35 100644 --- a/vendor/github.com/cilium/ebpf/link/iter.go +++ b/vendor/github.com/cilium/ebpf/link/iter.go @@ -27,53 +27,29 @@ func AttachIter(opts IterOptions) (*Iter, error) { return nil, fmt.Errorf("can't link iterator: %w", err) } - return &Iter{link}, err + return &Iter{*link}, err } // LoadPinnedIter loads a pinned iterator from a bpffs. -func LoadPinnedIter(fileName string) (*Iter, error) { - link, err := LoadPinnedRawLink(fileName) +func LoadPinnedIter(fileName string, opts *ebpf.LoadPinOptions) (*Iter, error) { + link, err := LoadPinnedRawLink(fileName, IterType, opts) if err != nil { return nil, err } - return &Iter{link}, err + return &Iter{*link}, err } // Iter represents an attached bpf_iter. type Iter struct { - link *RawLink -} - -var _ Link = (*Iter)(nil) - -func (it *Iter) isLink() {} - -// FD returns the underlying file descriptor. -func (it *Iter) FD() int { - return it.link.FD() -} - -// Close implements Link. -func (it *Iter) Close() error { - return it.link.Close() -} - -// Pin implements Link. -func (it *Iter) Pin(fileName string) error { - return it.link.Pin(fileName) -} - -// Update implements Link. -func (it *Iter) Update(new *ebpf.Program) error { - return it.link.Update(new) + RawLink } // Open creates a new instance of the iterator. // // Reading from the returned reader triggers the BPF program. func (it *Iter) Open() (io.ReadCloser, error) { - linkFd, err := it.link.fd.Value() + linkFd, err := it.fd.Value() if err != nil { return nil, err } diff --git a/vendor/github.com/cilium/ebpf/link/kprobe.go b/vendor/github.com/cilium/ebpf/link/kprobe.go new file mode 100644 index 000000000..ad3a5949a --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/kprobe.go @@ -0,0 +1,296 @@ +package link + +import ( + "crypto/rand" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/unix" +) + +var ( + kprobeEventsPath = filepath.Join(tracefsPath, "kprobe_events") +) + +// Kprobe attaches the given eBPF program to a perf event that fires when the +// given kernel symbol starts executing. See /proc/kallsyms for available +// symbols. For example, printk(): +// +// Kprobe("printk") +// +// The resulting Link must be Closed during program shutdown to avoid leaking +// system resources. +func Kprobe(symbol string, prog *ebpf.Program) (Link, error) { + k, err := kprobe(symbol, prog, false) + if err != nil { + return nil, err + } + + err = k.attach(prog) + if err != nil { + k.Close() + return nil, err + } + + return k, nil +} + +// Kretprobe attaches the given eBPF program to a perf event that fires right +// before the given kernel symbol exits, with the function stack left intact. +// See /proc/kallsyms for available symbols. For example, printk(): +// +// Kretprobe("printk") +// +// The resulting Link must be Closed during program shutdown to avoid leaking +// system resources. +func Kretprobe(symbol string, prog *ebpf.Program) (Link, error) { + k, err := kprobe(symbol, prog, true) + if err != nil { + return nil, err + } + + err = k.attach(prog) + if err != nil { + k.Close() + return nil, err + } + + return k, nil +} + +// kprobe opens a perf event on the given symbol and attaches prog to it. +// If ret is true, create a kretprobe. +func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) { + if symbol == "" { + return nil, fmt.Errorf("symbol name cannot be empty: %w", errInvalidInput) + } + if prog == nil { + return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput) + } + if !rgxTraceEvent.MatchString(symbol) { + return nil, fmt.Errorf("symbol '%s' must be alphanumeric or underscore: %w", symbol, errInvalidInput) + } + if prog.Type() != ebpf.Kprobe { + return nil, fmt.Errorf("eBPF program type %s is not a Kprobe: %w", prog.Type(), errInvalidInput) + } + + // Use kprobe PMU if the kernel has it available. + tp, err := pmuKprobe(symbol, ret) + if err == nil { + return tp, nil + } + if err != nil && !errors.Is(err, ErrNotSupported) { + return nil, fmt.Errorf("creating perf_kprobe PMU: %w", err) + } + + // Use tracefs if kprobe PMU is missing. + tp, err = tracefsKprobe(symbol, ret) + if err != nil { + return nil, fmt.Errorf("creating trace event '%s' in tracefs: %w", symbol, err) + } + + return tp, nil +} + +// pmuKprobe opens a perf event based on a Performance Monitoring Unit. +// Requires at least 4.17 (e12f03d7031a "perf/core: Implement the +// 'perf_kprobe' PMU"). +// Returns ErrNotSupported if the kernel doesn't support perf_kprobe PMU, +// or os.ErrNotExist if the given symbol does not exist in the kernel. +func pmuKprobe(symbol string, ret bool) (*perfEvent, error) { + + // Getting the PMU type will fail if the kernel doesn't support + // the perf_kprobe PMU. + et, err := getPMUEventType("kprobe") + if err != nil { + return nil, err + } + + // Create a pointer to a NUL-terminated string for the kernel. + sp, err := unsafeStringPtr(symbol) + if err != nil { + return nil, err + } + + // TODO: Parse the position of the bit from /sys/bus/event_source/devices/%s/format/retprobe. + config := 0 + if ret { + config = 1 + } + + attr := unix.PerfEventAttr{ + Type: uint32(et), // PMU event type read from sysfs + Ext1: uint64(uintptr(sp)), // Kernel symbol to trace + Config: uint64(config), // perf_kprobe PMU treats config as flags + } + + fd, err := unix.PerfEventOpen(&attr, perfAllThreads, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) + + // Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL + // when trying to create a kretprobe for a missing symbol. Make sure ENOENT + // is returned to the caller. + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) { + return nil, fmt.Errorf("symbol '%s' not found: %w", symbol, os.ErrNotExist) + } + if err != nil { + return nil, fmt.Errorf("opening perf event: %w", err) + } + + // Ensure the string pointer is not collected before PerfEventOpen returns. + runtime.KeepAlive(sp) + + // Kernel has perf_kprobe PMU available, initialize perf event. + return &perfEvent{ + fd: internal.NewFD(uint32(fd)), + pmuID: et, + name: symbol, + ret: ret, + progType: ebpf.Kprobe, + }, nil +} + +// tracefsKprobe creates a trace event by writing an entry to /kprobe_events. +// A new trace event group name is generated on every call to support creating +// multiple trace events for the same kernel symbol. A perf event is then opened +// on the newly-created trace event and returned to the caller. +func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) { + + // Generate a random string for each trace event we attempt to create. + // This value is used as the 'group' token in tracefs to allow creating + // multiple kprobe trace events with the same name. + group, err := randomGroup("ebpf") + if err != nil { + return nil, fmt.Errorf("randomizing group name: %w", err) + } + + // Before attempting to create a trace event through tracefs, + // check if an event with the same group and name already exists. + // Kernels 4.x and earlier don't return os.ErrExist on writing a duplicate + // entry, so we need to rely on reads for detecting uniqueness. + _, err = getTraceEventID(group, symbol) + if err == nil { + return nil, fmt.Errorf("trace event already exists: %s/%s", group, symbol) + } + // The read is expected to fail with ErrNotSupported due to a non-existing event. + if err != nil && !errors.Is(err, ErrNotSupported) { + return nil, fmt.Errorf("checking trace event %s/%s: %w", group, symbol, err) + } + + // Create the kprobe trace event using tracefs. + if err := createTraceFSKprobeEvent(group, symbol, ret); err != nil { + return nil, fmt.Errorf("creating kprobe event on tracefs: %w", err) + } + + // Get the newly-created trace event's id. + tid, err := getTraceEventID(group, symbol) + if err != nil { + return nil, fmt.Errorf("getting trace event id: %w", err) + } + + // Kprobes are ephemeral tracepoints and share the same perf event type. + fd, err := openTracepointPerfEvent(tid) + if err != nil { + return nil, err + } + + return &perfEvent{ + fd: fd, + group: group, + name: symbol, + ret: ret, + tracefsID: tid, + progType: ebpf.Kprobe, // kernel only allows attaching kprobe programs to kprobe events + }, nil +} + +// createTraceFSKprobeEvent creates a new ephemeral trace event by writing to +// /kprobe_events. Returns ErrNotSupported if symbol is not a valid +// kernel symbol, or if it is not traceable with kprobes. +func createTraceFSKprobeEvent(group, symbol string, ret bool) error { + // Open the kprobe_events file in tracefs. + f, err := os.OpenFile(kprobeEventsPath, os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return fmt.Errorf("error opening kprobe_events: %w", err) + } + defer f.Close() + + // The kprobe_events syntax is as follows (see Documentation/trace/kprobetrace.txt): + // p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe + // r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe + // -:[GRP/]EVENT : Clear a probe + // + // Some examples: + // r:ebpf_1234/r_my_kretprobe nf_conntrack_destroy + // p:ebpf_5678/p_my_kprobe __x64_sys_execve + // + // Leaving the kretprobe's MAXACTIVE set to 0 (or absent) will make the + // kernel default to NR_CPUS. This is desired in most eBPF cases since + // subsampling or rate limiting logic can be more accurately implemented in + // the eBPF program itself. See Documentation/kprobes.txt for more details. + pe := fmt.Sprintf("%s:%s/%s %s", kprobePrefix(ret), group, symbol, symbol) + _, err = f.WriteString(pe) + // Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL + // when trying to create a kretprobe for a missing symbol. Make sure ENOENT + // is returned to the caller. + if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) { + return fmt.Errorf("kernel symbol %s not found: %w", symbol, os.ErrNotExist) + } + if err != nil { + return fmt.Errorf("writing '%s' to kprobe_events: %w", pe, err) + } + + return nil +} + +// closeTraceFSKprobeEvent removes the kprobe with the given group, symbol and kind +// from /kprobe_events. +func closeTraceFSKprobeEvent(group, symbol string) error { + f, err := os.OpenFile(kprobeEventsPath, os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return fmt.Errorf("error opening kprobe_events: %w", err) + } + defer f.Close() + + // See kprobe_events syntax above. Kprobe type does not need to be specified + // for removals. + pe := fmt.Sprintf("-:%s/%s", group, symbol) + if _, err = f.WriteString(pe); err != nil { + return fmt.Errorf("writing '%s' to kprobe_events: %w", pe, err) + } + + return nil +} + +// randomGroup generates a pseudorandom string for use as a tracefs group name. +// Returns an error when the output string would exceed 63 characters (kernel +// limitation), when rand.Read() fails or when prefix contains characters not +// allowed by rgxTraceEvent. +func randomGroup(prefix string) (string, error) { + if !rgxTraceEvent.MatchString(prefix) { + return "", fmt.Errorf("prefix '%s' must be alphanumeric or underscore: %w", prefix, errInvalidInput) + } + + b := make([]byte, 8) + if _, err := rand.Read(b); err != nil { + return "", fmt.Errorf("reading random bytes: %w", err) + } + + group := fmt.Sprintf("%s_%x", prefix, b) + if len(group) > 63 { + return "", fmt.Errorf("group name '%s' cannot be longer than 63 characters: %w", group, errInvalidInput) + } + + return group, nil +} + +func kprobePrefix(ret bool) string { + if ret { + return "r" + } + return "p" +} diff --git a/vendor/github.com/cilium/ebpf/link/link.go b/vendor/github.com/cilium/ebpf/link/link.go index 48f1a5529..16cfff415 100644 --- a/vendor/github.com/cilium/ebpf/link/link.go +++ b/vendor/github.com/cilium/ebpf/link/link.go @@ -22,6 +22,11 @@ type Link interface { // May return an error wrapping ErrNotSupported. Pin(string) error + // Undo a previous call to Pin. + // + // May return an error wrapping ErrNotSupported. + Unpin() error + // Close frees resources. // // The link will be broken unless it has been pinned. A link @@ -58,7 +63,8 @@ type RawLinkInfo struct { // You should consider using the higher level interfaces in this // package instead. type RawLink struct { - fd *internal.FD + fd *internal.FD + pinnedPath string } // AttachRawLink creates a raw link. @@ -86,22 +92,21 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) { return nil, fmt.Errorf("can't create link: %s", err) } - return &RawLink{fd}, nil + return &RawLink{fd, ""}, nil } // LoadPinnedRawLink loads a persisted link from a bpffs. -func LoadPinnedRawLink(fileName string) (*RawLink, error) { - return loadPinnedRawLink(fileName, UnspecifiedType) -} - -func loadPinnedRawLink(fileName string, typ Type) (*RawLink, error) { - fd, err := internal.BPFObjGet(fileName) +// +// Returns an error if the pinned link type doesn't match linkType. Pass +// UnspecifiedType to disable this behaviour. +func LoadPinnedRawLink(fileName string, linkType Type, opts *ebpf.LoadPinOptions) (*RawLink, error) { + fd, err := internal.BPFObjGet(fileName, opts.Marshal()) if err != nil { - return nil, fmt.Errorf("load pinned link: %s", err) + return nil, fmt.Errorf("load pinned link: %w", err) } - link := &RawLink{fd} - if typ == UnspecifiedType { + link := &RawLink{fd, fileName} + if linkType == UnspecifiedType { return link, nil } @@ -111,9 +116,9 @@ func loadPinnedRawLink(fileName string, typ Type) (*RawLink, error) { return nil, fmt.Errorf("get pinned link info: %s", err) } - if info.Type != typ { + if info.Type != linkType { link.Close() - return nil, fmt.Errorf("link type %v doesn't match %v", info.Type, typ) + return nil, fmt.Errorf("link type %v doesn't match %v", info.Type, linkType) } return link, nil @@ -142,13 +147,23 @@ func (l *RawLink) Close() error { // Calling Close on a pinned Link will not break the link // until the pin is removed. func (l *RawLink) Pin(fileName string) error { - if err := internal.BPFObjPin(fileName, l.fd); err != nil { - return fmt.Errorf("can't pin link: %s", err) + if err := internal.Pin(l.pinnedPath, fileName, l.fd); err != nil { + return err } + l.pinnedPath = fileName return nil } -// Update implements Link. +// Unpin implements the Link interface. +func (l *RawLink) Unpin() error { + if err := internal.Unpin(l.pinnedPath); err != nil { + return err + } + l.pinnedPath = "" + return nil +} + +// Update implements the Link interface. func (l *RawLink) Update(new *ebpf.Program) error { return l.UpdateArgs(RawLinkUpdateOptions{ New: new, diff --git a/vendor/github.com/cilium/ebpf/link/netns.go b/vendor/github.com/cilium/ebpf/link/netns.go index 3533ff0fa..37e5b84c4 100644 --- a/vendor/github.com/cilium/ebpf/link/netns.go +++ b/vendor/github.com/cilium/ebpf/link/netns.go @@ -41,8 +41,8 @@ func AttachNetNs(ns int, prog *ebpf.Program) (*NetNsLink, error) { } // LoadPinnedNetNs loads a network namespace link from bpffs. -func LoadPinnedNetNs(fileName string) (*NetNsLink, error) { - link, err := loadPinnedRawLink(fileName, NetNsType) +func LoadPinnedNetNs(fileName string, opts *ebpf.LoadPinOptions) (*NetNsLink, error) { + link, err := LoadPinnedRawLink(fileName, NetNsType, opts) if err != nil { return nil, err } diff --git a/vendor/github.com/cilium/ebpf/link/perf_event.go b/vendor/github.com/cilium/ebpf/link/perf_event.go new file mode 100644 index 000000000..de973c741 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/perf_event.go @@ -0,0 +1,253 @@ +package link + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + "unsafe" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal" + "github.com/cilium/ebpf/internal/unix" +) + +// Getting the terminology right is usually the hardest part. For posterity and +// for staying sane during implementation: +// +// - trace event: Representation of a kernel runtime hook. Filesystem entries +// under /events. Can be tracepoints (static), kprobes or uprobes. +// Can be instantiated into perf events (see below). +// - tracepoint: A predetermined hook point in the kernel. Exposed as trace +// events in (sub)directories under /events. Cannot be closed or +// removed, they are static. +// - k(ret)probe: Ephemeral trace events based on entry or exit points of +// exported kernel symbols. kprobe-based (tracefs) trace events can be +// created system-wide by writing to the /kprobe_events file, or +// they can be scoped to the current process by creating PMU perf events. +// - perf event: An object instantiated based on an existing trace event or +// kernel symbol. Referred to by fd in userspace. +// Exactly one eBPF program can be attached to a perf event. Multiple perf +// events can be created from a single trace event. Closing a perf event +// stops any further invocations of the attached eBPF program. + +var ( + tracefsPath = "/sys/kernel/debug/tracing" + + // Trace event groups, names and kernel symbols must adhere to this set + // of characters. Non-empty, first character must not be a number, all + // characters must be alphanumeric or underscore. + rgxTraceEvent = regexp.MustCompile("^[a-zA-Z_][0-9a-zA-Z_]*$") + + errInvalidInput = errors.New("invalid input") +) + +const ( + perfAllThreads = -1 +) + +// A perfEvent represents a perf event kernel object. Exactly one eBPF program +// can be attached to it. It is created based on a tracefs trace event or a +// Performance Monitoring Unit (PMU). +type perfEvent struct { + + // Group and name of the tracepoint/kprobe/uprobe. + group string + name string + + // PMU event ID read from sysfs. Valid IDs are non-zero. + pmuID uint64 + // ID of the trace event read from tracefs. Valid IDs are non-zero. + tracefsID uint64 + + // True for kretprobes/uretprobes. + ret bool + + fd *internal.FD + progType ebpf.ProgramType +} + +func (pe *perfEvent) isLink() {} + +func (pe *perfEvent) Pin(string) error { + return fmt.Errorf("pin perf event: %w", ErrNotSupported) +} + +func (pe *perfEvent) Unpin() error { + return fmt.Errorf("unpin perf event: %w", ErrNotSupported) +} + +// Since 4.15 (e87c6bc3852b "bpf: permit multiple bpf attachments for a single perf event"), +// calling PERF_EVENT_IOC_SET_BPF appends the given program to a prog_array +// owned by the perf event, which means multiple programs can be attached +// simultaneously. +// +// Before 4.15, calling PERF_EVENT_IOC_SET_BPF more than once on a perf event +// returns EEXIST. +// +// Detaching a program from a perf event is currently not possible, so a +// program replacement mechanism cannot be implemented for perf events. +func (pe *perfEvent) Update(prog *ebpf.Program) error { + return fmt.Errorf("can't replace eBPF program in perf event: %w", ErrNotSupported) +} + +func (pe *perfEvent) Close() error { + if pe.fd == nil { + return nil + } + + pfd, err := pe.fd.Value() + if err != nil { + return fmt.Errorf("getting perf event fd: %w", err) + } + + err = unix.IoctlSetInt(int(pfd), unix.PERF_EVENT_IOC_DISABLE, 0) + if err != nil { + return fmt.Errorf("disabling perf event: %w", err) + } + + err = pe.fd.Close() + if err != nil { + return fmt.Errorf("closing perf event fd: %w", err) + } + + switch t := pe.progType; t { + case ebpf.Kprobe: + // For kprobes created using tracefs, clean up the /kprobe_events entry. + if pe.tracefsID != 0 { + return closeTraceFSKprobeEvent(pe.group, pe.name) + } + case ebpf.TracePoint: + // Tracepoint trace events don't hold any extra resources. + return nil + } + + return nil +} + +// attach the given eBPF prog to the perf event stored in pe. +// pe must contain a valid perf event fd. +// prog's type must match the program type stored in pe. +func (pe *perfEvent) attach(prog *ebpf.Program) error { + if prog == nil { + return errors.New("cannot attach a nil program") + } + if pe.fd == nil { + return errors.New("cannot attach to nil perf event") + } + if t := prog.Type(); t != pe.progType { + return fmt.Errorf("invalid program type (expected %s): %s", pe.progType, t) + } + if prog.FD() < 0 { + return fmt.Errorf("invalid program: %w", internal.ErrClosedFd) + } + + // The ioctl below will fail when the fd is invalid. + kfd, _ := pe.fd.Value() + + // Assign the eBPF program to the perf event. + err := unix.IoctlSetInt(int(kfd), unix.PERF_EVENT_IOC_SET_BPF, prog.FD()) + if err != nil { + return fmt.Errorf("setting perf event bpf program: %w", err) + } + + // PERF_EVENT_IOC_ENABLE and _DISABLE ignore their given values. + if err := unix.IoctlSetInt(int(kfd), unix.PERF_EVENT_IOC_ENABLE, 0); err != nil { + return fmt.Errorf("enable perf event: %s", err) + } + + // Close the perf event when its reference is lost to avoid leaking system resources. + runtime.SetFinalizer(pe, (*perfEvent).Close) + return nil +} + +// unsafeStringPtr returns an unsafe.Pointer to a NUL-terminated copy of str. +func unsafeStringPtr(str string) (unsafe.Pointer, error) { + p, err := unix.BytePtrFromString(str) + if err != nil { + return nil, err + } + return unsafe.Pointer(p), nil +} + +// getTraceEventID reads a trace event's ID from tracefs given its group and name. +// group and name must be alphanumeric or underscore, as required by the kernel. +func getTraceEventID(group, name string) (uint64, error) { + tid, err := uint64FromFile(tracefsPath, "events", group, name, "id") + if errors.Is(err, ErrNotSupported) { + return 0, fmt.Errorf("trace event %s/%s: %w", group, name, ErrNotSupported) + } + if err != nil { + return 0, fmt.Errorf("reading trace event ID of %s/%s: %w", group, name, err) + } + + return tid, nil +} + +// getPMUEventType reads a Performance Monitoring Unit's type (numeric identifier) +// from /sys/bus/event_source/devices//type. +func getPMUEventType(pmu string) (uint64, error) { + et, err := uint64FromFile("/sys/bus/event_source/devices", pmu, "type") + if errors.Is(err, ErrNotSupported) { + return 0, fmt.Errorf("pmu type %s: %w", pmu, ErrNotSupported) + } + if err != nil { + return 0, fmt.Errorf("reading pmu type %s: %w", pmu, err) + } + + return et, nil +} + +// openTracepointPerfEvent opens a tracepoint-type perf event. System-wide +// kprobes created by writing to /kprobe_events are tracepoints +// behind the scenes, and can be attached to using these perf events. +func openTracepointPerfEvent(tid uint64) (*internal.FD, error) { + attr := unix.PerfEventAttr{ + Type: unix.PERF_TYPE_TRACEPOINT, + Config: tid, + Sample_type: unix.PERF_SAMPLE_RAW, + Sample: 1, + Wakeup: 1, + } + + fd, err := unix.PerfEventOpen(&attr, perfAllThreads, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) + if err != nil { + return nil, fmt.Errorf("opening tracepoint perf event: %w", err) + } + + return internal.NewFD(uint32(fd)), nil +} + +// uint64FromFile reads a uint64 from a file. All elements of path are sanitized +// and joined onto base. Returns error if base no longer prefixes the path after +// joining all components. +func uint64FromFile(base string, path ...string) (uint64, error) { + + // Resolve leaf path separately for error feedback. Makes the join onto + // base more readable (can't mix with variadic args). + l := filepath.Join(path...) + + p := filepath.Join(base, l) + if !strings.HasPrefix(p, base) { + return 0, fmt.Errorf("path '%s' attempts to escape base path '%s': %w", l, base, errInvalidInput) + } + + data, err := ioutil.ReadFile(p) + if os.IsNotExist(err) { + // Only echo leaf path, the base path can be prepended at the call site + // if more verbosity is required. + return 0, fmt.Errorf("symbol %s: %w", l, ErrNotSupported) + } + if err != nil { + return 0, fmt.Errorf("reading file %s: %w", p, err) + } + + et := bytes.TrimSpace(data) + return strconv.ParseUint(string(et), 10, 64) +} diff --git a/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go b/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go index 65652486f..f4beb1e07 100644 --- a/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go +++ b/vendor/github.com/cilium/ebpf/link/raw_tracepoint.go @@ -55,3 +55,7 @@ func (rt *progAttachRawTracepoint) Update(_ *ebpf.Program) error { func (rt *progAttachRawTracepoint) Pin(_ string) error { return fmt.Errorf("can't pin raw_tracepoint: %w", ErrNotSupported) } + +func (rt *progAttachRawTracepoint) Unpin() error { + return fmt.Errorf("unpin raw_tracepoint: %w", ErrNotSupported) +} diff --git a/vendor/github.com/cilium/ebpf/link/tracepoint.go b/vendor/github.com/cilium/ebpf/link/tracepoint.go new file mode 100644 index 000000000..588466b9d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/link/tracepoint.go @@ -0,0 +1,56 @@ +package link + +import ( + "fmt" + + "github.com/cilium/ebpf" +) + +// Tracepoint attaches the given eBPF program to the tracepoint with the given +// group and name. See /sys/kernel/debug/tracing/events to find available +// tracepoints. The top-level directory is the group, the event's subdirectory +// is the name. Example: +// +// Tracepoint("syscalls", "sys_enter_fork") +// +// Note that attaching eBPF programs to syscalls (sys_enter_*/sys_exit_*) is +// only possible as of kernel 4.14 (commit cf5f5ce). +func Tracepoint(group, name string, prog *ebpf.Program) (Link, error) { + if group == "" || name == "" { + return nil, fmt.Errorf("group and name cannot be empty: %w", errInvalidInput) + } + if prog == nil { + return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput) + } + if !rgxTraceEvent.MatchString(group) || !rgxTraceEvent.MatchString(name) { + return nil, fmt.Errorf("group and name '%s/%s' must be alphanumeric or underscore: %w", group, name, errInvalidInput) + } + if prog.Type() != ebpf.TracePoint { + return nil, fmt.Errorf("eBPF program type %s is not a Tracepoint: %w", prog.Type(), errInvalidInput) + } + + tid, err := getTraceEventID(group, name) + if err != nil { + return nil, err + } + + fd, err := openTracepointPerfEvent(tid) + if err != nil { + return nil, err + } + + pe := &perfEvent{ + fd: fd, + tracefsID: tid, + group: group, + name: name, + progType: ebpf.TracePoint, + } + + if err := pe.attach(prog); err != nil { + pe.Close() + return nil, err + } + + return pe, nil +}