Linux Trace Kernel
 help / color / mirror / Atom feed
From: Wen Yang <wen.yang@linux.dev>
To: Gabriele Monaco <gmonaco@redhat.com>
Cc: linux-trace-kernel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v3 2/9] rv: add generic uprobe infrastructure for RV monitors
Date: Mon, 29 Jun 2026 00:47:56 +0800	[thread overview]
Message-ID: <878be1d4-2f93-4fbd-a1f6-b2b7836c9c44@linux.dev> (raw)
In-Reply-To: <a645534b04d9bf32f2c2caa5d2206bb87cfcb3e1.camel@redhat.com>



On 6/16/26 17:49, Gabriele Monaco wrote:
> On Mon, 2026-06-08 at 00:13 +0800, wen.yang@linux.dev wrote:
>> From: Wen Yang <wen.yang@linux.dev>
>>
>> Introduce rv_uprobe, a thin wrapper around uprobe_consumer providing
>> rv_uprobe_attach_path(), rv_uprobe_attach(), and rv_uprobe_detach()
>> for RV monitors.  An opaque priv pointer is forwarded unchanged to
>> entry/return handlers so monitors can carry per-binding state (e.g. a
>> latency threshold) to the hot path without any global lookup.
>>
>> rv_uprobe_detach() is fully synchronous (nosync + sync + path_put +
>> kfree), closing the use-after-free window present in open-coded
>> patterns where kfree() precedes uprobe_unregister_sync().
>>
>> Suggested-by: Gabriele Monaco <gmonaco@redhat.com>
>> Signed-off-by: Wen Yang <wen.yang@linux.dev>
> 
> I find your implementation solid, but I wonder if it isn't a bit
> overdoing it. It's good to have helper functions abstracting away the
> common uprobes code, but I find the number of structures used to indirect/hide
> the process a bit confusing.
> 
> Users of this headers still know they're using uprobes, so we don't
> really need to hide it that much, plus the double function pointers are
> an additional performance hit we could easily skip.
> 
> As I get it, we want to pass a private pointer (essentially to get the
> threshold in tlob) and the uprobe infrastructure doesn't allow that
> transparently. So let's keep on using struct embedding but a bit more
> cut to the bone:
> 
> 	struct rv_uprobe {
> 		struct uprobe_consumer	uc;
> 		struct uprobe		*uprobe;
> 		void			*priv;
> 	};
> 
> Then handlers would use container_of() appropriately to derive the data they
> need.
> 
> 	static int tlob_uprobe_entry_handler(struct uprobe_consumer *self, struct pt_regs *regs, __u64 *data)
> 	{
> 		struct rv_uprobe *p = container_of(self, struct rv_uprobe, uc);
> 		struct tlob_uprobe_binding *b = p->priv;
> 
> 		...
> 	}
> 
> You could even define the struct directly inside your private data and save one
> step (and no void *):
> 
> 	struct rv_uprobe {
> 		struct uprobe_consumer	uc;
> 		struct uprobe		*uprobe;
> 	};
> 
> 	#define DECLARE_RV_UPROBE(name) struct rv_uprobe name
> 
> 	// And then
> 
> 	struct tlob_uprobe_binding {
> 		u64 threshold_ns;
> 		...
> 		DECLARE_RV_UPROBE(start);
> 		DECLARE_RV_UPROBE(stop);
> 	}
> 
> 	static int tlob_uprobe_entry_handler(struct uprobe_consumer *self, struct pt_regs *regs, __u64 *data)
> 	{
> 		struct tlob_uprobe_binding *b = container_of(self, struct tlob_uprobe_binding, start.uc);
> 
> 		...
> 	}
> 
> 
> Now I intentionally skipped offset, as I don't really see why carry it around,
> and path, as we could live without it by relying on inodes (uprobes
> reference-track them but you'd need to save them somewhere).
> 
> Mind, I only build tested this idea (appended after). This was supported by an
> LLM which did a few more changes like standardising names and return values of
> registration functions (a bit more consistent with other stuff, you don't have
> to commit with that though).
> 
> By the way, do we really need the duplicate check? Would it break if a user
> defines it twice? If not, we could simplify the tlob_add_uprobe() even further
> and doing all path related checks in the library, since those aren't quite tlob
> specific.
> 
> Thoughts?
> 

Hi Gabriele,

Thanks for the detailed v3 review.  All feedback is addressed in v4;
only one item ([2-1]) required a design deviation due to a UAF found 
during PREEMPT_RT testing.

On [2-1]~[2-5]: embedded consumer causes UAF on PREEMPT_RT
-----------------------------------------------------------

The uprobe_bind selftest oopses on PREEMPT_RT(full):

   handler_chain+0xc9: mov rax, [r15+0x18]  ; advance list iterator
   RAX: 000015ec00001f28   ; garbage — &uprobe->consumers after kfree

handler_chain() reads uc->cons_node.next after uc->handler() returns,
still inside rcu_read_lock_trace().  That pointer is &uprobe->consumers
(the list head embedded in struct uprobe), which gets freed through:

   put_uprobe()
     -> schedule_work(uprobe_free_deferred)   /* async */
        -> call_srcu(&uretprobes_srcu, ...)
           -> call_rcu_tasks_trace(kfree_uprobe)
              -> kfree(uprobe)

rv_uprobe_sync() calls uprobe_unregister_sync() which calls
synchronize_srcu(&uretprobes_srcu), but that only matters after the
kworker has submitted work to uretprobes_srcu.  On a loaded PREEMPT_RT
box the kworker may not have run yet, so synchronize_srcu() returns
immediately and kfree(uprobe) races with the still-iterating
handler_chain():

   CPU A                                CPU B
   consumer_del() → list_del_rcu        rcu_read_lock_trace()
   put_uprobe()   → schedule_work         uc->handler() returns
   rv_uprobe_sync():                       reading cons_node.next...
     synchronize_srcu(&uretprobes_srcu)
     ← idle; returns immediately
   [kworker fires later]
     kfree(uprobe)  ← frees &uprobe->consumers
                                         cons_node.next = freed mem → CRASH

With uc embedded in the binding (as [2-1] suggests), no amount of
delaying kfree(binding) helps: uprobe->consumers is freed by a chain we
don't control.  The fix is to keep uc->cons_node in memory that outlives
the handler_chain() iteration, which means a separate allocation freed
only after rv_uprobe_sync():

   rv_uprobe_unregister_nosync()  /* list_del_rcu + schedule_work */
   rv_uprobe_sync()               /* waits for any already-submitted 
srcu work */
   rv_uprobe_free()               /* kfree(impl) — safe, iteration is 
done */
   kfree(b)                       /* binding; never contained uc */

v4 keeps the public API shape you suggested with impl private:

   struct rv_uprobe {
       struct rv_uprobe_impl  *impl;  /* allocated by 
rv_uprobe_register() */
       struct inode           *inode;
   };
   #define DECLARE_RV_UPROBE(name)  struct rv_uprobe name        /* [2-2] */

   int   rv_uprobe_register(const char *binpath, loff_t offset,
                            struct rv_uprobe *p, handler_fn,
                            ret_handler_fn, void *priv);         /* [2-4] */
   void  rv_uprobe_unregister(struct rv_uprobe *p);
   void  rv_uprobe_unregister_nosync(struct rv_uprobe *p);
   void  rv_uprobe_sync(void);
   void  rv_uprobe_free(struct rv_uprobe *p);                    /* 
[2-5] restored */
   bool  rv_uprobe_is_registered(const struct rv_uprobe *p);     /* 
[2-7] added */
   void *rv_uprobe_get_priv(struct uprobe_consumer *uc);

Handler signature is (struct uprobe_consumer *self, ...) [2-3]; private
data is retrieved via rv_uprobe_get_priv(self) instead of container_of().

Then, rv_uprobe_free() is restored ([2-5] partially reverted).


All other v3 items have been resolved, we are waiting for your comments 
on the above(embedded consumer causes UAF):

[1-1,1-3]  `# define` / `# error` space removed
[1-4]      `⟹` -> `=>`
[1-6,1-7]  #if/#else in da_destroy_storage() and da_monitor_init()
            replaced with plain if (DA_MON_ALLOCATION_STRATEGY == 
DA_ALLOC_POOL)
[1-8]      tracepoint_synchronize_unregister() lifted to 
da_monitor_destroy before the pool/kmalloc branch

[2-2]  DECLARE_RV_UPROBE(name) — kept
[2-3]  handler sig (struct uprobe_consumer *self, ...) — kept
[2-4]  rv_uprobe_register() returns int — kept
[2-5]  rv_uprobe_attach/detach removed; rv_uprobe_free() restored (see 
above)
[2-6]  offset, path fields removed; inode used for identity
[2-7]  rv_uprobe_is_registered() added

[6-1]  Suggested-by removed
[6-2]  tlob Kconfig entry placed after the deadline monitors marker
[6-3]  Unnecessary includes removed (hrtimer.h, ktime.h, sched.h, tracefs.h)
[6-4]  `#include "../../rv.h"` -> `<rv.h>`
[6-5,6-6]  Verbose comments around DA_MON_POOL_SIZE and da_extra_cleanup
            simplified; early return in tlob_reset_notify() on
            !trace_detail_env_tlob_enabled()
[6-7]  ha_setup_invariants():
          if (atomic_read_acquire(&target->stopping)) return;
          if (next_state < state_max_tlob) ha_start_timer_ns(...);
          else ha_cancel_timer(ha_mon);
[6-8]  offsetof() arithmetic -> enum-indexed array:
          enum tlob_acc_idx { TLOB_ACC_RUNNING, TLOB_ACC_WAITING,
                              TLOB_ACC_SLEEPING, TLOB_ACC_MAX };
          u64 accs_ns[TLOB_ACC_MAX];
[6-9]  1000 → TLOB_MIN_THRESHOLD_NS
[6-10] WARN_ON_ONCE(!da_mon) -> if (unlikely(WARN_ON_ONCE(!da_mon)))
[6-11] __free(kfree) + list_add_tail(&no_free_ptr(b)->list, ...) in
        tlob_add_uprobe(); note that "b = no_free_ptr(b)" would restore b
        and re-trigger __free on return — the correct pattern is to use
        no_free_ptr() as the argument to list_add_tail() directly
[6-15] tlob.h enum/struct order aligned with rvgen output
[6-16] tracefs_create_file() -> rv_create_file()

[7-1]  KUnit tests call tlob_parse_uprobe_line/tlob_parse_remove_line
        directly; no real uprobe creation from unit tests
[7-2]  KUNIT_EXPECT_EQ(test, ..., 0) for valid-line cases

[8-1]  ftracetest: walk-up algorithm to locate test.d/functions
[9-4]  tlob_busy/sleep/preempt_work unified to duration_ms


--
Best wishes,
Wen


> Gabriele
> 
> **Suggested simplification:**
> 
> ---
> diff --git a/include/rv/rv_uprobe.h b/include/rv/rv_uprobe.h
> index 9106c5c927..4fb3f50a63 100644
> --- a/include/rv/rv_uprobe.h
> +++ b/include/rv/rv_uprobe.h
> @@ -7,83 +7,56 @@
>   #ifndef _RV_UPROBE_H
>   #define _RV_UPROBE_H
>   
> -#include <linux/path.h>
>   #include <linux/types.h>
> +#include <linux/uprobes.h>
>   
>   struct pt_regs;
> +struct inode;
>   
>   /**
>    * struct rv_uprobe - a single uprobe registered on behalf of an RV monitor
>    *
> - * @offset:   byte offset within the ELF binary where the probe is installed
> - * @priv:     monitor-private pointer; set at attach time, never touched by
> - *            this layer; passed unchanged to entry_fn / ret_fn
> - * @path:     resolved path of the probed binary (read-only after attach);
> - *            callers may use path.dentry for identity comparisons
> - *
> - * The implementation fields (uprobe_consumer, uprobe handle, callbacks) are
> - * private to rv_uprobe.c and are not exposed here; monitors must not access
> - * them directly.
> + * @uc:       underlying uprobe consumer (publicly visible)
> + * @uprobe:   active uprobe structure handle
> + * @inode:    inode of the target binary (read-only after registration)
>    */
>   struct rv_uprobe {
> -	/* public: read-only after rv_uprobe_attach*() */
> -	loff_t		 offset;
> -	void		*priv;
> -	struct path	 path;
> +	struct uprobe_consumer	uc;
> +	struct uprobe		*uprobe;
> +	struct inode		*inode;
>   };
>   
> -/**
> - * rv_uprobe_attach_path - register an uprobe given an already-resolved path
> - * @path:     path of the target binary; rv_uprobe takes its own reference
> - * @offset:   byte offset within the binary
> - * @entry_fn: called on probe hit (entry); may be NULL
> - * @ret_fn:   called on function return (uretprobe); may be NULL
> - * @priv:     opaque pointer forwarded to callbacks unchanged
> - *
> - * Use this variant when the caller has already resolved the path (e.g. to
> - * register multiple probes on the same binary with a single kern_path call).
> - * The inode is derived internally via d_real_inode(), so inode and path are
> - * always consistent.
> - *
> - * Returns a pointer to the new rv_uprobe on success, ERR_PTR on failure.
> - */
> -struct rv_uprobe *rv_uprobe_attach_path(struct path *path, loff_t offset,
> -	int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64 *data),
> -	int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> -			struct pt_regs *regs, __u64 *data),
> -	void *priv);
> +/* Seamless inline declaration of a named uprobe inside user structs */
> +#define DECLARE_RV_UPROBE(name) \
> +	struct rv_uprobe name
>   
>   /**
> - * rv_uprobe_attach - resolve binpath and register an uprobe
> - * @binpath:  absolute path to the target binary
> - * @offset:   byte offset within the binary
> - * @entry_fn: called on probe hit (entry); may be NULL
> - * @ret_fn:   called on function return (uretprobe); may be NULL
> - * @priv:     opaque pointer forwarded to callbacks unchanged
> + * rv_uprobe_register - resolve binpath and register an uprobe
> + * @binpath:     absolute path to the target binary
> + * @offset:      byte offset within the binary
> + * @p:           pointer to the allocated/embedded rv_uprobe structure
> + * @handler:     called on probe hit (entry); may be NULL
> + * @ret_handler: called on function return (uretprobe); may be NULL
>    *
> - * Resolves binpath via kern_path(), then delegates to rv_uprobe_attach_path().
> + * Resolves binpath via kern_path(), registers the uprobe directly using the
> + * embedded `uprobe_consumer`, and immediately releases the path reference.
>    *
> - * Returns a pointer to the new rv_uprobe on success, ERR_PTR on failure.
> + * Returns 0 on success, or a negative error code on failure.
>    */
> -struct rv_uprobe *rv_uprobe_attach(const char *binpath, loff_t offset,
> -	int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64 *data),
> -	int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> -			struct pt_regs *regs, __u64 *data),
> -	void *priv);
> +int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe *p,
> +	int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs, __u64 *data),
> +	int (*ret_handler)(struct uprobe_consumer *self, unsigned long func,
> +			   struct pt_regs *regs, __u64 *data));
>   
>   /**
> - * rv_uprobe_detach - synchronously unregister an uprobe and free it
> - * @p:  probe to detach; may be NULL (no-op)
> - *
> - * Calls uprobe_unregister_nosync(), then uprobe_unregister_sync() to wait
> - * for any in-progress handler to finish, then releases the path reference
> - * and frees the rv_uprobe struct.  The caller's priv data is NOT freed.
> + * rv_uprobe_unregister - synchronously unregister a uprobe
> + * @p:  probe to unregister; may be NULL (no-op)
>    *
> - * When removing a single probe, prefer this over the three-phase API.
> - * Safe to call from process context only (uprobe_unregister_sync() may
> - * schedule).
> + * Dequeues the uprobe and waits synchronously for all in-flight handlers
> + * to complete.
>    */
> -void rv_uprobe_detach(struct rv_uprobe *p);
> +void rv_uprobe_unregister(struct rv_uprobe *p);
>   
>   /**
>    * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting
> @@ -91,9 +64,7 @@ void rv_uprobe_detach(struct rv_uprobe *p);
>    *
>    * Removes the uprobe from the uprobe subsystem but does NOT wait for
>    * in-flight handlers to complete.  The caller must call rv_uprobe_sync()
> - * before calling rv_uprobe_free() on the same probe.
> - *
> - * Use this to batch multiple deregistrations before a single rv_uprobe_sync().
> + * before freeing any container holding this probe.
>    */
>   void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
>   
> @@ -101,19 +72,8 @@ void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
>    * rv_uprobe_sync - wait for all in-flight uprobe handlers to complete
>    *
>    * Global barrier: waits for every in-flight uprobe handler across the system
> - * to finish.  Call once after a batch of rv_uprobe_unregister_nosync() calls
> - * and before any rv_uprobe_free() call.
> + * to finish.
>    */
>   void rv_uprobe_sync(void);
>   
> -/**
> - * rv_uprobe_free - release resources of a previously deregistered probe
> - * @p:  probe to free; may be NULL (no-op)
> - *
> - * Releases the path reference and frees the rv_uprobe struct.  Must only
> - * be called after rv_uprobe_sync() has returned.  The caller's priv data
> - * is NOT freed.
> - */
> -void rv_uprobe_free(struct rv_uprobe *p);
> -
>   #endif /* _RV_UPROBE_H */
> diff --git a/kernel/trace/rv/monitors/tlob/tlob.c b/kernel/trace/rv/monitors/tlob/tlob.c
> index d8e0c47947..28a6c740c7 100644
> --- a/kernel/trace/rv/monitors/tlob/tlob.c
> +++ b/kernel/trace/rv/monitors/tlob/tlob.c
> @@ -252,8 +252,8 @@ struct tlob_uprobe_binding {
>   	char			binpath[TLOB_MAX_PATH];
>   	loff_t			offset_start;
>   	loff_t			offset_stop;
> -	struct rv_uprobe	*start_probe;
> -	struct rv_uprobe	*stop_probe;
> +	DECLARE_RV_UPROBE(start_probe);
> +	DECLARE_RV_UPROBE(stop_probe);
>   };
>   
>   /* RCU callback: free the slab once no readers remain. */
> @@ -512,16 +512,16 @@ int tlob_stop_task(struct task_struct *task)
>   EXPORT_SYMBOL_GPL(tlob_stop_task);
>   
>   
> -static int tlob_uprobe_entry_handler(struct rv_uprobe *p, struct pt_regs *regs,
> +static int tlob_uprobe_entry_handler(struct uprobe_consumer *self, struct pt_regs *regs,
>   				     __u64 *data)
>   {
> -	struct tlob_uprobe_binding *b = p->priv;
> +	struct tlob_uprobe_binding *b = container_of(self, struct tlob_uprobe_binding, start_probe.uc);
>   
>   	tlob_start_task(current, b->threshold_ns);
>   	return 0;
>   }
>   
> -static int tlob_uprobe_stop_handler(struct rv_uprobe *p, struct pt_regs *regs,
> +static int tlob_uprobe_stop_handler(struct uprobe_consumer *self, struct pt_regs *regs,
>   				    __u64 *data)
>   {
>   	tlob_stop_task(current);
> @@ -537,6 +537,7 @@ static int tlob_add_uprobe(u64 threshold_ns, const char *binpath,
>   {
>   	struct tlob_uprobe_binding *b, *tmp_b;
>   	char pathbuf[TLOB_MAX_PATH];
> +	struct inode *inode;
>   	struct path path;
>   	char *canon;
>   	int ret;
> @@ -561,10 +562,12 @@ static int tlob_add_uprobe(u64 threshold_ns, const char *binpath,
>   		goto err_path;
>   	}
>   
> -	/* Reject duplicate start offset for the same binary. */
> +	inode = d_real_inode(path.dentry);
> +
> +	/* Reject duplicate start offset for the same binary inode. */
>   	list_for_each_entry(tmp_b, &tlob_uprobe_list, list) {
>   		if (tmp_b->offset_start == offset_start &&
> -		    tmp_b->start_probe->path.dentry == path.dentry) {
> +		    tmp_b->start_probe.inode == inode) {
>   			ret = -EEXIST;
>   			goto err_path;
>   		}
> @@ -577,29 +580,25 @@ static int tlob_add_uprobe(u64 threshold_ns, const char *binpath,
>   	}
>   	strscpy(b->binpath, canon, sizeof(b->binpath));
>   
> -	/* Both probes share b (priv) and path; attach_path refs path itself. */
> -	b->start_probe = rv_uprobe_attach_path(&path, offset_start,
> -					       tlob_uprobe_entry_handler, NULL, b);
> -	if (IS_ERR(b->start_probe)) {
> -		ret = PTR_ERR(b->start_probe);
> -		b->start_probe = NULL;
> -		goto err_path;
> -	}
> +	path_put(&path);
> +
> +	/* Both probes are registered directly on the embedded fields */
> +	ret = rv_uprobe_register(b->binpath, offset_start, &b->start_probe,
> +				 tlob_uprobe_entry_handler, NULL);
> +	if (ret)
> +		goto err_free;
>   
> -	b->stop_probe = rv_uprobe_attach_path(&path, offset_stop,
> -					      tlob_uprobe_stop_handler, NULL, b);
> -	if (IS_ERR(b->stop_probe)) {
> -		ret = PTR_ERR(b->stop_probe);
> -		b->stop_probe = NULL;
> +	ret = rv_uprobe_register(b->binpath, offset_stop, &b->stop_probe,
> +				 tlob_uprobe_stop_handler, NULL);
> +	if (ret)
>   		goto err_start;
> -	}
>   
> -	path_put(&path);
>   	list_add_tail(&b->list, &tlob_uprobe_list);
>   	return 0;
>   
>   err_start:
> -	rv_uprobe_detach(b->start_probe);
> +	rv_uprobe_unregister(&b->start_probe);
> +	goto err_free;
>   err_path:
>   	path_put(&path);
>   err_free:
> @@ -611,21 +610,24 @@ static int tlob_remove_uprobe_by_key(loff_t offset_start, const char *binpath)
>   {
>   	struct tlob_uprobe_binding *b, *tmp;
>   	struct path remove_path;
> +	struct inode *inode;
>   	int ret;
>   
>   	ret = kern_path(binpath, LOOKUP_FOLLOW, &remove_path);
>   	if (ret)
>   		return ret;
>   
> +	inode = d_real_inode(remove_path.dentry);
> +
>   	ret = -ENOENT;
>   	list_for_each_entry_safe(b, tmp, &tlob_uprobe_list, list) {
>   		if (b->offset_start != offset_start)
>   			continue;
> -		if (b->start_probe->path.dentry != remove_path.dentry)
> +		if (b->start_probe.inode != inode)
>   			continue;
>   		list_del(&b->list);
> -		rv_uprobe_detach(b->start_probe);
> -		rv_uprobe_detach(b->stop_probe);
> +		rv_uprobe_unregister(&b->start_probe);
> +		rv_uprobe_unregister(&b->stop_probe);
>   		kfree(b);
>   		ret = 0;
>   		break;
> @@ -643,8 +645,8 @@ static void tlob_remove_all_uprobes(void)
>   	mutex_lock(&tlob_uprobe_mutex);
>   	list_for_each_entry_safe(b, tmp, &tlob_uprobe_list, list) {
>   		list_move(&b->list, &pending);
> -		rv_uprobe_unregister_nosync(b->start_probe);
> -		rv_uprobe_unregister_nosync(b->stop_probe);
> +		rv_uprobe_unregister_nosync(&b->start_probe);
> +		rv_uprobe_unregister_nosync(&b->stop_probe);
>   	}
>   	mutex_unlock(&tlob_uprobe_mutex);
>   
> @@ -658,8 +660,6 @@ static void tlob_remove_all_uprobes(void)
>   	rv_uprobe_sync();
>   
>   	list_for_each_entry_safe(b, tmp, &pending, list) {
> -		rv_uprobe_free(b->start_probe);
> -		rv_uprobe_free(b->stop_probe);
>   		kfree(b);
>   	}
>   }
> diff --git a/kernel/trace/rv/rv_uprobe.c b/kernel/trace/rv/rv_uprobe.c
> index 3d8b764dde..69b8b0c27e 100644
> --- a/kernel/trace/rv/rv_uprobe.c
> +++ b/kernel/trace/rv/rv_uprobe.c
> @@ -10,149 +10,74 @@
>   #include <linux/uprobes.h>
>   #include <rv/rv_uprobe.h>
>   
> -/*
> - * Private extension of struct rv_uprobe.  Allocated by rv_uprobe_attach*()
> - * and returned to callers as &impl->pub.
> - */
> -struct rv_uprobe_impl {
> -	struct rv_uprobe	pub;	/* must be first; callers hold &pub */
> -	struct uprobe_consumer	uc;
> -	struct uprobe		*uprobe;
> -	int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64 *data);
> -	int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> -			struct pt_regs *regs, __u64 *data);
> -};
> -
> -static int rv_uprobe_handler(struct uprobe_consumer *uc,
> -			     struct pt_regs *regs, __u64 *data)
> -{
> -	struct rv_uprobe_impl *impl = container_of(uc, struct rv_uprobe_impl, uc);
> -
> -	if (impl->entry_fn)
> -		return impl->entry_fn(&impl->pub, regs, data);
> -	return 0;
> -}
> -
> -static int rv_uprobe_ret_handler(struct uprobe_consumer *uc,
> -				 unsigned long func,
> -				 struct pt_regs *regs, __u64 *data)
> -{
> -	struct rv_uprobe_impl *impl = container_of(uc, struct rv_uprobe_impl, uc);
> -
> -	if (impl->ret_fn)
> -		return impl->ret_fn(&impl->pub, func, regs, data);
> -	return 0;
> -}
> -
> -static struct rv_uprobe *
> -__rv_uprobe_attach(struct inode *inode, struct path *path, loff_t offset,
> -		   int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64 *data),
> -		   int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> -				   struct pt_regs *regs, __u64 *data),
> -		   void *priv)
> -{
> -	struct rv_uprobe_impl *impl;
> -	int ret;
> -
> -	if (!entry_fn && !ret_fn)
> -		return ERR_PTR(-EINVAL);
> -
> -	impl = kzalloc_obj(*impl, GFP_KERNEL);
> -	if (!impl)
> -		return ERR_PTR(-ENOMEM);
> -
> -	impl->pub.offset = offset;
> -	impl->pub.priv   = priv;
> -	impl->entry_fn   = entry_fn;
> -	impl->ret_fn     = ret_fn;
> -	path_get(path);
> -	impl->pub.path   = *path;
> -
> -	if (entry_fn)
> -		impl->uc.handler     = rv_uprobe_handler;
> -	if (ret_fn)
> -		impl->uc.ret_handler = rv_uprobe_ret_handler;
> -
> -	impl->uprobe = uprobe_register(inode, offset, 0, &impl->uc);
> -	if (IS_ERR(impl->uprobe)) {
> -		ret = PTR_ERR(impl->uprobe);
> -		path_put(&impl->pub.path);
> -		kfree(impl);
> -		return ERR_PTR(ret);
> -	}
> -
> -	return &impl->pub;
> -}
> -
> -/**
> - * rv_uprobe_attach_path - register an uprobe given an already-resolved path
> - */
> -struct rv_uprobe *rv_uprobe_attach_path(struct path *path, loff_t offset,
> -	int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64 *data),
> -	int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> -			struct pt_regs *regs, __u64 *data),
> -	void *priv)
> -{
> -	struct inode *inode = d_real_inode(path->dentry);
> -
> -	return __rv_uprobe_attach(inode, path, offset, entry_fn, ret_fn, priv);
> -}
> -EXPORT_SYMBOL_GPL(rv_uprobe_attach_path);
> -
>   /**
> - * rv_uprobe_attach - resolve binpath and register an uprobe
> + * rv_uprobe_register - resolve binpath and register an uprobe
>    */
> -struct rv_uprobe *rv_uprobe_attach(const char *binpath, loff_t offset,
> -	int (*entry_fn)(struct rv_uprobe *p, struct pt_regs *regs, __u64 *data),
> -	int (*ret_fn)(struct rv_uprobe *p, unsigned long func,
> -			struct pt_regs *regs, __u64 *data),
> -	void *priv)
> +int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe *p,
> +	int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs, __u64 *data),
> +	int (*ret_handler)(struct uprobe_consumer *self, unsigned long func,
> +			   struct pt_regs *regs, __u64 *data))
>   {
> -	struct rv_uprobe *p;
> +	struct inode *inode;
>   	struct path path;
>   	int ret;
>   
> +	if (!handler && !ret_handler)
> +		return -EINVAL;
> +
>   	ret = kern_path(binpath, LOOKUP_FOLLOW, &path);
>   	if (ret)
> -		return ERR_PTR(ret);
> +		return ret;
>   
>   	if (!d_is_reg(path.dentry)) {
>   		path_put(&path);
> -		return ERR_PTR(-EINVAL);
> +		return -EINVAL;
>   	}
>   
> -	p = rv_uprobe_attach_path(&path, offset, entry_fn, ret_fn, priv);
> +	inode = d_real_inode(path.dentry);
> +
> +	p->uc.handler     = handler;
> +	p->uc.ret_handler = ret_handler;
> +	p->inode          = inode;
> +
> +	p->uprobe = uprobe_register(inode, offset, 0, &p->uc);
>   	path_put(&path);
> -	return p;
> +
> +	if (IS_ERR(p->uprobe)) {
> +		ret = PTR_ERR(p->uprobe);
> +		p->uprobe = NULL;
> +		p->inode  = NULL;
> +		return ret;
> +	}
> +
> +	return 0;
>   }
> -EXPORT_SYMBOL_GPL(rv_uprobe_attach);
> +EXPORT_SYMBOL_GPL(rv_uprobe_register);
>   
>   /**
> - * rv_uprobe_detach - synchronously unregister an uprobe and free it
> + * rv_uprobe_unregister - synchronously unregister a uprobe
>    */
> -void rv_uprobe_detach(struct rv_uprobe *p)
> +void rv_uprobe_unregister(struct rv_uprobe *p)
>   {
> -	if (!p)
> +	if (!p || IS_ERR_OR_NULL(p->uprobe))
>   		return;
>   
>   	rv_uprobe_unregister_nosync(p);
>   	rv_uprobe_sync();
> -	rv_uprobe_free(p);
>   }
> -EXPORT_SYMBOL_GPL(rv_uprobe_detach);
> +EXPORT_SYMBOL_GPL(rv_uprobe_unregister);
>   
>   /**
>    * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting
>    */
>   void rv_uprobe_unregister_nosync(struct rv_uprobe *p)
>   {
> -	struct rv_uprobe_impl *impl;
> -
> -	if (!p)
> +	if (!p || IS_ERR_OR_NULL(p->uprobe))
>   		return;
>   
> -	impl = container_of(p, struct rv_uprobe_impl, pub);
> -	uprobe_unregister_nosync(impl->uprobe, &impl->uc);
> +	uprobe_unregister_nosync(p->uprobe, &p->uc);
> +	p->uprobe = NULL;
> +	p->inode  = NULL;
>   }
>   EXPORT_SYMBOL_GPL(rv_uprobe_unregister_nosync);
>   
> @@ -164,19 +89,3 @@ void rv_uprobe_sync(void)
>   	uprobe_unregister_sync();
>   }
>   EXPORT_SYMBOL_GPL(rv_uprobe_sync);
> -
> -/**
> - * rv_uprobe_free - release resources of a previously deregistered probe
> - */
> -void rv_uprobe_free(struct rv_uprobe *p)
> -{
> -	struct rv_uprobe_impl *impl;
> -
> -	if (!p)
> -		return;
> -
> -	impl = container_of(p, struct rv_uprobe_impl, pub);
> -	path_put(&p->path);
> -	kfree(impl);
> -}
> -EXPORT_SYMBOL_GPL(rv_uprobe_free);
> 

  reply	other threads:[~2026-06-28 16:48 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-07 16:13 [PATCH v3 0/9] rv/tlob: Add task latency over budget RV monitor wen.yang
2026-06-07 16:13 ` [PATCH v3 1/9] rv/da: introduce DA_MON_ALLOCATION_STRATEGY wen.yang
2026-06-15  9:56   ` Gabriele Monaco
2026-06-07 16:13 ` [PATCH v3 2/9] rv: add generic uprobe infrastructure for RV monitors wen.yang
2026-06-16  9:49   ` Gabriele Monaco
2026-06-28 16:47     ` Wen Yang [this message]
2026-06-07 16:13 ` [PATCH v3 3/9] rv/tlob: add tlob model DOT file wen.yang
2026-06-07 16:13 ` [PATCH v3 4/9] rv/ha: fix ha_invariant_passed_ns silent bypass of invariant check wen.yang
2026-06-15 10:12   ` Gabriele Monaco
2026-06-07 16:13 ` [PATCH v3 5/9] rv/ha: make da_monitor_reset_hook and EVENT_NONE_LBL overridable wen.yang
2026-06-15 10:16   ` Gabriele Monaco
2026-06-07 16:13 ` [PATCH v3 6/9] rv/tlob: add tlob hybrid automaton monitor wen.yang
2026-06-15 15:24   ` Gabriele Monaco
2026-06-07 16:13 ` [PATCH v3 7/9] rv/tlob: add KUnit tests for the tlob monitor wen.yang
2026-06-17  7:49   ` Gabriele Monaco
2026-06-07 16:13 ` [PATCH v3 8/9] selftests/verification: fix verificationtest-ktap for out-of-tree execution wen.yang
2026-06-16 11:14   ` Gabriele Monaco
2026-06-07 16:13 ` [PATCH v3 9/9] selftests/verification: add tlob selftests wen.yang
2026-06-16 14:58   ` Gabriele Monaco
2026-06-17 15:09   ` Gabriele Monaco
2026-06-22  9:26   ` Gabriele Monaco
2026-06-13 16:00 ` [PATCH v3 0/9] rv/tlob: Add task latency over budget RV monitor Wen Yang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=878be1d4-2f93-4fbd-a1f6-b2b7836c9c44@linux.dev \
    --to=wen.yang@linux.dev \
    --cc=gmonaco@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox