Thread (25 messages) 25 messages, 2 authors, 3d ago

Re: [PATCH v3 2/9] rv: add generic uprobe infrastructure for RV monitors

From: Gabriele Monaco <gmonaco@redhat.com>
Date: 2026-06-30 08:48:32
Also in: lkml

On Mon, 2026-06-29 at 00:47 +0800, Wen Yang wrote:
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)
If CPU B is in an RCU trace read-side critical section this doesn't
return immediately, it returns after readers are done, doesn't it?
(well, what you really care here is the synchronize_rcu_tasks_trace()
but we do both).
     ← idle; returns immediately
   [kworker fires later]
     kfree(uprobe)  ← frees &uprobe->consumers
                                         cons_node.next = freed mem → CRASH
So I had a bit of a look and as far as I understand, we don't have any
control over the uprobe allocation pattern (workqueues and whatnot) and
we don't really care as long as we deregister it appropriately.

What we do control is the uprobe_consumer, that must be freed only after
the uprobe was synchronously deregistered, that should guarantee no
reader is going to reference it, shouldn't it?

uprobe_unregister_nosync() removes it from the cons_node list, so it's
safe to assume any handler_chain() after the next RCU-trace grace period
won't see it.

In the sketch I sent this is happening (all kfree(b) are after
rv_uprobe_unregister() which does the sync). What am I missing here?


uprobe is also freed after both grace periods, so no reader should use
that either.

The situation you're seeing isn't fully clear to me, I applied my sketch
and don't see any splat on a vng box.
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 */
We of course need to do any free after this sync, but I don't get why we
need an additional allocation since the following are both plain
synchronous frees, why isn't a single one (on b) enough?

I'm a bit lost on this..

Thanks,
Gabriele
   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

quoted
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);
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help