| package link |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| "unsafe" |
| |
| "github.com/cilium/ebpf" |
| "github.com/cilium/ebpf/asm" |
| "github.com/cilium/ebpf/internal" |
| "github.com/cilium/ebpf/internal/sys" |
| "github.com/cilium/ebpf/internal/unix" |
| ) |
| |
| // KprobeMultiOptions defines additional parameters that will be used |
| // when opening a KprobeMulti Link. |
| type KprobeMultiOptions struct { |
| // Symbols takes a list of kernel symbol names to attach an ebpf program to. |
| // |
| // Mutually exclusive with Addresses. |
| Symbols []string |
| |
| // Addresses takes a list of kernel symbol addresses in case they can not |
| // be referred to by name. |
| // |
| // Note that only start addresses can be specified, since the fprobe API |
| // limits the attach point to the function entry or return. |
| // |
| // Mutually exclusive with Symbols. |
| Addresses []uintptr |
| |
| // Cookies specifies arbitrary values that can be fetched from an eBPF |
| // program via `bpf_get_attach_cookie()`. |
| // |
| // If set, its length should be equal to the length of Symbols or Addresses. |
| // Each Cookie is assigned to the Symbol or Address specified at the |
| // corresponding slice index. |
| Cookies []uint64 |
| } |
| |
| // KprobeMulti attaches the given eBPF program to the entry point of a given set |
| // of kernel symbols. |
| // |
| // The difference with Kprobe() is that multi-kprobe accomplishes this in a |
| // single system call, making it significantly faster than attaching many |
| // probes one at a time. |
| // |
| // Requires at least Linux 5.18. |
| func KprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions) (Link, error) { |
| return kprobeMulti(prog, opts, 0) |
| } |
| |
| // KretprobeMulti attaches the given eBPF program to the return point of a given |
| // set of kernel symbols. |
| // |
| // The difference with Kretprobe() is that multi-kprobe accomplishes this in a |
| // single system call, making it significantly faster than attaching many |
| // probes one at a time. |
| // |
| // Requires at least Linux 5.18. |
| func KretprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions) (Link, error) { |
| return kprobeMulti(prog, opts, unix.BPF_F_KPROBE_MULTI_RETURN) |
| } |
| |
| func kprobeMulti(prog *ebpf.Program, opts KprobeMultiOptions, flags uint32) (Link, error) { |
| if prog == nil { |
| return nil, errors.New("cannot attach a nil program") |
| } |
| |
| syms := uint32(len(opts.Symbols)) |
| addrs := uint32(len(opts.Addresses)) |
| cookies := uint32(len(opts.Cookies)) |
| |
| if syms == 0 && addrs == 0 { |
| return nil, fmt.Errorf("one of Symbols or Addresses is required: %w", errInvalidInput) |
| } |
| if syms != 0 && addrs != 0 { |
| return nil, fmt.Errorf("Symbols and Addresses are mutually exclusive: %w", errInvalidInput) |
| } |
| if cookies > 0 && cookies != syms && cookies != addrs { |
| return nil, fmt.Errorf("Cookies must be exactly Symbols or Addresses in length: %w", errInvalidInput) |
| } |
| |
| attr := &sys.LinkCreateKprobeMultiAttr{ |
| ProgFd: uint32(prog.FD()), |
| AttachType: sys.BPF_TRACE_KPROBE_MULTI, |
| KprobeMultiFlags: flags, |
| } |
| |
| switch { |
| case syms != 0: |
| attr.Count = syms |
| attr.Syms = sys.NewStringSlicePointer(opts.Symbols) |
| |
| case addrs != 0: |
| attr.Count = addrs |
| attr.Addrs = sys.NewPointer(unsafe.Pointer(&opts.Addresses[0])) |
| } |
| |
| if cookies != 0 { |
| attr.Cookies = sys.NewPointer(unsafe.Pointer(&opts.Cookies[0])) |
| } |
| |
| fd, err := sys.LinkCreateKprobeMulti(attr) |
| if errors.Is(err, unix.ESRCH) { |
| return nil, fmt.Errorf("couldn't find one or more symbols: %w", os.ErrNotExist) |
| } |
| if errors.Is(err, unix.EINVAL) { |
| return nil, fmt.Errorf("%w (missing kernel symbol or prog's AttachType not AttachTraceKprobeMulti?)", err) |
| } |
| |
| if err != nil { |
| if haveFeatErr := haveBPFLinkKprobeMulti(); haveFeatErr != nil { |
| return nil, haveFeatErr |
| } |
| return nil, err |
| } |
| |
| return &kprobeMultiLink{RawLink{fd, ""}}, nil |
| } |
| |
| type kprobeMultiLink struct { |
| RawLink |
| } |
| |
| var _ Link = (*kprobeMultiLink)(nil) |
| |
| func (kml *kprobeMultiLink) Update(prog *ebpf.Program) error { |
| return fmt.Errorf("update kprobe_multi: %w", ErrNotSupported) |
| } |
| |
| func (kml *kprobeMultiLink) Pin(string) error { |
| return fmt.Errorf("pin kprobe_multi: %w", ErrNotSupported) |
| } |
| |
| func (kml *kprobeMultiLink) Unpin() error { |
| return fmt.Errorf("unpin kprobe_multi: %w", ErrNotSupported) |
| } |
| |
| var haveBPFLinkKprobeMulti = internal.NewFeatureTest("bpf_link_kprobe_multi", "5.18", func() error { |
| prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ |
| Name: "probe_kpm_link", |
| Type: ebpf.Kprobe, |
| Instructions: asm.Instructions{ |
| asm.Mov.Imm(asm.R0, 0), |
| asm.Return(), |
| }, |
| AttachType: ebpf.AttachTraceKprobeMulti, |
| License: "MIT", |
| }) |
| if errors.Is(err, unix.E2BIG) { |
| // Kernel doesn't support AttachType field. |
| return internal.ErrNotSupported |
| } |
| if err != nil { |
| return err |
| } |
| defer prog.Close() |
| |
| fd, err := sys.LinkCreateKprobeMulti(&sys.LinkCreateKprobeMultiAttr{ |
| ProgFd: uint32(prog.FD()), |
| AttachType: sys.BPF_TRACE_KPROBE_MULTI, |
| Count: 1, |
| Syms: sys.NewStringSlicePointer([]string{"vprintk"}), |
| }) |
| switch { |
| case errors.Is(err, unix.EINVAL): |
| return internal.ErrNotSupported |
| // If CONFIG_FPROBE isn't set. |
| case errors.Is(err, unix.EOPNOTSUPP): |
| return internal.ErrNotSupported |
| case err != nil: |
| return err |
| } |
| |
| fd.Close() |
| |
| return nil |
| }) |