Skip to content

Commit

Permalink
link: support bpf_cookie for any perf-event backed link (k,uprobes, t…
Browse files Browse the repository at this point in the history
…racepoints)

Signed-off-by: Mattia Meleleo <mattia.meleleo@elastic.co>
  • Loading branch information
mmat11 committed Feb 11, 2022
1 parent 4023616 commit 4862079
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 36 deletions.
49 changes: 31 additions & 18 deletions link/kprobe.go
Expand Up @@ -29,10 +29,20 @@ var (
type probeType uint8

type probeArgs struct {
symbol, group, path string
offset, refCtrOffset uint64
pid int
ret bool
symbol, group, path string
offset, refCtrOffset, bpfCookie uint64
pid int
ret bool
}

// KprobeOptions defines additional parameters that will be used
// when loading Kprobes.
type KprobeOptions struct {
// Arbitrary value that can be fetched from an eBPF program
// via `bpf_get_attach_cookie()`.
//
// Needs kernel 5.15+.
BpfCookie uint64
}

const (
Expand Down Expand Up @@ -78,13 +88,13 @@ func (pt probeType) RetprobeBit() (uint64, error) {
// given kernel symbol starts executing. See /proc/kallsyms for available
// symbols. For example, printk():
//
// kp, err := Kprobe("printk", prog)
// kp, err := Kprobe("printk", prog, nil)
//
// Losing the reference to the resulting Link (kp) will close the Kprobe
// and prevent further execution of prog. The 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)
func Kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
k, err := kprobe(symbol, prog, opts, false)
if err != nil {
return nil, err
}
Expand All @@ -102,13 +112,13 @@ func Kprobe(symbol string, prog *ebpf.Program) (Link, error) {
// before the given kernel symbol exits, with the function stack left intact.
// See /proc/kallsyms for available symbols. For example, printk():
//
// kp, err := Kretprobe("printk", prog)
// kp, err := Kretprobe("printk", prog, nil)
//
// Losing the reference to the resulting Link (kp) will close the Kretprobe
// and prevent further execution of prog. The 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)
func Kretprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions) (Link, error) {
k, err := kprobe(symbol, prog, opts, true)
if err != nil {
return nil, err
}
Expand All @@ -124,7 +134,7 @@ func Kretprobe(symbol string, prog *ebpf.Program) (Link, error) {

// 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) {
func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*perfEvent, error) {
if symbol == "" {
return nil, fmt.Errorf("symbol name cannot be empty: %w", errInvalidInput)
}
Expand All @@ -139,9 +149,10 @@ func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
}

args := probeArgs{
pid: perfAllThreads,
symbol: platformPrefix(symbol),
ret: ret,
pid: perfAllThreads,
symbol: platformPrefix(symbol),
ret: ret,
bpfCookie: opts.BpfCookie,
}

// Use kprobe PMU if the kernel has it available.
Expand Down Expand Up @@ -268,10 +279,11 @@ func pmuProbe(typ probeType, args probeArgs) (*perfEvent, error) {

// Kernel has perf_[k,u]probe PMU available, initialize perf event.
return &perfEvent{
fd: fd,
pmuID: et,
name: args.symbol,
typ: typ.PerfEventType(args.ret),
fd: fd,
pmuID: et,
name: args.symbol,
typ: typ.PerfEventType(args.ret),
bpfCookie: args.bpfCookie,
}, nil
}

Expand Down Expand Up @@ -331,6 +343,7 @@ func tracefsProbe(typ probeType, args probeArgs) (*perfEvent, error) {
name: args.symbol,
tracefsID: tid,
typ: typ.PerfEventType(args.ret),
bpfCookie: args.bpfCookie,
}, nil
}

Expand Down
29 changes: 20 additions & 9 deletions link/kprobe_test.go
Expand Up @@ -21,13 +21,13 @@ func TestKprobe(t *testing.T) {

prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")

k, err := Kprobe(ksym, prog)
k, err := Kprobe(ksym, prog, nil)
c.Assert(err, qt.IsNil)
defer k.Close()

testLink(t, k, prog)

k, err = Kprobe("bogus", prog)
k, err = Kprobe("bogus", prog, nil)
c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
if k != nil {
k.Close()
Expand All @@ -39,13 +39,13 @@ func TestKretprobe(t *testing.T) {

prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")

k, err := Kretprobe(ksym, prog)
k, err := Kretprobe(ksym, prog, nil)
c.Assert(err, qt.IsNil)
defer k.Close()

testLink(t, k, prog)

k, err = Kretprobe("bogus", prog)
k, err = Kretprobe("bogus", prog, nil)
c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
if k != nil {
k.Close()
Expand All @@ -57,16 +57,16 @@ func TestKprobeErrors(t *testing.T) {

// Invalid Kprobe incantations. Kretprobe uses the same code paths
// with a different ret flag.
_, err := Kprobe("", nil) // empty symbol
_, err := Kprobe("", nil, nil) // empty symbol
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)

_, err = Kprobe("_", nil) // empty prog
_, err = Kprobe("_", nil, nil) // empty prog
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)

_, err = Kprobe(".", &ebpf.Program{}) // illegal chars in symbol
_, err = Kprobe(".", &ebpf.Program{}, nil) // illegal chars in symbol
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)

_, err = Kprobe("foo", &ebpf.Program{}) // wrong prog type
_, err = Kprobe("foo", &ebpf.Program{}, nil) // wrong prog type
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)
}

Expand Down Expand Up @@ -275,7 +275,7 @@ func TestKprobeProgramCall(t *testing.T) {

// Open Kprobe on `sys_getpid` and attach it
// to the ebpf program created above.
k, err := Kprobe("sys_getpid", p)
k, err := Kprobe("sys_getpid", p, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -365,3 +365,14 @@ func assertMapValue(t *testing.T, m *ebpf.Map, k, v uint32) {
t.Fatalf("unexpected value: want '%d', got '%d'", v, val)
}
}

func TestKprobeCookie(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.15", "bpf_perf_link")

prog := mustLoadProgram(t, ebpf.Kprobe, 0, "")
k, err := Kprobe(ksym, prog, &KprobeOptions{BpfCookie: 1000})
if err != nil {
t.Fatal(err)
}
k.Close()
}
8 changes: 8 additions & 0 deletions link/perf_event.go
Expand Up @@ -87,6 +87,9 @@ type perfEvent struct {
// BPF link FD.
bpfLinkFD *sys.FD

// User provided arbitrary value.
bpfCookie uint64

fd *sys.FD
}

Expand Down Expand Up @@ -195,6 +198,7 @@ func (pe *perfEvent) attach(prog *ebpf.Program) error {
ProgFd: uint32(prog.FD()),
TargetFd: uint32(kfd),
AttachType: sys.BPF_PERF_EVENT,
BpfCookie: pe.bpfCookie,
}

fd, err := sys.LinkCreatePerfEvent(&attr)
Expand All @@ -203,6 +207,10 @@ func (pe *perfEvent) attach(prog *ebpf.Program) error {
}
pe.bpfLinkFD = fd
} else {
if pe.bpfCookie != 0 {
return fmt.Errorf("bpf cookies are available from kernel version 5.15+: %w", ErrNotSupported)
}

// Assign the eBPF program to the perf event.
err := unix.IoctlSetInt(int(kfd), unix.PERF_EVENT_IOC_SET_BPF, prog.FD())
if err != nil {
Expand Down
15 changes: 13 additions & 2 deletions link/tracepoint.go
Expand Up @@ -6,20 +6,30 @@ import (
"github.com/cilium/ebpf"
)

// TracepointOptions defines additional parameters that will be used
// when loading Tracepoints.
type TracepointOptions struct {
// Arbitrary value that can be fetched from an eBPF program
// via `bpf_get_attach_cookie()`.
//
// Needs kernel 5.15+.
BpfCookie uint64
}

// 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:
//
// tp, err := Tracepoint("syscalls", "sys_enter_fork", prog)
// tp, err := Tracepoint("syscalls", "sys_enter_fork", prog, nil)
//
// Losing the reference to the resulting Link (tp) will close the Tracepoint
// and prevent further execution of prog. The Link must be Closed during
// program shutdown to avoid leaking system resources.
//
// 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) {
func Tracepoint(group, name string, prog *ebpf.Program, opts *TracepointOptions) (Link, error) {
if group == "" || name == "" {
return nil, fmt.Errorf("group and name cannot be empty: %w", errInvalidInput)
}
Expand Down Expand Up @@ -49,6 +59,7 @@ func Tracepoint(group, name string, prog *ebpf.Program) (Link, error) {
group: group,
name: name,
typ: tracepointEvent,
bpfCookie: opts.BpfCookie,
}

if err := pe.attach(prog); err != nil {
Expand Down
14 changes: 7 additions & 7 deletions link/tracepoint_test.go
Expand Up @@ -20,7 +20,7 @@ func TestTracepoint(t *testing.T) {

// printk is guaranteed to be present.
// Kernels before 4.14 don't support attaching to syscall tracepoints.
tp, err := Tracepoint("printk", "console", prog)
tp, err := Tracepoint("printk", "console", prog, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -36,7 +36,7 @@ func TestTracepointMissing(t *testing.T) {

prog := mustLoadProgram(t, ebpf.TracePoint, 0, "")

_, err := Tracepoint("missing", "foobazbar", prog)
_, err := Tracepoint("missing", "foobazbar", prog, nil)
if !errors.Is(err, os.ErrNotExist) {
t.Error("Expected os.ErrNotExist, got", err)
}
Expand All @@ -46,16 +46,16 @@ func TestTracepointErrors(t *testing.T) {
c := qt.New(t)

// Invalid Tracepoint incantations.
_, err := Tracepoint("", "", nil) // empty names
_, err := Tracepoint("", "", nil, nil) // empty names
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)

_, err = Tracepoint("_", "_", nil) // empty prog
_, err = Tracepoint("_", "_", nil, nil) // empty prog
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)

_, err = Tracepoint(".", "+", &ebpf.Program{}) // illegal chars in group/name
_, err = Tracepoint(".", "+", &ebpf.Program{}, nil) // illegal chars in group/name
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)

_, err = Tracepoint("foo", "bar", &ebpf.Program{}) // wrong prog type
_, err = Tracepoint("foo", "bar", &ebpf.Program{}, nil) // wrong prog type
c.Assert(errors.Is(err, errInvalidInput), qt.IsTrue)
}

Expand All @@ -79,7 +79,7 @@ func TestTracepointProgramCall(t *testing.T) {

// Open Tracepoint at /sys/kernel/debug/tracing/events/syscalls/sys_enter_getpid
// and attach it to the ebpf program created above.
tp, err := Tracepoint("syscalls", "sys_enter_getpid", p)
tp, err := Tracepoint("syscalls", "sys_enter_getpid", p, nil)
if err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 6 additions & 0 deletions link/uprobe.go
Expand Up @@ -70,6 +70,11 @@ type UprobeOptions struct {
// github.com/torvalds/linux/commit/1cc33161a83d
// github.com/torvalds/linux/commit/a6ca88b241d5
RefCtrOffset uint64
// Arbitrary value that can be fetched from an eBPF program
// via `bpf_get_attach_cookie()`.
//
// Needs kernel 5.15+.
BpfCookie uint64
}

// To open a new Executable, use:
Expand Down Expand Up @@ -278,6 +283,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
pid: pid,
refCtrOffset: opts.RefCtrOffset,
ret: ret,
bpfCookie: opts.BpfCookie,
}

// Use uprobe PMU if the kernel has it available.
Expand Down

0 comments on commit 4862079

Please sign in to comment.