From 4862079a00b16bacb5587e6d805da9415e2b4986 Mon Sep 17 00:00:00 2001 From: Mattia Meleleo Date: Fri, 11 Feb 2022 12:08:30 +0100 Subject: [PATCH] link: support bpf_cookie for any perf-event backed link (k,uprobes, tracepoints) Signed-off-by: Mattia Meleleo --- link/kprobe.go | 49 ++++++++++++++++++++++++++--------------- link/kprobe_test.go | 29 ++++++++++++++++-------- link/perf_event.go | 8 +++++++ link/tracepoint.go | 15 +++++++++++-- link/tracepoint_test.go | 14 ++++++------ link/uprobe.go | 6 +++++ 6 files changed, 85 insertions(+), 36 deletions(-) diff --git a/link/kprobe.go b/link/kprobe.go index 6b896360d..a2abe5a08 100644 --- a/link/kprobe.go +++ b/link/kprobe.go @@ -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 ( @@ -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 } @@ -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 } @@ -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) } @@ -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. @@ -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 } @@ -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 } diff --git a/link/kprobe_test.go b/link/kprobe_test.go index d0fb1051f..35a73708b 100644 --- a/link/kprobe_test.go +++ b/link/kprobe_test.go @@ -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() @@ -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() @@ -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) } @@ -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) } @@ -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() +} diff --git a/link/perf_event.go b/link/perf_event.go index 1e027cb2a..8a59241b8 100644 --- a/link/perf_event.go +++ b/link/perf_event.go @@ -87,6 +87,9 @@ type perfEvent struct { // BPF link FD. bpfLinkFD *sys.FD + // User provided arbitrary value. + bpfCookie uint64 + fd *sys.FD } @@ -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) @@ -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 { diff --git a/link/tracepoint.go b/link/tracepoint.go index 7423df86b..cb6821d01 100644 --- a/link/tracepoint.go +++ b/link/tracepoint.go @@ -6,12 +6,22 @@ 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 @@ -19,7 +29,7 @@ import ( // // 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) } @@ -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 { diff --git a/link/tracepoint_test.go b/link/tracepoint_test.go index 3c79e2a6a..f7c054af1 100644 --- a/link/tracepoint_test.go +++ b/link/tracepoint_test.go @@ -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) } @@ -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) } @@ -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) } @@ -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) } diff --git a/link/uprobe.go b/link/uprobe.go index d603575ca..8f5687258 100644 --- a/link/uprobe.go +++ b/link/uprobe.go @@ -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: @@ -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.