Repository: cilium/ebpf
Pull request #906 (2023-01-18 10:04:46)

link: use /sys/kernel/tracing if available
The tracefs should be mounted at `/sys/kernel/tracing`, and/or `/sys/kernel/debug/tracing`, or not mounted at all.
Today cilium/ebpf rely only on `/sys/kernel/debug/tracing`, this PR adds a fallback to also consider `/sys/kernel/tracing` (which should be the default one since kernel 4.1, based on what I understand from [the documentation](https://www.kernel.org/doc/Documentation/trace/ftrace.txt)).


spikat: Hi @lmb and thank you for your quick review. I've applied all of your suggestions. :)
spikat: Gentle bump. Have you by chance the time to have a look on my last changes @lmb ? :)
lmb: Sorry, this one slipped through the cracks. I had a review pending but didn't submit it somehow. I pushed another commit that uses `internal.Memoize` which I added recently.

Patch:
diff --git a/examples/tracepoint_in_c/main.go b/examples/tracepoint_in_c/main.go
index 18a8886..c4f4858 100644
--- a/examples/tracepoint_in_c/main.go
+++ b/examples/tracepoint_in_c/main.go
@@ -1,7 +1,7 @@
 // This program demonstrates attaching an eBPF program to a kernel tracepoint.
 // The eBPF program will be attached to the page allocation tracepoint and
 // prints out the number of times it has been reached. The tracepoint fields
-// are printed into /sys/kernel/debug/tracing/trace_pipe.
+// are printed into /sys/kernel/tracing/trace_pipe.
 package main
 
 import (
@@ -35,7 +35,7 @@ func main() {
 	// counter by 1. The read loop below polls this map value once per
 	// second.
 	// The first two arguments are taken from the following pathname:
-	// /sys/kernel/debug/tracing/events/kmem/mm_page_alloc
+	// /sys/kernel/tracing/events/kmem/mm_page_alloc
 	kp, err := link.Tracepoint("kmem", "mm_page_alloc", objs.MmPageAlloc, nil)
 	if err != nil {
 		log.Fatalf("opening tracepoint: %s", err)
diff --git a/examples/tracepoint_in_c/tracepoint.c b/examples/tracepoint_in_c/tracepoint.c
index fe66395..3b7961d 100644
--- a/examples/tracepoint_in_c/tracepoint.c
+++ b/examples/tracepoint_in_c/tracepoint.c
@@ -12,7 +12,7 @@ struct bpf_map_def SEC("maps") counting_map = {
 };
 
 // This struct is defined according to the following format file:
-// /sys/kernel/debug/tracing/events/kmem/mm_page_alloc/format
+// /sys/kernel/tracing/events/kmem/mm_page_alloc/format
 struct alloc_info {
 	/* The first 8 bytes is not allowed to read */
 	unsigned long pad;
@@ -24,7 +24,7 @@ struct alloc_info {
 };
 
 // This tracepoint is defined in mm/page_alloc.c:__alloc_pages_nodemask()
-// Userspace pathname: /sys/kernel/debug/tracing/events/kmem/mm_page_alloc
+// Userspace pathname: /sys/kernel/tracing/events/kmem/mm_page_alloc
 SEC("tracepoint/kmem/mm_page_alloc")
 int mm_page_alloc(struct alloc_info *info) {
 	u32 key     = 0;
diff --git a/link/kprobe.go b/link/kprobe.go
index 9ce7eb4..b426b52 100644
--- a/link/kprobe.go
+++ b/link/kprobe.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"path/filepath"
 	"runtime"
 	"strings"
 	"syscall"
@@ -16,10 +15,6 @@ import (
 	"github.com/cilium/ebpf/internal/unix"
 )
 
-var (
-	kprobeEventsPath = filepath.Join(tracefsPath, "kprobe_events")
-)
-
 type probeType uint8
 
 type probeArgs struct {
@@ -61,11 +56,13 @@ func (pt probeType) String() string {
 	return "uprobe"
 }
 
-func (pt probeType) EventsPath() string {
-	if pt == kprobeType {
-		return kprobeEventsPath
+func (pt probeType) EventsFile() (*os.File, error) {
+	path, err := sanitizeTracefsPath(fmt.Sprintf("%s_events", pt.String()))
+	if err != nil {
+		return nil, err
 	}
-	return uprobeEventsPath
+
+	return os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0666)
 }
 
 func (pt probeType) PerfEventType(ret bool) perfEventType {
@@ -419,9 +416,9 @@ func createTraceFSProbeEvent(typ probeType, args probeArgs) (uint64, error) {
 	}
 
 	// Open the kprobe_events file in tracefs.
-	f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
+	f, err := typ.EventsFile()
 	if err != nil {
-		return 0, fmt.Errorf("error opening '%s': %w", typ.EventsPath(), err)
+		return 0, err
 	}
 	defer f.Close()
 
@@ -515,16 +512,16 @@ func closeTraceFSProbeEvent(typ probeType, group, symbol string) error {
 }
 
 func removeTraceFSProbeEvent(typ probeType, pe string) error {
-	f, err := os.OpenFile(typ.EventsPath(), os.O_APPEND|os.O_WRONLY, 0666)
+	f, err := typ.EventsFile()
 	if err != nil {
-		return fmt.Errorf("error opening %s: %w", typ.EventsPath(), err)
+		return err
 	}
 	defer f.Close()
 
 	// See [k,u]probe_events syntax above. The probe type does not need to be specified
 	// for removals.
 	if _, err = f.WriteString("-:" + pe); err != nil {
-		return fmt.Errorf("remove event %q from %s: %w", pe, typ.EventsPath(), err)
+		return fmt.Errorf("remove event %q from %s: %w", pe, f.Name(), err)
 	}
 
 	return nil
diff --git a/link/perf_event.go b/link/perf_event.go
index 6736dcd..525d0e9 100644
--- a/link/perf_event.go
+++ b/link/perf_event.go
@@ -42,8 +42,6 @@ import (
 //   stops any further invocations of the attached eBPF program.
 
 var (
-	tracefsPath = "/sys/kernel/debug/tracing"
-
 	errInvalidInput = errors.New("invalid input")
 )
 
@@ -274,7 +272,7 @@ func unsafeStringPtr(str string) (unsafe.Pointer, error) {
 // can pass a raw symbol name, e.g. a kernel symbol containing dots.
 func getTraceEventID(group, name string) (uint64, error) {
 	name = sanitizeSymbol(name)
-	path, err := sanitizePath(tracefsPath, "events", group, name, "id")
+	path, err := sanitizeTracefsPath("events", group, name, "id")
 	if err != nil {
 		return 0, err
 	}
@@ -309,7 +307,11 @@ func openTracepointPerfEvent(tid uint64, pid int) (*sys.FD, error) {
 	return sys.NewFD(fd)
 }
 
-func sanitizePath(base string, path ...string) (string, error) {
+func sanitizeTracefsPath(path ...string) (string, error) {
+	base, err := getTracefsPath()
+	if err != nil {
+		return "", err
+	}
 	l := filepath.Join(path...)
 	p := filepath.Join(base, l)
 	if !strings.HasPrefix(p, base) {
@@ -436,3 +438,24 @@ func isValidTraceID(s string) bool {
 
 	return true
 }
+
+// getTracefsPath will return a correct path to the tracefs mount point.
+// Since kernel 4.1 tracefs should be mounted by default at /sys/kernel/tracing,
+// but may be also be available at /sys/kernel/debug/tracing if debugfs is mounted.
+// The available tracefs paths will depends on distribution choices.
+var getTracefsPath = internal.Memoize(func() (string, error) {
+	for _, p := range []struct {
+		file, path string
+	}{
+		// It's not enough to stat /sys/kernel/tracing, since some distros have
+		// a directory without a filesystem there.
+		{"/sys/kernel/tracing/kprobe_events", "/sys/kernel/tracing"},
+		{"/sys/kernel/debug/tracing", "/sys/kernel/debug/tracing"},
+	} {
+		if _, err := os.Stat(p.file); err == nil {
+			return p.path, nil
+		}
+	}
+
+	return "", errors.New("neither debugfs nor tracefs are mounted")
+})
diff --git a/link/tracepoint.go b/link/tracepoint.go
index a59ef9d..a810d08 100644
--- a/link/tracepoint.go
+++ b/link/tracepoint.go
@@ -17,7 +17,7 @@ type TracepointOptions struct {
 }
 
 // Tracepoint attaches the given eBPF program to the tracepoint with the given
-// group and name. See /sys/kernel/debug/tracing/events to find available
+// group and name. See /sys/kernel/tracing/events to find available
 // tracepoints. The top-level directory is the group, the event's subdirectory
 // is the name. Example:
 //
diff --git a/link/uprobe.go b/link/uprobe.go
index 9a03eb7..a591d82 100644
--- a/link/uprobe.go
+++ b/link/uprobe.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"path/filepath"
 	"strings"
 
 	"github.com/cilium/ebpf"
@@ -13,8 +12,6 @@ import (
 )
 
 var (
-	uprobeEventsPath = filepath.Join(tracefsPath, "uprobe_events")
-
 	uprobeRefCtrOffsetPMUPath = "/sys/bus/event_source/devices/uprobe/format/ref_ctr_offset"
 	// elixir.bootlin.com/linux/v5.15-rc7/source/kernel/events/core.c#L9799
 	uprobeRefCtrOffsetShift = 32


Test patch:
diff --git a/link/perf_event_test.go b/link/perf_event_test.go
index 4395c47..0490388 100644
--- a/link/perf_event_test.go
+++ b/link/perf_event_test.go
@@ -17,12 +17,12 @@ func TestTraceEventID(t *testing.T) {
 }
 
 func TestSanitizePath(t *testing.T) {
-	_, err := sanitizePath("/base/path/", "../escaped")
+	_, err := sanitizeTracefsPath("../escaped")
 	if !errors.Is(err, errInvalidInput) {
 		t.Errorf("expected error %s, got: %s", errInvalidInput, err)
 	}
 
-	_, err = sanitizePath("/base/path/not", "../not/escaped")
+	_, err = sanitizeTracefsPath("./not/escaped")
 	if err != nil {
 		t.Errorf("expected no error, got: %s", err)
 	}
diff --git a/link/tracepoint_test.go b/link/tracepoint_test.go
index 18d3612..f548020 100644
--- a/link/tracepoint_test.go
+++ b/link/tracepoint_test.go
@@ -77,7 +77,7 @@ func TestTracepointProgramCall(t *testing.T) {
 
 	m, p := newUpdaterMapProg(t, ebpf.TracePoint, 0)
 
-	// Open Tracepoint at /sys/kernel/debug/tracing/events/syscalls/sys_enter_getpid
+	// Open Tracepoint at /sys/kernel/tracing/events/syscalls/sys_enter_getpid
 	// and attach it to the ebpf program created above.
 	tp, err := Tracepoint("syscalls", "sys_enter_getpid", p, nil)
 	if err != nil {