Skip to content

Commit

Permalink
link: implement uprobe_multi link type
Browse files Browse the repository at this point in the history
This commit adds support for this through the UprobeMulti() and
UretprobeMulti() APIs in package link.

Signed-off-by: Jiri Olsa <jolsa@kernel.org>
  • Loading branch information
olsajiri committed Dec 13, 2023
1 parent 6457734 commit 92f8d87
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 1 deletion.
1 change: 1 addition & 0 deletions internal/unix/types_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
BPF_F_MMAPABLE = linux.BPF_F_MMAPABLE
BPF_F_INNER_MAP = linux.BPF_F_INNER_MAP
BPF_F_KPROBE_MULTI_RETURN = linux.BPF_F_KPROBE_MULTI_RETURN
BPF_F_UPROBE_MULTI_RETURN = linux.BPF_F_UPROBE_MULTI_RETURN
BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
BPF_TAG_SIZE = linux.BPF_TAG_SIZE
BPF_RINGBUF_BUSY_BIT = linux.BPF_RINGBUF_BUSY_BIT
Expand Down
1 change: 1 addition & 0 deletions internal/unix/types_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
BPF_F_MMAPABLE
BPF_F_INNER_MAP
BPF_F_KPROBE_MULTI_RETURN
BPF_F_UPROBE_MULTI_RETURN
BPF_F_XDP_HAS_FRAGS
BPF_OBJ_NAME_LEN
BPF_TAG_SIZE
Expand Down
4 changes: 3 additions & 1 deletion link/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ func wrapRawLink(raw *RawLink) (_ Link, err error) {
return &NetNsLink{*raw}, nil
case KprobeMultiType:
return &kprobeMultiLink{*raw}, nil
case UprobeMultiType:
return &uprobeMultiLink{*raw}, nil
case PerfEventType:
return nil, fmt.Errorf("recovering perf event fd: %w", ErrNotSupported)
case TCXType:
Expand Down Expand Up @@ -316,7 +318,7 @@ func (l *RawLink) Info() (*Info, error) {
case XDPType:
extra = &XDPInfo{}
case RawTracepointType, IterType,
PerfEventType, KprobeMultiType:
PerfEventType, KprobeMultiType, UprobeMultiType:
// Extra metadata not supported.
case TCXType:
extra = &TCXInfo{}
Expand Down
1 change: 1 addition & 0 deletions link/syscalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
PerfEventType = sys.BPF_LINK_TYPE_PERF_EVENT
KprobeMultiType = sys.BPF_LINK_TYPE_KPROBE_MULTI
TCXType = sys.BPF_LINK_TYPE_TCX
UprobeMultiType = sys.BPF_LINK_TYPE_UPROBE_MULTI
)

var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", "4.10", func() error {
Expand Down
42 changes: 42 additions & 0 deletions link/uprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,48 @@ func (ex *Executable) load(f *internal.SafeELFFile) error {
return nil
}

// address calculates the address of a symbol in the executable.
//
// opts must not be nil.
func (ex *Executable) addressMulti(symbols []string) ([]uint64, error) {
var err error
ex.addressesOnce.Do(func() {
var f *internal.SafeELFFile
f, err = internal.OpenSafeELFFile(ex.path)
if err != nil {
err = fmt.Errorf("parse ELF file: %w", err)
return
}
defer f.Close()

err = ex.load(f)
})
if err != nil {
return []uint64{0}, fmt.Errorf("lazy load symbols: %w", err)
}

var addresses []uint64
for _, symbol := range symbols {
address, ok := ex.addresses[symbol]
if !ok {
return []uint64{0}, fmt.Errorf("symbol %s: %w", symbol, ErrNoSymbol)
}

// Symbols with location 0 from section undef are shared library calls and
// are relocated before the binary is executed. Dynamic linking is not
// implemented by the library, so mark this as unsupported for now.
//
// Since only offset values are stored and not elf.Symbol, if the value is 0,
// assume it's an external symbol.
if address == 0 {
return []uint64{0}, fmt.Errorf("cannot resolve %s library call '%s': %w "+
"(consider providing UprobeOptions.Address)", ex.path, symbol, ErrNotSupported)
}
addresses = append(addresses, address)
}
return addresses, nil
}

// address calculates the address of a symbol in the executable.
//
// opts must not be nil.
Expand Down
148 changes: 148 additions & 0 deletions link/uprobe_multi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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"
)

type UprobeMultiOptions struct {
Path string
Offsets []uint64
RefCtrOffsets []uint64
Cookies []uint64
}

func (ex *Executable) UprobeMulti(symbols []string, prog *ebpf.Program, opts *UprobeMultiOptions) (Link, error) {
if opts == nil {
opts = &UprobeMultiOptions{}
}
var err error
if len(symbols) != 0 {
opts.Offsets, err = ex.addressMulti(symbols)
if err != nil {
return nil, err
}
}
return ex.uprobeMulti(prog, opts, 0)
}

func (ex *Executable) UretprobeMulti(symbols []string, prog *ebpf.Program, opts *UprobeMultiOptions) (Link, error) {
return ex.uprobeMulti(prog, opts, unix.BPF_F_UPROBE_MULTI_RETURN)
}

func (ex *Executable) uprobeMulti(prog *ebpf.Program, opts *UprobeMultiOptions, flags uint32) (Link, error) {
if prog == nil {
return nil, errors.New("cannot attach a nil program")
}

offsets := uint32(len(opts.Offsets))
refCtrOffsets := uint32(len(opts.RefCtrOffsets))
cookies := uint32(len(opts.Cookies))

if offsets == 0 {
return nil, fmt.Errorf("Offsets is required: %w", errInvalidInput)
}
if refCtrOffsets > 0 && refCtrOffsets != offsets {
return nil, fmt.Errorf("RefCtrOffsets must be exactly Offsets in length: %w", errInvalidInput)
}
if cookies > 0 && cookies != offsets {
return nil, fmt.Errorf("Cookies must be exactly Offsets in length: %w", errInvalidInput)
}

attr := &sys.LinkCreateUprobeMultiAttr{
Path: sys.NewStringPointer(ex.path),
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_UPROBE_MULTI,
UprobeMultiFlags: flags,
Count: offsets,
Offsets: sys.NewPointer(unsafe.Pointer(&opts.Offsets[0])),
}

if refCtrOffsets != 0 {
attr.RefCtrOffsets = sys.NewPointer(unsafe.Pointer(&opts.RefCtrOffsets[0]))
}
if cookies != 0 {
attr.Cookies = sys.NewPointer(unsafe.Pointer(&opts.Cookies[0]))
}

fd, err := sys.LinkCreateUprobeMulti(attr)
if errors.Is(err, unix.ESRCH) {
return nil, fmt.Errorf("XXX: %w", os.ErrNotExist)
}
if errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("%w (missing kernel symbol or prog's AttachType not AttachTraceUprobeMulti?)", err)
}

if err != nil {
if haveFeatErr := haveBPFLinkUprobeMulti(); haveFeatErr != nil {
return nil, haveFeatErr
}
return nil, err
}

return &uprobeMultiLink{RawLink{fd, ""}}, nil
}

type uprobeMultiLink struct {
RawLink
}

var _ Link = (*uprobeMultiLink)(nil)

func (kml *uprobeMultiLink) Update(prog *ebpf.Program) error {
return fmt.Errorf("update uprobe_multi: %w", ErrNotSupported)
}

func (kml *uprobeMultiLink) Pin(string) error {
return fmt.Errorf("pin uprobe_multi: %w", ErrNotSupported)
}

func (kml *uprobeMultiLink) Unpin() error {
return fmt.Errorf("unpin uprobe_multi: %w", ErrNotSupported)
}

var haveBPFLinkUprobeMulti = internal.NewFeatureTest("bpf_link_uprobe_multi", "6.6", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Name: "probe_upm_link",
Type: ebpf.Kprobe,
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
AttachType: ebpf.AttachTraceUprobeMulti,
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.LinkCreateUprobeMulti(&sys.LinkCreateUprobeMultiAttr{
ProgFd: uint32(prog.FD()),
AttachType: sys.BPF_TRACE_UPROBE_MULTI,
Path: sys.NewStringPointer("/"),
Offsets: sys.NewPointer(unsafe.Pointer(&[]uint64{0})),
Count: 1,
})
switch {
case errors.Is(err, unix.EBADF):
return nil
case err != nil:
return internal.ErrNotSupported
}
// should not happen
fd.Close()
return internal.ErrNotSupported
})

0 comments on commit 92f8d87

Please sign in to comment.