Repository: cilium/ebpf
Pull request #1023 (2023-04-25 09:20:19)

refactor tracefs event handling into an object
tracefs: remove unused perfEvent.pmuID
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>

tracefs: simplify kprobe / uprobe event creation tests
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>

tracefs: add ProbeArgs.Type
    
    Add a field to ProbeArgs which indicates the kind of probe to create,
    which means that ProbeArgs fully specifies an event now.
    This allows us to get rid of a bunch of one line wrapper functions.
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>

tracefs: refactor tracefs event handling into an object
    
    It is currently cumbersome to deal with a tracefs event since
    it is only identified by its ID. The caller must remember the
    probe type, gruop name, etc. to be able to remove the event.
    
    Refactor the code into a new Event struct, which deals with
    all of this and which also has a finalizer.
    
    Also change GetTraceEventID to not sanitize its arguments:
    the caller is now expected to provide valid tracefs names.
    This reduces confusion between the tracefs event name and
    the probe symbol: we sanitize the symbol to obtain a valid
    event name.
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>

tracefs: remove duplicate GetTraceEventID test
    
    Signed-off-by: Lorenz Bauer <lmb@isovalent.com>



Patch:
diff --git a/internal/tracefs/kprobe.go b/internal/tracefs/kprobe.go
index f3c0fc8..2b1698b 100644
--- a/internal/tracefs/kprobe.go
+++ b/internal/tracefs/kprobe.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"syscall"
 
@@ -22,12 +23,12 @@ var (
 type ProbeType uint8
 
 const (
-	KprobeType ProbeType = iota
-	UprobeType
+	Kprobe ProbeType = iota
+	Uprobe
 )
 
 func (pt ProbeType) String() string {
-	if pt == KprobeType {
+	if pt == Kprobe {
 		return "kprobe"
 	}
 	return "uprobe"
@@ -43,6 +44,7 @@ func (pt ProbeType) eventsFile() (*os.File, error) {
 }
 
 type ProbeArgs struct {
+	Type                         ProbeType
 	Symbol, Group, Path          string
 	Offset, RefCtrOffset, Cookie uint64
 	Pid, RetprobeMaxActive       int
@@ -54,7 +56,7 @@ type ProbeArgs struct {
 // limitation), when rand.Read() fails or when prefix contains characters not
 // allowed by IsValidTraceID.
 func RandomGroup(prefix string) (string, error) {
-	if !IsValidTraceID(prefix) {
+	if !validIdentifier(prefix) {
 		return "", fmt.Errorf("prefix '%s' must be alphanumeric or underscore: %w", prefix, ErrInvalidInput)
 	}
 
@@ -71,13 +73,13 @@ func RandomGroup(prefix string) (string, error) {
 	return group, nil
 }
 
-// IsValidTraceID implements the equivalent of a regex match
+// validIdentifier implements the equivalent of a regex match
 // against "^[a-zA-Z_][0-9a-zA-Z_]*$".
 //
 // Trace event groups, names and kernel symbols must adhere to this set
 // of characters. Non-empty, first character must not be a number, all
 // characters must be alphanumeric or underscore.
-func IsValidTraceID(s string) bool {
+func validIdentifier(s string) bool {
 	if len(s) < 1 {
 		return false
 	}
@@ -131,9 +133,10 @@ var getTracefsPath = internal.Memoize(func() (string, error) {
 	return "", errors.New("neither debugfs nor tracefs are mounted")
 })
 
-// SanitizeSymbol replaces every invalid character for the tracefs api with an underscore.
+// sanitizeIdentifier replaces every invalid character for the tracefs api with an underscore.
+//
 // It is equivalent to calling regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString("_").
-func SanitizeSymbol(s string) string {
+func sanitizeIdentifier(s string) string {
 	var skip bool
 	return strings.Map(func(c rune) rune {
 		switch {
@@ -153,13 +156,17 @@ func SanitizeSymbol(s string) string {
 	}, s)
 }
 
-// GetTraceEventID reads a trace event's ID from tracefs given its group and name.
+// EventID reads a trace event's ID from tracefs given its group and name.
 // The kernel requires group and name to be alphanumeric or underscore.
-//
-// name automatically has its invalid symbols converted to underscores so the caller
-// can pass a raw symbol name, e.g. a kernel symbol containing dots.
-func GetTraceEventID(group, name string) (uint64, error) {
-	name = SanitizeSymbol(name)
+func EventID(group, name string) (uint64, error) {
+	if !validIdentifier(group) {
+		return 0, fmt.Errorf("invalid tracefs group: %q", group)
+	}
+
+	if !validIdentifier(name) {
+		return 0, fmt.Errorf("invalid tracefs name: %q", name)
+	}
+
 	path, err := sanitizeTracefsPath("events", group, name, "id")
 	if err != nil {
 		return 0, err
@@ -185,36 +192,45 @@ func probePrefix(ret bool, maxActive int) string {
 	return "p"
 }
 
-// CreateTraceFSProbeEvent creates a new ephemeral trace event.
+// Event represents an entry in a tracefs probe events file.
+type Event struct {
+	typ         ProbeType
+	group, name string
+	// event id allocated by the kernel. 0 if the event has already been removed.
+	id uint64
+}
+
+// NewEvent creates a new ephemeral trace event.
 //
 // Returns os.ErrNotExist if symbol is not a valid
 // kernel symbol, or if it is not traceable with kprobes. Returns os.ErrExist
 // if a probe with the same group and symbol already exists. Returns an error if
 // args.RetprobeMaxActive is used on non kprobe types. Returns ErrNotSupported if
 // the kernel is too old to support kretprobe maxactive.
-func CreateTraceFSProbeEvent(typ ProbeType, args ProbeArgs) (uint64, error) {
+func NewEvent(args ProbeArgs) (*Event, error) {
 	// Before attempting to create a trace event through tracefs,
 	// check if an event with the same group and name already exists.
 	// Kernels 4.x and earlier don't return os.ErrExist on writing a duplicate
 	// entry, so we need to rely on reads for detecting uniqueness.
-	_, err := GetTraceEventID(args.Group, args.Symbol)
+	eventName := sanitizeIdentifier(args.Symbol)
+	_, err := EventID(args.Group, eventName)
 	if err == nil {
-		return 0, fmt.Errorf("trace event %s/%s: %w", args.Group, args.Symbol, os.ErrExist)
+		return nil, fmt.Errorf("trace event %s/%s: %w", args.Group, eventName, os.ErrExist)
 	}
 	if err != nil && !errors.Is(err, os.ErrNotExist) {
-		return 0, fmt.Errorf("checking trace event %s/%s: %w", args.Group, args.Symbol, err)
+		return nil, fmt.Errorf("checking trace event %s/%s: %w", args.Group, eventName, err)
 	}
 
 	// Open the kprobe_events file in tracefs.
-	f, err := typ.eventsFile()
+	f, err := args.Type.eventsFile()
 	if err != nil {
-		return 0, err
+		return nil, err
 	}
 	defer f.Close()
 
 	var pe, token string
-	switch typ {
-	case KprobeType:
+	switch args.Type {
+	case Kprobe:
 		// The kprobe_events syntax is as follows (see Documentation/trace/kprobetrace.txt):
 		// p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
 		// r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
@@ -230,11 +246,11 @@ func CreateTraceFSProbeEvent(typ ProbeType, args ProbeArgs) (uint64, error) {
 		// the eBPF program itself.
 		// See Documentation/kprobes.txt for more details.
 		if args.RetprobeMaxActive != 0 && !args.Ret {
-			return 0, ErrInvalidMaxActive
+			return nil, ErrInvalidMaxActive
 		}
 		token = KprobeToken(args)
-		pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, args.RetprobeMaxActive), args.Group, SanitizeSymbol(args.Symbol), token)
-	case UprobeType:
+		pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, args.RetprobeMaxActive), args.Group, eventName, token)
+	case Uprobe:
 		// The uprobe_events syntax is as follows:
 		// p[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a probe
 		// r[:[GRP/]EVENT] PATH:OFFSET [FETCHARGS] : Set a return probe
@@ -246,62 +262,71 @@ func CreateTraceFSProbeEvent(typ ProbeType, args ProbeArgs) (uint64, error) {
 		//
 		// See Documentation/trace/uprobetracer.txt for more details.
 		if args.RetprobeMaxActive != 0 {
-			return 0, ErrInvalidMaxActive
+			return nil, ErrInvalidMaxActive
 		}
 		token = UprobeToken(args)
-		pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, 0), args.Group, args.Symbol, token)
+		pe = fmt.Sprintf("%s:%s/%s %s", probePrefix(args.Ret, 0), args.Group, eventName, token)
 	}
 	_, err = f.WriteString(pe)
 
 	// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
 	// when trying to create a retprobe for a missing symbol.
 	if errors.Is(err, os.ErrNotExist) {
-		return 0, fmt.Errorf("token %s: not found: %w", token, err)
+		return nil, fmt.Errorf("token %s: not found: %w", token, err)
 	}
 	// Since commit ab105a4fb894, EILSEQ is returned when a kprobe sym+offset is resolved
 	// to an invalid insn boundary. The exact conditions that trigger this error are
 	// arch specific however.
 	if errors.Is(err, syscall.EILSEQ) {
-		return 0, fmt.Errorf("token %s: bad insn boundary: %w", token, os.ErrNotExist)
+		return nil, fmt.Errorf("token %s: bad insn boundary: %w", token, os.ErrNotExist)
 	}
 	// ERANGE is returned when the `SYM[+offs]` token is too big and cannot
 	// be resolved.
 	if errors.Is(err, syscall.ERANGE) {
-		return 0, fmt.Errorf("token %s: offset too big: %w", token, os.ErrNotExist)
+		return nil, fmt.Errorf("token %s: offset too big: %w", token, os.ErrNotExist)
 	}
 
 	if err != nil {
-		return 0, fmt.Errorf("token %s: writing '%s': %w", token, pe, err)
+		return nil, fmt.Errorf("token %s: writing '%s': %w", token, pe, err)
 	}
 
 	// Get the newly-created trace event's id.
-	tid, err := GetTraceEventID(args.Group, args.Symbol)
+	tid, err := EventID(args.Group, eventName)
 	if args.RetprobeMaxActive != 0 && errors.Is(err, os.ErrNotExist) {
 		// Kernels < 4.12 don't support maxactive and therefore auto generate
 		// group and event names from the symbol and offset. The symbol is used
 		// without any sanitization.
 		// See https://elixir.bootlin.com/linux/v4.10/source/kernel/trace/trace_kprobe.c#L712
 		event := fmt.Sprintf("kprobes/r_%s_%d", args.Symbol, args.Offset)
-		if err := removeTraceFSProbeEvent(typ, event); err != nil {
-			return 0, fmt.Errorf("failed to remove spurious maxactive event: %s", err)
+		if err := removeEvent(args.Type, event); err != nil {
+			return nil, fmt.Errorf("failed to remove spurious maxactive event: %s", err)
 		}
-		return 0, fmt.Errorf("create trace event with non-default maxactive: %w", internal.ErrNotSupported)
+		return nil, fmt.Errorf("create trace event with non-default maxactive: %w", internal.ErrNotSupported)
 	}
 	if err != nil {
-		return 0, fmt.Errorf("get trace event id: %w", err)
+		return nil, fmt.Errorf("get trace event id: %w", err)
 	}
 
-	return tid, nil
+	evt := &Event{args.Type, args.Group, eventName, tid}
+	runtime.SetFinalizer(evt, (*Event).Close)
+	return evt, nil
 }
 
-// CloseTraceFSProbeEvent removes the [k,u]probe with the given type, group and symbol
-// from <tracefs>/[k,u]probe_events.
-func CloseTraceFSProbeEvent(typ ProbeType, group, symbol string) error {
-	pe := fmt.Sprintf("%s/%s", group, SanitizeSymbol(symbol))
-	return removeTraceFSProbeEvent(typ, pe)
+// Close removes the event from tracefs.
+//
+// Returns os.ErrClosed if the event has already been closed before.
+func (evt *Event) Close() error {
+	if evt.id == 0 {
+		return os.ErrClosed
+	}
+
+	evt.id = 0
+	runtime.SetFinalizer(evt, nil)
+	pe := fmt.Sprintf("%s/%s", evt.group, evt.name)
+	return removeEvent(evt.typ, pe)
 }
 
-func removeTraceFSProbeEvent(typ ProbeType, pe string) error {
+func removeEvent(typ ProbeType, pe string) error {
 	f, err := typ.eventsFile()
 	if err != nil {
 		return err
@@ -317,6 +342,11 @@ func removeTraceFSProbeEvent(typ ProbeType, pe string) error {
 	return nil
 }
 
+// ID returns the tracefs ID associated with the event.
+func (evt *Event) ID() uint64 {
+	return evt.id
+}
+
 // KprobeToken creates the SYM[+offs] token for the tracefs api.
 func KprobeToken(args ProbeArgs) string {
 	po := args.Symbol
diff --git a/link/kprobe.go b/link/kprobe.go
index 7fab049..663b408 100644
--- a/link/kprobe.go
+++ b/link/kprobe.go
@@ -40,7 +40,7 @@ type KprobeOptions struct {
 }
 
 func perfEventTypeFromProbeType(pt tracefs.ProbeType, ret bool) perfEventType {
-	if pt == tracefs.KprobeType {
+	if pt == tracefs.Kprobe {
 		if ret {
 			return kretprobeEvent
 		}
@@ -155,6 +155,7 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
 	}
 
 	args := tracefs.ProbeArgs{
+		Type:   tracefs.Kprobe,
 		Pid:    perfAllThreads,
 		Symbol: symbol,
 		Ret:    ret,
@@ -168,11 +169,11 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
 	}
 
 	// Use kprobe PMU if the kernel has it available.
-	tp, err := pmuKprobe(args)
+	tp, err := pmuProbe(args)
 	if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
 		if prefixedSymbol := internal.PlatformPrefix(symbol); prefixedSymbol != "" {
 			args.Symbol = prefixedSymbol
-			tp, err = pmuKprobe(args)
+			tp, err = pmuProbe(args)
 		}
 	}
 	if err == nil {
@@ -184,11 +185,11 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
 
 	// Use tracefs if kprobe PMU is missing.
 	args.Symbol = symbol
-	tp, err = tracefsKprobe(args)
+	tp, err = tracefsProbe(args)
 	if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
 		if prefixedSymbol := internal.PlatformPrefix(symbol); prefixedSymbol != "" {
 			args.Symbol = prefixedSymbol
-			tp, err = tracefsKprobe(args)
+			tp, err = tracefsProbe(args)
 		}
 	}
 	if err != nil {
@@ -198,12 +199,6 @@ func kprobe(symbol string, prog *ebpf.Program, opts *KprobeOptions, ret bool) (*
 	return tp, nil
 }
 
-// pmuKprobe opens a perf event based on the kprobe PMU.
-// Returns os.ErrNotExist if the given symbol does not exist in the kernel.
-func pmuKprobe(args tracefs.ProbeArgs) (*perfEvent, error) {
-	return pmuProbe(tracefs.KprobeType, args)
-}
-
 // pmuProbe opens a perf event based on a Performance Monitoring Unit.
 //
 // Requires at least a 4.17 kernel.
@@ -211,12 +206,12 @@ func pmuKprobe(args tracefs.ProbeArgs) (*perfEvent, error) {
 // 33ea4b24277b "perf/core: Implement the 'perf_uprobe' PMU"
 //
 // Returns ErrNotSupported if the kernel doesn't support perf_[k,u]probe PMU
-func pmuProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error) {
+func pmuProbe(args tracefs.ProbeArgs) (*perfEvent, error) {
 	// Getting the PMU type will fail if the kernel doesn't support
 	// the perf_[k,u]probe PMU.
-	et, err := internal.ReadUint64FromFileOnce("%d\n", "/sys/bus/event_source/devices", typ.String(), "type")
+	eventType, err := internal.ReadUint64FromFileOnce("%d\n", "/sys/bus/event_source/devices", args.Type.String(), "type")
 	if errors.Is(err, os.ErrNotExist) {
-		return nil, fmt.Errorf("%s: %w", typ, ErrNotSupported)
+		return nil, fmt.Errorf("%s: %w", args.Type, ErrNotSupported)
 	}
 	if err != nil {
 		return nil, err
@@ -229,7 +224,7 @@ func pmuProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error)
 
 	var config uint64
 	if args.Ret {
-		bit, err := internal.ReadUint64FromFileOnce("config:%d\n", "/sys/bus/event_source/devices", typ.String(), "/format/retprobe")
+		bit, err := internal.ReadUint64FromFileOnce("config:%d\n", "/sys/bus/event_source/devices", args.Type.String(), "/format/retprobe")
 		if err != nil {
 			return nil, err
 		}
@@ -241,8 +236,8 @@ func pmuProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error)
 		sp    unsafe.Pointer
 		token string
 	)
-	switch typ {
-	case tracefs.KprobeType:
+	switch args.Type {
+	case tracefs.Kprobe:
 		// Create a pointer to a NUL-terminated string for the kernel.
 		sp, err = unsafeStringPtr(args.Symbol)
 		if err != nil {
@@ -255,12 +250,12 @@ func pmuProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error)
 			// The minimum size required for PMU kprobes is PERF_ATTR_SIZE_VER1,
 			// since it added the config2 (Ext2) field. Use Ext2 as probe_offset.
 			Size:   unix.PERF_ATTR_SIZE_VER1,
-			Type:   uint32(et),          // PMU event type read from sysfs
+			Type:   uint32(eventType),   // PMU event type read from sysfs
 			Ext1:   uint64(uintptr(sp)), // Kernel symbol to trace
 			Ext2:   args.Offset,         // Kernel symbol offset
 			Config: config,              // Retprobe flag
 		}
-	case tracefs.UprobeType:
+	case tracefs.Uprobe:
 		sp, err = unsafeStringPtr(args.Path)
 		if err != nil {
 			return nil, err
@@ -278,7 +273,7 @@ func pmuProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error)
 			// size of the internal buffer the kernel allocates for reading the
 			// perf_event_attr argument from userspace.
 			Size:   unix.PERF_ATTR_SIZE_VER1,
-			Type:   uint32(et),          // PMU event type read from sysfs
+			Type:   uint32(eventType),   // PMU event type read from sysfs
 			Ext1:   uint64(uintptr(sp)), // Uprobe path
 			Ext2:   args.Offset,         // Uprobe offset
 			Config: config,              // RefCtrOffset, Retprobe flag
@@ -324,26 +319,20 @@ func pmuProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error)
 
 	// Kernel has perf_[k,u]probe PMU available, initialize perf event.
 	return &perfEvent{
-		typ:    perfEventTypeFromProbeType(typ, args.Ret),
+		typ:    perfEventTypeFromProbeType(args.Type, args.Ret),
 		name:   args.Symbol,
-		pmuID:  et,
 		cookie: args.Cookie,
 		fd:     fd,
 	}, nil
 }
 
-// tracefsKprobe creates a Kprobe tracefs entry.
-func tracefsKprobe(args tracefs.ProbeArgs) (*perfEvent, error) {
-	return tracefsProbe(tracefs.KprobeType, args)
-}
-
 // tracefsProbe creates a trace event by writing an entry to <tracefs>/[k,u]probe_events.
 // A new trace event group name is generated on every call to support creating
 // multiple trace events for the same kernel or userspace symbol.
 // Path and offset are only set in the case of uprobe(s) and are used to set
 // the executable/library path on the filesystem and the offset where the probe is inserted.
 // A perf event is then opened on the newly-created trace event and returned to the caller.
-func tracefsProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, error) {
+func tracefsProbe(args tracefs.ProbeArgs) (*perfEvent, error) {
 	groupPrefix := "ebpf"
 	if args.Group != "" {
 		groupPrefix = args.Group
@@ -359,28 +348,28 @@ func tracefsProbe(typ tracefs.ProbeType, args tracefs.ProbeArgs) (*perfEvent, er
 	args.Group = group
 
 	// Create the [k,u]probe trace event using tracefs.
-	tid, err := tracefs.CreateTraceFSProbeEvent(typ, args)
+	evt, err := tracefs.NewEvent(args)
 	if err != nil {
 		return nil, fmt.Errorf("creating probe entry on tracefs: %w", err)
 	}
 
 	// Kprobes are ephemeral tracepoints and share the same perf event type.
-	fd, err := openTracepointPerfEvent(tid, args.Pid)
+	fd, err := openTracepointPerfEvent(evt.ID(), args.Pid)
 	if err != nil {
 		// Make sure we clean up the created tracefs event when we return error.
 		// If a livepatch handler is already active on the symbol, the write to
 		// tracefs will succeed, a trace event will show up, but creating the
 		// perf event will fail with EBUSY.
-		_ = tracefs.CloseTraceFSProbeEvent(typ, args.Group, args.Symbol)
+		_ = evt.Close()
 		return nil, err
 	}
 
 	return &perfEvent{
-		typ:       perfEventTypeFromProbeType(typ, args.Ret),
-		group:     group,
-		name:      args.Symbol,
-		tracefsID: tid,
-		cookie:    args.Cookie,
-		fd:        fd,
+		typ:          perfEventTypeFromProbeType(args.Type, args.Ret),
+		group:        group,
+		name:         args.Symbol,
+		tracefsEvent: evt,
+		cookie:       args.Cookie,
+		fd:           fd,
 	}, nil
 }
diff --git a/link/perf_event.go b/link/perf_event.go
index 15628cd..96f2a0d 100644
--- a/link/perf_event.go
+++ b/link/perf_event.go
@@ -66,10 +66,8 @@ type perfEvent struct {
 	group string
 	name  string
 
-	// PMU event ID read from sysfs. Valid IDs are non-zero.
-	pmuID uint64
-	// ID of the trace event read from tracefs. Valid IDs are non-zero.
-	tracefsID uint64
+	// Trace event backing this perfEvent. May be nil.
+	tracefsEvent *tracefs.Event
 
 	// User provided arbitrary value.
 	cookie uint64
@@ -83,20 +81,8 @@ func (pe *perfEvent) Close() error {
 		return fmt.Errorf("closing perf event fd: %w", err)
 	}
 
-	switch pe.typ {
-	case kprobeEvent, kretprobeEvent:
-		// Clean up kprobe tracefs entry.
-		if pe.tracefsID != 0 {
-			return tracefs.CloseTraceFSProbeEvent(tracefs.KprobeType, pe.group, pe.name)
-		}
-	case uprobeEvent, uretprobeEvent:
-		// Clean up uprobe tracefs entry.
-		if pe.tracefsID != 0 {
-			return tracefs.CloseTraceFSProbeEvent(tracefs.UprobeType, pe.group, pe.name)
-		}
-	case tracepointEvent:
-		// Tracepoint trace events don't hold any extra resources.
-		return nil
+	if pe.tracefsEvent != nil {
+		return pe.tracefsEvent.Close()
 	}
 
 	return nil
diff --git a/link/tracepoint.go b/link/tracepoint.go
index 142db7c..e911c0a 100644
--- a/link/tracepoint.go
+++ b/link/tracepoint.go
@@ -37,14 +37,11 @@ func Tracepoint(group, name string, prog *ebpf.Program, opts *TracepointOptions)
 	if prog == nil {
 		return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
 	}
-	if !tracefs.IsValidTraceID(group) || !tracefs.IsValidTraceID(name) {
-		return nil, fmt.Errorf("group and name '%s/%s' must be alphanumeric or underscore: %w", group, name, errInvalidInput)
-	}
 	if prog.Type() != ebpf.TracePoint {
 		return nil, fmt.Errorf("eBPF program type %s is not a Tracepoint: %w", prog.Type(), errInvalidInput)
 	}
 
-	tid, err := tracefs.GetTraceEventID(group, name)
+	tid, err := tracefs.EventID(group, name)
 	if err != nil {
 		return nil, err
 	}
@@ -60,12 +57,11 @@ func Tracepoint(group, name string, prog *ebpf.Program, opts *TracepointOptions)
 	}
 
 	pe := &perfEvent{
-		typ:       tracepointEvent,
-		group:     group,
-		name:      name,
-		tracefsID: tid,
-		cookie:    cookie,
-		fd:        fd,
+		typ:    tracepointEvent,
+		group:  group,
+		name:   name,
+		cookie: cookie,
+		fd:     fd,
 	}
 
 	lnk, err := attachPerfEvent(pe, prog)
diff --git a/link/uprobe.go b/link/uprobe.go
index ee68e1b..fa2aa1e 100644
--- a/link/uprobe.go
+++ b/link/uprobe.go
@@ -291,6 +291,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 	}
 
 	args := tracefs.ProbeArgs{
+		Type:         tracefs.Uprobe,
 		Symbol:       symbol,
 		Path:         ex.path,
 		Offset:       offset,
@@ -302,7 +303,7 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 	}
 
 	// Use uprobe PMU if the kernel has it available.
-	tp, err := pmuUprobe(args)
+	tp, err := pmuProbe(args)
 	if err == nil {
 		return tp, nil
 	}
@@ -311,21 +312,10 @@ func (ex *Executable) uprobe(symbol string, prog *ebpf.Program, opts *UprobeOpti
 	}
 
 	// Use tracefs if uprobe PMU is missing.
-	args.Symbol = tracefs.SanitizeSymbol(symbol)
-	tp, err = tracefsUprobe(args)
+	tp, err = tracefsProbe(args)
 	if err != nil {
 		return nil, fmt.Errorf("creating trace event '%s:%s' in tracefs: %w", ex.path, symbol, err)
 	}
 
 	return tp, nil
 }
-
-// pmuUprobe opens a perf event based on the uprobe PMU.
-func pmuUprobe(args tracefs.ProbeArgs) (*perfEvent, error) {
-	return pmuProbe(tracefs.UprobeType, args)
-}
-
-// tracefsUprobe creates a Uprobe tracefs entry.
-func tracefsUprobe(args tracefs.ProbeArgs) (*perfEvent, error) {
-	return tracefsProbe(tracefs.UprobeType, args)
-}
diff --git a/syscalls.go b/syscalls.go
index 44aa13f..03cd991 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -273,6 +273,7 @@ var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func
 	}
 
 	args := tracefs.ProbeArgs{
+		Type:   tracefs.Kprobe,
 		Symbol: testSyscallName,
 		Pid:    -1,
 	}
@@ -283,7 +284,7 @@ var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func
 		return err
 	}
 
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.KprobeType, args)
+	evt, err := tracefs.NewEvent(args)
 	if errors.Is(err, os.ErrNotExist) {
 		return internal.ErrNotSupported
 	}
@@ -291,5 +292,5 @@ var haveSyscallWrapper = internal.NewFeatureTest("syscall wrapper", "4.17", func
 		return err
 	}
 
-	return tracefs.CloseTraceFSProbeEvent(tracefs.KprobeType, args.Group, testSyscallName)
+	return evt.Close()
 })


Test patch:
diff --git a/internal/tracefs/kprobe_test.go b/internal/tracefs/kprobe_test.go
index 2fda308..6921410 100644
--- a/internal/tracefs/kprobe_test.go
+++ b/internal/tracefs/kprobe_test.go
@@ -1,7 +1,6 @@
 package tracefs
 
 import (
-	"errors"
 	"fmt"
 	"os"
 	"testing"
@@ -54,46 +53,27 @@ func TestKprobeToken(t *testing.T) {
 	}
 }
 
-// Test k(ret)probe creation writing directly to <tracefs>/kprobe_events.
-func TestKprobeCreateTraceFS(t *testing.T) {
-	c := qt.New(t)
-
-	pg, _ := RandomGroup("ebpftest")
-	rg, _ := RandomGroup("ebpftest")
-
-	// Tee up cleanups in case any of the Asserts abort the function.
-	defer func() {
-		_ = CloseTraceFSProbeEvent(KprobeType, pg, ksym)
-		_ = CloseTraceFSProbeEvent(KprobeType, rg, ksym)
-	}()
-
-	// Prepare probe args.
-	args := ProbeArgs{Group: pg, Symbol: ksym}
-
-	// Create a kprobe.
-	_, err := CreateTraceFSProbeEvent(KprobeType, args)
-	c.Assert(err, qt.IsNil)
-
-	// Attempt to create an identical kprobe using tracefs,
-	// expect it to fail with os.ErrExist.
-	_, err = CreateTraceFSProbeEvent(KprobeType, args)
-	c.Assert(errors.Is(err, os.ErrExist), qt.IsTrue,
-		qt.Commentf("expected consecutive kprobe creation to contain os.ErrExist, got: %v", err))
-
-	// Expect a successful close of the kprobe.
-	c.Assert(CloseTraceFSProbeEvent(KprobeType, pg, ksym), qt.IsNil)
-
-	args.Group = rg
-	args.Ret = true
-
-	// Same test for a kretprobe.
-	_, err = CreateTraceFSProbeEvent(KprobeType, args)
-	c.Assert(err, qt.IsNil)
-
-	_, err = CreateTraceFSProbeEvent(KprobeType, args)
-	c.Assert(os.IsExist(err), qt.IsFalse,
-		qt.Commentf("expected consecutive kretprobe creation to contain os.ErrExist, got: %v", err))
-
-	// Expect a successful close of the kretprobe.
-	c.Assert(CloseTraceFSProbeEvent(KprobeType, rg, ksym), qt.IsNil)
+func TestNewEvent(t *testing.T) {
+	for _, args := range []ProbeArgs{
+		{Type: Kprobe, Symbol: ksym},
+		{Type: Kprobe, Symbol: ksym, Ret: true},
+		{Type: Uprobe, Path: "/bin/bash", Symbol: "main"},
+		{Type: Uprobe, Path: "/bin/bash", Symbol: "main", Ret: true},
+	} {
+		name := fmt.Sprintf("%s ret=%v", args.Type, args.Ret)
+		t.Run(name, func(t *testing.T) {
+			args.Group, _ = RandomGroup("ebpftest")
+
+			evt, err := NewEvent(args)
+			qt.Assert(t, err, qt.IsNil)
+			defer evt.Close()
+
+			_, err = NewEvent(args)
+			qt.Assert(t, err, qt.ErrorIs, os.ErrExist,
+				qt.Commentf("expected consecutive event creation to contain os.ErrExist"))
+
+			qt.Assert(t, evt.Close(), qt.IsNil)
+			qt.Assert(t, evt.Close(), qt.ErrorIs, os.ErrClosed)
+		})
+	}
 }
diff --git a/internal/tracefs/perf_event_test.go b/internal/tracefs/perf_event_test.go
index 7b6ab56..ded1279 100644
--- a/internal/tracefs/perf_event_test.go
+++ b/internal/tracefs/perf_event_test.go
@@ -2,15 +2,16 @@ package tracefs
 
 import (
 	"errors"
+	"fmt"
 	"testing"
 
 	qt "github.com/frankban/quicktest"
 )
 
-func TestTraceEventID(t *testing.T) {
+func TestEventID(t *testing.T) {
 	c := qt.New(t)
 
-	eid, err := GetTraceEventID("syscalls", "sys_enter_mmap")
+	eid, err := EventID("syscalls", "sys_enter_mmap")
 	c.Assert(err, qt.IsNil)
 	c.Assert(eid, qt.Not(qt.Equals), 0)
 }
@@ -27,7 +28,7 @@ func TestSanitizePath(t *testing.T) {
 	}
 }
 
-func TestTraceValidID(t *testing.T) {
+func TestValidIdentifier(t *testing.T) {
 	tests := []struct {
 		name string
 		in   string
@@ -49,13 +50,36 @@ func TestTraceValidID(t *testing.T) {
 				exp = "fail"
 			}
 
-			if IsValidTraceID(tt.in) == tt.fail {
+			if validIdentifier(tt.in) == tt.fail {
 				t.Errorf("expected string '%s' to %s valid ID check", tt.in, exp)
 			}
 		})
 	}
 }
 
+func TestSanitizeIdentifier(t *testing.T) {
+	tests := []struct {
+		symbol   string
+		expected string
+	}{
+		{"readline", "readline"},
+		{"main.Func123", "main_Func123"},
+		{"a.....a", "a_a"},
+		{"./;'{}[]a", "_a"},
+		{"***xx**xx###", "_xx_xx_"},
+		{`@P#r$i%v^3*+t)i&k++--`, "_P_r_i_v_3_t_i_k_"},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprint(i), func(t *testing.T) {
+			sanitized := sanitizeIdentifier(tt.symbol)
+			if tt.expected != sanitized {
+				t.Errorf("Expected sanitized symbol to be '%s', got '%s'", tt.expected, sanitized)
+			}
+		})
+	}
+}
+
 func TestGetTracefsPath(t *testing.T) {
 	_, err := getTracefsPath()
 	qt.Assert(t, err, qt.IsNil)
diff --git a/internal/tracefs/tracepoint_test.go b/internal/tracefs/tracepoint_test.go
deleted file mode 100644
index 6286050..0000000
--- a/internal/tracefs/tracepoint_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package tracefs
-
-import (
-	"errors"
-	"os"
-	"testing"
-)
-
-func TestTraceGetEventID(t *testing.T) {
-	_, err := GetTraceEventID("syscalls", "sys_enter_openat")
-	if err != nil {
-		t.Fatal("Can't read trace event ID:", err)
-	}
-
-	_, err = GetTraceEventID("totally", "bogus")
-	if !errors.Is(err, os.ErrNotExist) {
-		t.Fatal("Expected os.ErrNotExist, got", err)
-	}
-}
diff --git a/internal/tracefs/uprobe_test.go b/internal/tracefs/uprobe_test.go
index eeeae35..6912734 100644
--- a/internal/tracefs/uprobe_test.go
+++ b/internal/tracefs/uprobe_test.go
@@ -27,26 +27,3 @@ func TestUprobeToken(t *testing.T) {
 		})
 	}
 }
-
-func TestUprobeSanitizedSymbol(t *testing.T) {
-	tests := []struct {
-		symbol   string
-		expected string
-	}{
-		{"readline", "readline"},
-		{"main.Func123", "main_Func123"},
-		{"a.....a", "a_a"},
-		{"./;'{}[]a", "_a"},
-		{"***xx**xx###", "_xx_xx_"},
-		{`@P#r$i%v^3*+t)i&k++--`, "_P_r_i_v_3_t_i_k_"},
-	}
-
-	for i, tt := range tests {
-		t.Run(fmt.Sprint(i), func(t *testing.T) {
-			sanitized := SanitizeSymbol(tt.symbol)
-			if tt.expected != sanitized {
-				t.Errorf("Expected sanitized symbol to be '%s', got '%s'", tt.expected, sanitized)
-			}
-		})
-	}
-}
diff --git a/link/kprobe_test.go b/link/kprobe_test.go
index d7d8ebf..bc1d6e7 100644
--- a/link/kprobe_test.go
+++ b/link/kprobe_test.go
@@ -147,14 +147,14 @@ func TestKprobeCreatePMU(t *testing.T) {
 	c := qt.New(t)
 
 	// kprobe happy path. printk is always present.
-	pk, err := pmuKprobe(tracefs.ProbeArgs{Symbol: ksym})
+	pk, err := pmuProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym})
 	c.Assert(err, qt.IsNil)
 	defer pk.Close()
 
 	c.Assert(pk.typ, qt.Equals, kprobeEvent)
 
 	// kretprobe happy path.
-	pr, err := pmuKprobe(tracefs.ProbeArgs{Symbol: ksym, Ret: true})
+	pr, err := pmuProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym, Ret: true})
 	c.Assert(err, qt.IsNil)
 	defer pr.Close()
 
@@ -162,12 +162,12 @@ func TestKprobeCreatePMU(t *testing.T) {
 
 	// Expect os.ErrNotExist when specifying a non-existent kernel symbol
 	// on kernels 4.17 and up.
-	_, err = pmuKprobe(tracefs.ProbeArgs{Symbol: "bogus"})
+	_, err = pmuProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: "bogus"})
 	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
 
 	// A kernel bug was fixed in 97c753e62e6c where EINVAL was returned instead
 	// of ENOENT, but only for kretprobes.
-	_, err = pmuKprobe(tracefs.ProbeArgs{Symbol: "bogus", Ret: true})
+	_, err = pmuProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: "bogus", Ret: true})
 	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
 }
 
@@ -175,7 +175,7 @@ func TestKprobeCreatePMU(t *testing.T) {
 func TestKprobePMUUnavailable(t *testing.T) {
 	c := qt.New(t)
 
-	pk, err := pmuKprobe(tracefs.ProbeArgs{Symbol: ksym})
+	pk, err := pmuProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym})
 	if err == nil {
 		pk.Close()
 		t.Skipf("Kernel supports perf_kprobe PMU, not asserting error.")
@@ -187,7 +187,7 @@ func TestKprobePMUUnavailable(t *testing.T) {
 
 func BenchmarkKprobeCreatePMU(b *testing.B) {
 	for n := 0; n < b.N; n++ {
-		pr, err := pmuKprobe(tracefs.ProbeArgs{Symbol: ksym})
+		pr, err := pmuProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym})
 		if err != nil {
 			b.Error("error creating perf_kprobe PMU:", err)
 		}
@@ -203,51 +203,51 @@ func TestKprobeTraceFS(t *testing.T) {
 	c := qt.New(t)
 
 	// Open and close tracefs k(ret)probes, checking all errors.
-	kp, err := tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym})
+	kp, err := tracefsProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym})
 	c.Assert(err, qt.IsNil)
 	c.Assert(kp.Close(), qt.IsNil)
 	c.Assert(kp.typ, qt.Equals, kprobeEvent)
 
-	kp, err = tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym, Ret: true})
+	kp, err = tracefsProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym, Ret: true})
 	c.Assert(err, qt.IsNil)
 	c.Assert(kp.Close(), qt.IsNil)
 	c.Assert(kp.typ, qt.Equals, kretprobeEvent)
 
 	// Create two identical trace events, ensure their IDs differ.
-	k1, err := tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym})
+	k1, err := tracefsProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym})
 	c.Assert(err, qt.IsNil)
 	defer k1.Close()
-	c.Assert(k1.tracefsID, qt.Not(qt.Equals), 0)
+	c.Assert(k1.tracefsEvent, qt.IsNotNil)
 
-	k2, err := tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym})
+	k2, err := tracefsProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym})
 	c.Assert(err, qt.IsNil)
 	defer k2.Close()
-	c.Assert(k2.tracefsID, qt.Not(qt.Equals), 0)
+	c.Assert(k2.tracefsEvent, qt.IsNotNil)
 
 	// Compare the kprobes' tracefs IDs.
-	c.Assert(k1.tracefsID, qt.Not(qt.CmpEquals()), k2.tracefsID)
+	c.Assert(k1.tracefsEvent.ID(), qt.Not(qt.Equals), k2.tracefsEvent.ID())
 
 	// Expect an error when supplying an invalid custom group name
-	_, err = tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym, Group: "/"})
+	_, err = tracefsProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym, Group: "/"})
 	c.Assert(err, qt.Not(qt.IsNil))
 
 	cg := "customgroup"
-	k3, err := tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym, Group: cg})
+	k3, err := tracefsProbe(tracefs.ProbeArgs{Type: tracefs.Kprobe, Symbol: ksym, Group: cg})
 	c.Assert(err, qt.IsNil)
 	defer k3.Close()
 	c.Assert(k3.group, qt.Matches, `customgroup_[a-f0-9]{16}`)
 
 	// Prepare probe args.
-	args := tracefs.ProbeArgs{Group: "testgroup", Symbol: "symbol"}
+	args := tracefs.ProbeArgs{Type: tracefs.Kprobe, Group: "testgroup", Symbol: "symbol"}
 
 	// Write a k(ret)probe event for a non-existing symbol.
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.KprobeType, args)
+	_, err = tracefs.NewEvent(args)
 	c.Assert(errors.Is(err, os.ErrNotExist), qt.IsTrue, qt.Commentf("got error: %s", err))
 
 	// A kernel bug was fixed in 97c753e62e6c where EINVAL was returned instead
 	// of ENOENT, but only for kretprobes.
 	args.Ret = true
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.KprobeType, args)
+	_, err = tracefs.NewEvent(args)
 	if !(errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL)) {
 		t.Fatal(err)
 	}
@@ -257,7 +257,7 @@ func BenchmarkKprobeCreateTraceFS(b *testing.B) {
 	for n := 0; n < b.N; n++ {
 		// Include <tracefs>/kprobe_events operations in the benchmark loop
 		// because we create one per perf event.
-		pr, err := tracefsKprobe(tracefs.ProbeArgs{Symbol: ksym})
+		pr, err := tracefsProbe(tracefs.ProbeArgs{Symbol: ksym})
 		if err != nil {
 			b.Error("error creating tracefs perf event:", err)
 		}
diff --git a/link/uprobe_test.go b/link/uprobe_test.go
index d891620..e666604 100644
--- a/link/uprobe_test.go
+++ b/link/uprobe_test.go
@@ -176,6 +176,7 @@ func TestUprobeCreatePMU(t *testing.T) {
 
 	// Prepare probe args.
 	args := tracefs.ProbeArgs{
+		Type:   tracefs.Uprobe,
 		Symbol: bashSym,
 		Path:   bashEx.path,
 		Offset: off,
@@ -183,7 +184,7 @@ func TestUprobeCreatePMU(t *testing.T) {
 	}
 
 	// uprobe PMU
-	pu, err := pmuUprobe(args)
+	pu, err := pmuProbe(args)
 	c.Assert(err, qt.IsNil)
 	defer pu.Close()
 
@@ -191,7 +192,7 @@ func TestUprobeCreatePMU(t *testing.T) {
 
 	// uretprobe PMU
 	args.Ret = true
-	pr, err := pmuUprobe(args)
+	pr, err := pmuProbe(args)
 	c.Assert(err, qt.IsNil)
 	defer pr.Close()
 
@@ -208,13 +209,14 @@ func TestUprobePMUUnavailable(t *testing.T) {
 
 	// Prepare probe args.
 	args := tracefs.ProbeArgs{
+		Type:   tracefs.Uprobe,
 		Symbol: bashSym,
 		Path:   bashEx.path,
 		Offset: off,
 		Pid:    perfAllThreads,
 	}
 
-	pk, err := pmuUprobe(args)
+	pk, err := pmuProbe(args)
 	if err == nil {
 		pk.Close()
 		t.Skipf("Kernel supports perf_uprobe PMU, not asserting error.")
@@ -234,107 +236,52 @@ func TestUprobeTraceFS(t *testing.T) {
 
 	// Prepare probe args.
 	args := tracefs.ProbeArgs{
-		Symbol: tracefs.SanitizeSymbol(bashSym),
+		Type:   tracefs.Uprobe,
+		Symbol: bashSym,
 		Path:   bashEx.path,
 		Offset: off,
 		Pid:    perfAllThreads,
 	}
 
 	// Open and close tracefs u(ret)probes, checking all errors.
-	up, err := tracefsUprobe(args)
+	up, err := tracefsProbe(args)
 	c.Assert(err, qt.IsNil)
 	c.Assert(up.Close(), qt.IsNil)
 	c.Assert(up.typ, qt.Equals, uprobeEvent)
 
 	args.Ret = true
-	up, err = tracefsUprobe(args)
+	up, err = tracefsProbe(args)
 	c.Assert(err, qt.IsNil)
 	c.Assert(up.Close(), qt.IsNil)
 	c.Assert(up.typ, qt.Equals, uretprobeEvent)
 
 	// Create two identical trace events, ensure their IDs differ.
 	args.Ret = false
-	u1, err := tracefsUprobe(args)
+	u1, err := tracefsProbe(args)
 	c.Assert(err, qt.IsNil)
 	defer u1.Close()
-	c.Assert(u1.tracefsID, qt.Not(qt.Equals), 0)
+	c.Assert(u1.tracefsEvent, qt.IsNotNil)
 
-	u2, err := tracefsUprobe(args)
+	u2, err := tracefsProbe(args)
 	c.Assert(err, qt.IsNil)
 	defer u2.Close()
-	c.Assert(u2.tracefsID, qt.Not(qt.Equals), 0)
+	c.Assert(u2.tracefsEvent, qt.IsNotNil)
 
 	// Compare the uprobes' tracefs IDs.
-	c.Assert(u1.tracefsID, qt.Not(qt.CmpEquals()), u2.tracefsID)
+	c.Assert(u1.tracefsEvent.ID(), qt.Not(qt.Equals), u2.tracefsEvent.ID())
 
 	// Expect an error when supplying an invalid custom group name
 	args.Group = "/"
-	_, err = tracefsUprobe(args)
+	_, err = tracefsProbe(args)
 	c.Assert(err, qt.Not(qt.IsNil))
 
 	args.Group = "customgroup"
-	u3, err := tracefsUprobe(args)
+	u3, err := tracefsProbe(args)
 	c.Assert(err, qt.IsNil)
 	defer u3.Close()
 	c.Assert(u3.group, qt.Matches, `customgroup_[a-f0-9]{16}`)
 }
 
-// Test u(ret)probe creation writing directly to <tracefs>/uprobe_events.
-func TestUprobeCreateTraceFS(t *testing.T) {
-	c := qt.New(t)
-
-	// Fetch the offset from the /bin/bash Executable already defined.
-	off, err := bashEx.address(bashSym, &UprobeOptions{})
-	c.Assert(err, qt.IsNil)
-
-	// Sanitize the symbol in order to be used in tracefs API.
-	ssym := tracefs.SanitizeSymbol(bashSym)
-
-	pg, _ := tracefs.RandomGroup("ebpftest")
-	rg, _ := tracefs.RandomGroup("ebpftest")
-
-	// Tee up cleanups in case any of the Asserts abort the function.
-	defer func() {
-		_ = tracefs.CloseTraceFSProbeEvent(tracefs.UprobeType, pg, ssym)
-		_ = tracefs.CloseTraceFSProbeEvent(tracefs.UprobeType, rg, ssym)
-	}()
-
-	// Prepare probe args.
-	args := tracefs.ProbeArgs{
-		Group:  pg,
-		Symbol: ssym,
-		Path:   bashEx.path,
-		Offset: off,
-	}
-
-	// Create a uprobe.
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.UprobeType, args)
-	c.Assert(err, qt.IsNil)
-
-	// Attempt to create an identical uprobe using tracefs,
-	// expect it to fail with os.ErrExist.
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.UprobeType, args)
-	c.Assert(errors.Is(err, os.ErrExist), qt.IsTrue,
-		qt.Commentf("expected consecutive uprobe creation to contain os.ErrExist, got: %v", err))
-
-	// Expect a successful close of the uprobe.
-	c.Assert(tracefs.CloseTraceFSProbeEvent(tracefs.UprobeType, pg, ssym), qt.IsNil)
-
-	args.Group = rg
-	args.Ret = true
-
-	// Same test for a kretprobe.
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.UprobeType, args)
-	c.Assert(err, qt.IsNil)
-
-	_, err = tracefs.CreateTraceFSProbeEvent(tracefs.UprobeType, args)
-	c.Assert(os.IsExist(err), qt.IsFalse,
-		qt.Commentf("expected consecutive uretprobe creation to contain os.ErrExist, got: %v", err))
-
-	// Expect a successful close of the uretprobe.
-	c.Assert(tracefs.CloseTraceFSProbeEvent(tracefs.UprobeType, rg, ssym), qt.IsNil)
-}
-
 func TestUprobeProgramCall(t *testing.T) {
 	tests := []struct {
 		name string