From: Wen Yang <[email protected]> Monitors that instrument user-space function boundaries need to resolve paths, register uprobes, and deregister them safely. Provide a thin wrapper so monitors share a single implementation of this boilerplate.
struct rv_uprobe embeds struct uprobe_consumer directly, avoiding a separate heap allocation per probe. The struct holds a struct path for the probed binary so that the inode and its mount remain referenced for the full uprobe lifetime; uprobe_register() does not take its own reference to the inode. The path is released in rv_uprobe_unregister_nosync() after the consumer has been removed. rv_uprobe_sync() calls uprobe_unregister_sync() which performs synchronize_rcu_tasks_trace(), waiting for all rcu_read_lock_trace() readers (handler_chain()) to complete on all CPUs before returning; the caller may then free the containing struct. The API provides register, synchronous and nosync unregister, a global handler barrier (rv_uprobe_sync), and an active-state predicate. Suggested-by: Gabriele Monaco <[email protected]> Signed-off-by: Wen Yang <[email protected]> --- include/rv/rv_uprobe.h | 90 ++++++++++++++++++++++++++++++++++++ kernel/trace/rv/rv_uprobe.c | 91 +++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 include/rv/rv_uprobe.h create mode 100644 kernel/trace/rv/rv_uprobe.c diff --git a/include/rv/rv_uprobe.h b/include/rv/rv_uprobe.h new file mode 100644 index 000000000000..d0a9079ac5be --- /dev/null +++ b/include/rv/rv_uprobe.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2026 Wen Yang <[email protected]> */ +/* + * Generic uprobe infrastructure for RV monitors. + * + */ + +#ifndef _RV_UPROBE_H +#define _RV_UPROBE_H + +#include <linux/path.h> +#include <linux/types.h> +#include <linux/uprobes.h> + +struct pt_regs; + +/** + * struct rv_uprobe - embeddable uprobe handle for RV monitors + * + * Embed via DECLARE_RV_UPROBE() and pass &name to rv_uprobe_register(). + * The caller may free the containing struct after rv_uprobe_unregister() + * (or rv_uprobe_unregister_nosync() + rv_uprobe_sync()) returns. + * + * @uc: embedded uprobe_consumer; set handler/ret_handler before registering + * @uprobe: registered uprobe pointer (NULL when not registered) + * @path: path of the probed binary, held until unregistration + */ +struct rv_uprobe { + struct uprobe_consumer uc; + struct uprobe *uprobe; + struct path path; +}; + +/* Embed a named rv_uprobe inside a caller struct */ +#define DECLARE_RV_UPROBE(name) struct rv_uprobe name + +/** + * rv_uprobe_is_registered - test whether an uprobe is currently active + * @p: probe to test; may be NULL + */ +bool rv_uprobe_is_registered(const struct rv_uprobe *p); + +/** + * rv_uprobe_register - initialise and register an uprobe + * @binpath: absolute path to the target binary + * @offset: byte offset within the binary + * @p: caller-provided rv_uprobe (embedded via DECLARE_RV_UPROBE); + * p->uc.handler and/or p->uc.ret_handler must be set before this call + * + * Resolves the path and registers p->uc with the uprobe subsystem. + * No heap allocation is performed. + * + * Returns 0 on success, negative errno on failure. + */ +int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe *p); + +/** + * rv_uprobe_unregister - synchronously unregister a uprobe + * @p: probe to unregister; may be NULL (no-op) + * + * Removes the consumer from the uprobe subsystem and waits for all in-flight + * handlers to complete (via synchronize_rcu_tasks_trace()). After this + * returns, the containing struct may be safely freed by the caller. + * Use rv_uprobe_unregister_nosync() + rv_uprobe_sync() to batch multiple + * deregistrations before a single synchronisation. + */ +void rv_uprobe_unregister(struct rv_uprobe *p); + +/** + * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting + * @p: probe to dequeue; may be NULL (no-op) + * + * Removes the consumer without waiting for in-flight handlers. The path + * (p->path) is NOT released here; the caller must call rv_uprobe_sync() + * followed by path_put(&p->path) before freeing the containing struct. + * Use rv_uprobe_unregister() to handle both in one step. + */ +void rv_uprobe_unregister_nosync(struct rv_uprobe *p); + +/** + * rv_uprobe_sync - wait for all in-flight uprobe handlers to complete + * + * Global barrier: calls uprobe_unregister_sync(), which runs + * synchronize_rcu_tasks_trace() and synchronize_srcu(&uretprobes_srcu). + * After this returns, no handler_chain() iteration referencing any + * previously deregistered consumer is still in progress. + */ +void rv_uprobe_sync(void); + +#endif /* _RV_UPROBE_H */ diff --git a/kernel/trace/rv/rv_uprobe.c b/kernel/trace/rv/rv_uprobe.c new file mode 100644 index 000000000000..b412a8e28a6e --- /dev/null +++ b/kernel/trace/rv/rv_uprobe.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic uprobe infrastructure for RV monitors. + * + * rv_uprobe embeds struct uprobe_consumer; rv_uprobe_sync() drains in-flight + * handlers before the containing struct may be freed (see rv_uprobe.h). + */ +#include <linux/dcache.h> +#include <linux/fs.h> +#include <linux/namei.h> +#include <linux/uprobes.h> +#include <rv/rv_uprobe.h> + +/** + * rv_uprobe_register - initialise and register an uprobe + */ +int rv_uprobe_register(const char *binpath, loff_t offset, struct rv_uprobe *p) +{ + struct inode *inode; + int ret; + + ret = kern_path(binpath, LOOKUP_FOLLOW, &p->path); + if (ret) + return ret; + + if (!d_is_reg(p->path.dentry)) { + path_put(&p->path); + return -EINVAL; + } + + inode = d_real_inode(p->path.dentry); + + /* uprobe_register() takes no inode reference; the path is held in p->path */ + p->uprobe = uprobe_register(inode, offset, 0, &p->uc); + if (IS_ERR(p->uprobe)) { + ret = PTR_ERR(p->uprobe); + p->uprobe = NULL; + path_put(&p->path); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rv_uprobe_register); + +/** + * rv_uprobe_is_registered - test whether an uprobe is currently active + */ +bool rv_uprobe_is_registered(const struct rv_uprobe *p) +{ + return p && p->uprobe; +} +EXPORT_SYMBOL_GPL(rv_uprobe_is_registered); + +/** + * rv_uprobe_unregister - synchronously unregister a uprobe + */ +void rv_uprobe_unregister(struct rv_uprobe *p) +{ + if (!p || !p->uprobe) + return; + + uprobe_unregister_nosync(p->uprobe, &p->uc); + p->uprobe = NULL; + rv_uprobe_sync(); + path_put(&p->path); +} +EXPORT_SYMBOL_GPL(rv_uprobe_unregister); + +/** + * rv_uprobe_unregister_nosync - dequeue an uprobe without waiting + */ +void rv_uprobe_unregister_nosync(struct rv_uprobe *p) +{ + if (!p || !p->uprobe) + return; + + uprobe_unregister_nosync(p->uprobe, &p->uc); + p->uprobe = NULL; + /* path held; caller must call rv_uprobe_sync() then path_put(&p->path) */ +} +EXPORT_SYMBOL_GPL(rv_uprobe_unregister_nosync); + +/** + * rv_uprobe_sync - wait for all in-flight uprobe handlers to complete + */ +void rv_uprobe_sync(void) +{ + uprobe_unregister_sync(); +} +EXPORT_SYMBOL_GPL(rv_uprobe_sync); -- 2.25.1
