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) } if err := haveBPFLinkKprobeMulti(); err != nil { return nil, err } 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 { 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 })