Skip to contentSkip to conversation

Add task latency over budget RV monitor

PATCH New [email protected] 1 participant 15 messages Nov 5

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

v8 cover Add task latency over budget RV monitor

From: Wen Yang <[email protected]>

This series introduces tlob (task latency over budget), a per-task hybrid automaton RV monitor that measures elapsed wall-clock time across a user-delimited code section and emits an error when the elapsed time exceeds a configurable budget.

The monitor tracks four states (running, waiting, sleeping, stopped) driven by sched_switch and sched_wakeup tracepoints. A single clock invariant, clk_elapsed < BUDGET_NS(), is enforced by a per-task hrtimer (HRTIMER_MODE_REL_HARD). On expiry error_env_tlob is emitted; which phase consumed the budget (running, waiting or sleeping) is recovered from event_tlob in user space (see monitor_tlob.rst).

Tasks are registered for monitoring by writing to the tracefs monitor file:
echo "p:myregion /path/to/binary:OFFSET_START OFFSET_STOP threshold=NS" \
>> /sys/kernel/tracing/rv/monitors/tlob/monitor

Two uprobes delimit the measured section without modifying the target binary. Multiple uprobe pairs can be active simultaneously. A task may repeat the START/STOP sequence any number of times: the entry parks in the automaton's "stopped" state and restarts in place, reusing the same pre-allocated slot.

Changes in v8
=============
This version is a comprehensive rework addressing Gabriele Monaco's review on v7, with substantial simplifications to both framework support and the tlob monitor itself:

1. HA timer teardown and underflow fixes (Patches 1-5):
- Deleted ha_timer_ready() completely. Following Gabriele's suggestion, ha_monitor_free_env() now checks da_monitoring() to disarm the hrtimer on the storage free path (Patch 4).
- Isolated the HA underflow and timer free path KUnit tests into a dedicated compilation unit (kernel/trace/rv/rv_ha_kunit.c) guarded by CONFIG_RV_HA_MONITOR, avoiding polluting rv_monitors_test.c (Patches 3, 5).

2. Framework per-object storage improvements (Patches 6-8):
- Added DA_OBJ_EMBEDDED_TYPE support based on Gabriele's PoC. Per-object DA storage can now directly embed monitor-specific target data inside struct da_monitor_storage, completely eliminating secondary dynamic memory allocations and double pointer dereferences (Patch 8).
- Added smp_store_release/smp_load_acquire memory barriers between da_storage_set_target() and da_storage_is_empty(), and ensured da_get_target() returns NULL if the storage slot is not yet filled.

3. Generic uprobe helper infrastructure (Patch 9):
- Extracted reusable rv_uprobe registration and synchronization helpers into include/rv/rv_uprobe.h and kernel/trace/rv/rv_uprobe.c.

4. Major tlob simplification (Patch 11):
- Reduced tlob.c from ~1065 lines to ~660 lines (-38% code size).
- Completely removed the private mempool (tlob_record_pool) and custom allocator; tlob now relies entirely on the framework's pre-allocated storage via DA_MON_POOL_SIZE.
- Removed the parallel state machine (OPEN, CLOSED, RETIRED) and tlob_binding_lock. Window lifecycle is now 100% managed by the automaton states and framework storage lifecycle.
- Replaced complex quiesce/reap/RCU callouts with a clean RCU walk (tlob_release_binding()) executed after rv_uprobe_unregister_sync().
- Optimized timer re-arming on the scheduler hot path.
- Refined error semantics: nested START is detected as an illegal event (running x start) and reported via error_tlob without altering the running window; pool exhaustion cleanly returns -ENOSPC.

5. Test coverage and selftests expansion (Patches 12-14):
- Added 3 new selftests covering critical edge cases:
* uprobe_task_exit.tc: verifies task exit/SIGKILL inside an active window disarms the timer with no orphaned/late violations.
* uprobe_nested_start.tc: verifies nested START reporting and window integrity.
* uprobe_pool_exhausted.tc: verifies the 64-task cap and graceful rejection of the 65th task.
- Extended tlob_target with "nest" mode.
- All 13 selftests and all KUnit test suites pass cleanly.

Series structure
================

Part 1: Independent fixes and enhancements to the RV/HA framework
Patch 1: rv: Force environment reset action on HA monitor start Patch 2: rv: Fix ha_monitor timer and invariant underflow Patch 3: rv: Add a KUnit test for the HA timer underflow Patch 4: rv: Disarm HA timers on the storage free path Patch 5: rv: Add a KUnit test for the storage free path Patch 6: rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL
overridable
Patch 7: rv: Introduce DA_MON_ALLOCATION_STRATEGY
Patch 8: rv: Support embedded target types in per-object DA storage Patch 9: rv: Add generic uprobe infrastructure for RV monitors

Part 2: The tlob monitor and models
Patch 10: rv: Add tlob model DOT file
Patch 11: rv: Add tlob hybrid automaton monitor

Part 3: Testing and selftests
Patch 12: rv: Add KUnit tests for the tlob monitor
Patch 13: selftests/verification: Add tlob selftests
Patch 14: selftests/ftrace: Walk up parent dirs to find test.d/functions

v7: https://lore.kernel.org/all/[email protected]/
v6: https://lore.kernel.org/all/[email protected]/
v4: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/all/[email protected]/
v2: https://lore.kernel.org/all/[email protected]/
v1: https://lore.kernel.org/all/[email protected]/

Gabriele Monaco (1):
rv: Force environment reset action on HA monitor start

Wen Yang (13):
rv: Fix ha_monitor timer and invariant underflow
rv: Add a KUnit test for the HA timer underflow
rv: Disarm HA timers on the storage free path
rv: Add a KUnit test for the storage free path
rv: Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL
overridable
rv: Introduce DA_MON_ALLOCATION_STRATEGY
rv: Support embedded target types in per-object DA storage
rv: Add generic uprobe infrastructure for RV monitors
rv: Add tlob model DOT file
rv: Add tlob hybrid automaton monitor
rv: Add KUnit tests for the tlob monitor selftests/verification: Add tlob selftests selftests/ftrace: Walk up parent dirs to find test.d/functions

 Documentation/trace/rv/index.rst              |   1 +
 Documentation/trace/rv/monitor_tlob.rst       | 162 ++++
 include/rv/da_monitor.h                       | 226 +++++-
 include/rv/ha_monitor.h                       |  53 +-
 include/rv/rv_uprobe.h                        |  35 +
 kernel/trace/rv/Kconfig                       |   5 +
 kernel/trace/rv/Makefile                      |   3 +
 kernel/trace/rv/monitors/nomiss/nomiss.c      |   2 +-
 kernel/trace/rv/monitors/opid/opid.h          |   1 +
 kernel/trace/rv/monitors/tlob/Kconfig         |  12 +
 kernel/trace/rv/monitors/tlob/tlob.c          | 743 ++++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob.h          | 143 ++++
 kernel/trace/rv/monitors/tlob/tlob_kunit.c    | 151 ++++
 kernel/trace/rv/monitors/tlob/tlob_kunit.h    |  18 +
 kernel/trace/rv/monitors/tlob/tlob_trace.h    |  20 +
 kernel/trace/rv/rv_ha_kunit.c                 | 201 +++++
 kernel/trace/rv/rv_monitors_test.c            |   2 +
 kernel/trace/rv/rv_trace.h                    |   1 +
 kernel/trace/rv/rv_uprobe.c                   |  56 ++
 tools/testing/selftests/ftrace/ftracetest     |  19 +-
 .../testing/selftests/verification/.gitignore |   1 +
 tools/testing/selftests/verification/Makefile |   4 +
 tools/testing/selftests/verification/config   |   3 +
 .../selftests/verification/test.d/functions   |   9 +
 .../test.d/tlob/run_tlob_tests.sh             |  32 +
 .../verification/test.d/tlob/uprobe_bind.tc   |  47 ++
 .../test.d/tlob/uprobe_disable_live.tc        |  79 ++
 .../verification/test.d/tlob/uprobe_multi.tc  |  58 ++
 .../test.d/tlob/uprobe_multi_instance.tc      |  65 ++
 .../test.d/tlob/uprobe_nested_start.tc        |  62 ++
 .../test.d/tlob/uprobe_no_event.tc            |  17 +
 .../test.d/tlob/uprobe_overlap_register.tc    |  66 ++
 .../test.d/tlob/uprobe_pool_exhausted.tc      |  75 ++
 .../test.d/tlob/uprobe_restart.tc             |  66 ++
 .../test.d/tlob/uprobe_restart_violation.tc   |  48 ++
 .../test.d/tlob/uprobe_task_exit.tc           |  65 ++
 .../test.d/tlob/uprobe_unbind_live.tc         |  65 ++
 .../test.d/tlob/uprobe_violation.tc           |  53 ++
 .../selftests/verification/tlob_target.c      | 156 ++++
 tools/verification/models/tlob.dot            |  27 +
 tools/verification/rvgen/rvgen/dot2c.py       |   2 +
 41 files changed, 2829 insertions(+), 25 deletions(-)
 create mode 100644 Documentation/trace/rv/monitor_tlob.rst
 create mode 100644 include/rv/rv_uprobe.h
 create mode 100644 kernel/trace/rv/monitors/tlob/Kconfig
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.h
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.h
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_trace.h
 create mode 100644 kernel/trace/rv/rv_ha_kunit.c
 create mode 100644 kernel/trace/rv/rv_uprobe.c
 create mode 100755 tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_nested_start.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_pool_exhausted.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_task_exit.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
 create mode 100644 tools/testing/selftests/verification/tlob_target.c
 create mode 100644 tools/verification/models/tlob.dot
Signature · 1 line
2.25.1
#1 lore raw

PATCH v8 1/14 Force environment reset action on HA monitor start

From: Gabriele Monaco <[email protected]>

Currently the monitor variables with a storage (e.g. clocks) are initialised as invalid, then the first explicit reset() sets them as valid for constraint. This only adds extra complexity to handle the invalid case in constraints check.

Apply a reset() by default when starting monitors instead of resetting to an invalid state. If monitors have no stored variable (hence no reset function) add a macro HA_NO_RESET to stub it, this is all transparently handled by rvgen.

The reset() action on ns-granularity clocks needs the (possibly cached) current time, read it directly there. This might cause a double call to ktime_get_ns() within the same da_handle_start_run_event() but will occur only the first time and is harmless.

---
 include/rv/ha_monitor.h                 | 8 +++++++-
 kernel/trace/rv/monitors/opid/opid.h    | 1 +
 tools/verification/rvgen/rvgen/dot2c.py | 2 ++
 3 files changed, 10 insertions(+), 1 deletion(-)
include/rv/ha_monitor.h +7 -1
index 6e1c7fe5449a..93ac99ff7f1d 100644
@@ -159,19 +159,25 @@ static void ha_monitor_destroy(void)
 
 /* Should be supplied by the monitor */
 static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns);
 static bool ha_verify_constraint(struct ha_monitor *ha_mon,
 				 enum states curr_state,
 				 enum events event,
 				 enum states next_state,
 				 u64 time_ns);
+#ifdef HA_NO_RESET
+static void ha_reset_env(struct ha_monitor *ha_mon, enum envs env, u64 time_ns) { }
+#endif
 
 /*
  * ha_monitor_reset_all_stored - reset all environment variables in the monitor
  */
 static inline void ha_monitor_reset_all_stored(struct ha_monitor *ha_mon)
 {
+	u64 time_ns = ha_get_ns();
+
 	for (int i = 0; i < ENV_MAX_STORED; i++)
-		WRITE_ONCE(ha_mon->env_store[i], ENV_INVALID_VALUE);
+		ha_reset_env(ha_mon, i, time_ns);
 }
 
 /*
kernel/trace/rv/monitors/opid/opid.h +1 -0
index fb0aa4c28aa6..f85b1959c2fb 100644
@@ -28,6 +28,7 @@ enum envs_opid {
 };
 
 _Static_assert(env_max_stored_opid <= MAX_HA_ENV_LEN, "Not enough slots");
+#define HA_NO_RESET
 
 struct automaton_opid {
 	char *state_names[state_max_opid];
tools/verification/rvgen/rvgen/dot2c.py +2 -0
index 22938ce1bf6c..1532e6b6e199 100644
@@ -90,6 +90,8 @@ class Dot2c(Automata):
                         ' "Not enough slots");')
             if {"ns", "us", "ms", "s"}.intersection(self.env_types.values()):
                 buff.append("#define HA_CLK_NS")
+            if len(self.env_stored) == 0:
+                buff.append("#define HA_NO_RESET")
             buff.append("")
         return buff
 
Signature · 1 line
2.25.1
#2 lore raw

PATCH v8 2/14 Fix ha_monitor timer and invariant underflow

In reply to [email protected]

From: Wen Yang <[email protected]>

ha_start_timer_ns() and ha_start_timer_jiffy() compute the remaining budget as expire - passed. Once the elapsed time passed exceeds the budget expire (e.g. a state transition after the invariant should have fired, before the timer callback ran), the unsigned subtraction underflows to a huge value and the timer is scheduled far into the future instead of expiring immediately, so the monitor stops enforcing the budget.

ha_check_invariant_ns() computes store >= time_ns - expire_ns, which underflows when the system uptime is still below expire_ns (a monitor armed early in boot with a large budget), reporting a false violation.

Clamp the timer remainder to 0 (fire immediately) when passed >= expire, and rewrite the invariant check as store + expire_ns >= time_ns so neither side can underflow. Both timer flavours (jiffy and hrtimer) are fixed.

Fixes f5587d1b6ec9 ("rv: Add Hybrid Automata monitor type")
Fixes ab2900ae252b ("rv: Simplify hybrid automata monitors's clock variables")
---
 include/rv/ha_monitor.h | 29 ++++++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 5 deletions(-)
include/rv/ha_monitor.h +24 -5
index 93ac99ff7f1d..ebb7f91e3447 100644
@@ -351,7 +351,11 @@ static inline void ha_reset_clk_ns(struct ha_monitor *ha_mon, enum envs env, u64
 static inline bool ha_check_invariant_ns(struct ha_monitor *ha_mon, enum envs env,
 					 u64 time_ns, u64 expire_ns)
 {
-	return READ_ONCE(ha_mon->env_store[env]) >= time_ns - expire_ns;
+	/*
+	 * Compare as store + expire >= time to avoid underflow when the
+	 * system uptime is still below expire_ns (early boot, large budget).
+	 */
+	return READ_ONCE(ha_mon->env_store[env]) + expire_ns >= time_ns;
 }
 /*
  * ha_invariant_passed_ns - prepare the invariant and return the time since reset
@@ -410,6 +414,17 @@ static inline void ha_inv_to_guard(struct ha_monitor *ha_mon, enum envs env,
 	WRITE_ONCE(ha_mon->env_store[env], READ_ONCE(ha_mon->env_store[env]) - value);
 }
 
+/*
+ * ha_time_left - time left before expire, 0 once it already passed
+ *
+ * A plain expire - passed underflows and would arm the timer far in the
+ * future.  Unit-agnostic: callers pass ns or jiffies consistently.
+ */
+static inline u64 ha_time_left(u64 passed, u64 expire)
+{
+	return passed < expire ? expire - passed : 0;
+}
+
 #if HA_TIMER_TYPE == HA_TIMER_WHEEL
 /*
  * Helper functions to handle the monitor timer.
@@ -439,15 +454,17 @@ static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env
 {
 	u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns);
 
-	mod_timer(&ha_mon->timer, get_jiffies_64() + expire - passed);
+	mod_timer(&ha_mon->timer,
+		  get_jiffies_64() + ha_time_left(passed, expire));
 }
 static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
 				     u64 expire, u64 time_ns)
 {
 	u64 passed = ha_invariant_passed_ns(ha_mon, env, time_ns);
+	u64 delay = ha_time_left(passed, expire);
 
 	ha_start_timer_jiffy(ha_mon, ENV_MAX_STORED,
-			     nsecs_to_jiffies(expire - passed + TICK_NSEC - 1), time_ns);
+			     nsecs_to_jiffies(delay + TICK_NSEC - 1), time_ns);
 }
 /*
  * ha_cancel_timer - Cancel the timer
@@ -494,7 +511,8 @@ static inline void ha_start_timer_ns(struct ha_monitor *ha_mon, enum envs env,
 
 	if (RV_MON_TYPE == RV_MON_PER_CPU)
 		mode |= HRTIMER_MODE_PINNED;
-	hrtimer_start(&ha_mon->hrtimer, ns_to_ktime(expire - passed), mode);
+	hrtimer_start(&ha_mon->hrtimer,
+		      ns_to_ktime(ha_time_left(passed, expire)), mode);
 }
 static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env,
 					u64 expire, u64 time_ns)
@@ -502,7 +520,8 @@ static inline void ha_start_timer_jiffy(struct ha_monitor *ha_mon, enum envs env
 	u64 passed = ha_invariant_passed_jiffy(ha_mon, env, time_ns);
 
 	ha_start_timer_ns(ha_mon, ENV_MAX_STORED,
-			  jiffies_to_nsecs(expire - passed), time_ns);
+			  jiffies_to_nsecs(ha_time_left(passed, expire)),
+			  time_ns);
 }
 /*
  * ha_cancel_timer - Cancel the timer
Signature · 1 line
2.25.1
#3 lore raw

PATCH v8 3/14 Add a KUnit test for the HA timer underflow

In reply to [email protected]

From: Wen Yang <[email protected]>

Add rv_ha_kunit.c as a standalone test suite with rvtest, a minimal per-object HA monitor, to test both underflows fixed by the previous
patch: an invariant checked while the uptime is below the budget holds, and a timer armed after the budget elapsed expires now. Without that fix both expectations fail.

Building the HA test in its own compilation unit keeps ha_monitor.h isolated from non-monitor test runners and avoids macro conflicts. The suite is guarded by IS_ENABLED(CONFIG_RV_HA_MONITOR) so kernels without HA monitors build cleanly.

---
 kernel/trace/rv/Makefile      |   1 +
 kernel/trace/rv/rv_ha_kunit.c | 152 ++++++++++++++++++++++++++++++++++
 2 files changed, 153 insertions(+)
 create mode 100644 kernel/trace/rv/rv_ha_kunit.c
kernel/trace/rv/Makefile +1 -0
index cdbf68c84f5a..4b84b83b942b 100644
@@ -26,3 +26,4 @@ obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
 obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
 obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
 obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_monitors_test.o
+obj-$(CONFIG_RV_MONITORS_KUNIT_TEST) += rv_ha_kunit.o
kernel/trace/rv/rv_ha_kunit.c new file +152 -0

152 lines not on this page · raw message

Signature · 1 line
2.25.1
#4 lore raw

PATCH v8 4/14 Disarm HA timers on the storage free path

In reply to [email protected]

From: Wen Yang <[email protected]>

da_destroy_storage() cancels the HA timer through da_monitor_reset_hook() and only then unlinks the storage with hash_del_rcu(). A handler on another CPU that looked the storage up before the unlink can still call ha_start_timer_*() after the cancel. The timer then stays queued on a storage that kfree_rcu() frees a grace period later, and fires on freed memory, reporting a violation for an object that no longer exists.

A monitor cannot close this from its own handlers: any cancel it issues precedes the unlink. The first point no reader can race with is the RCU callback that frees the storage.

Add da_monitor_free_hook(), called right before a per-object storage is freed, from the RCU callback and from da_monitor_destroy(). HA monitors implement it by calling ha_cancel_timer_sync() if da_monitoring() is true. Replace kfree_rcu() with call_rcu() so there is a callback to hook.

Cancelling synchronously from the RCU callback is safe: HA hrtimers are always armed in HARD mode, so hrtimer_cancel() only spins, and on PREEMPT_RT, where timer_delete_sync() may sleep, RCU callbacks run in the rcuc kthread.

Storages whose monitor never started are skipped via da_monitoring().

Fixes 4a24127bd6cb ("rv: Add support for per-object monitors in DA/HA")
---
 include/rv/da_monitor.h | 27 ++++++++++++++++++++++++++-
 include/rv/ha_monitor.h |  8 ++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)
include/rv/da_monitor.h +26 -1
index e3cf85c9ce55..c7f9733fb8a4 100644
@@ -67,6 +67,17 @@ static struct rv_monitor rv_this;
 #define da_monitor_sync_hook()
 #endif
 
+/*
+ * Called on the last reference to a storage, right before it is freed.
+ * Monitors embedding a timer disarm it here: an earlier point races with a
+ * reader that resolved the storage before hash_del_rcu(), and
+ * tracepoint_synchronize_unregister() only covers tracepoint probes.
+ * Default: no-op.
+ */
+#ifndef da_monitor_free_hook
+#define da_monitor_free_hook(da_mon)
+#endif
+
 /*
  * Type for the target id, default to int but can be overridden.
  * A long type can work as hash table key (PER_OBJ) but will be downgraded to
@@ -543,6 +554,15 @@ static inline monitor_target da_get_target_by_id(da_id_type id)
 	return mon_storage->target;
 }
 
+static void da_kfree_cb(struct rcu_head *head)
+{
+	struct da_monitor_storage *ms =
+		container_of(head, struct da_monitor_storage, rcu);
+
+	da_monitor_free_hook(&ms->rv.da_mon);
+	kfree(ms);
+}
+
 /*
  * da_destroy_storage - destroy the per-object storage
  *
@@ -564,7 +584,11 @@ static inline void da_destroy_storage(da_id_type id)
 		return;
 	da_monitor_reset_hook(&mon_storage->rv.da_mon);
 	hash_del_rcu(&mon_storage->node);
-	kfree_rcu(mon_storage, rcu);
+	/*
+	 * A reader from before hash_del_rcu() may still arm; the free
+	 * callback runs past it, so the hook disarms there.
+	 */
+	call_rcu(&mon_storage->rcu, da_kfree_cb);
 }
 
 static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
@@ -607,6 +631,7 @@ static inline void da_monitor_destroy(void)
 	 * pending, we can safely assume no concurrent user.
 	 */
 	hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) {
+		da_monitor_free_hook(&mon_storage->rv.da_mon);
 		hash_del_rcu(&mon_storage->node);
 		kfree(mon_storage);
 	}
include/rv/ha_monitor.h +8 -0
index ebb7f91e3447..3bf1dd15544c 100644
@@ -29,6 +29,7 @@ static inline void ha_monitor_reset_env(struct da_monitor *da_mon);
 static inline void ha_setup_timer(struct ha_monitor *ha_mon);
 static inline bool ha_cancel_timer(struct ha_monitor *ha_mon);
 static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon);
+static inline void ha_monitor_free_env(struct da_monitor *da_mon);
 static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
 					 enum states curr_state,
 					 enum events event,
@@ -38,6 +39,7 @@ static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
 #define da_monitor_init_hook ha_monitor_init_env
 #define da_monitor_reset_hook ha_monitor_reset_env
 #define da_monitor_sync_hook() synchronize_rcu()
+#define da_monitor_free_hook ha_monitor_free_env
 
 #if !defined(HA_SKIP_AUTO_CLEANUP) && RV_MON_TYPE == RV_MON_PER_TASK
 /*
@@ -551,6 +553,12 @@ static inline bool ha_cancel_timer(struct ha_monitor *ha_mon)
 static inline void ha_cancel_timer_sync(struct ha_monitor *ha_mon) { }
 #endif
 
+static inline void ha_monitor_free_env(struct da_monitor *da_mon)
+{
+	if (da_monitoring(da_mon))
+		ha_cancel_timer_sync(to_ha_monitor(da_mon));
+}
+
 #if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
 #ifdef RV_MON_OPS_INIT
 #undef RV_MON_OPS_INIT
Signature · 1 line
2.25.1
#5 lore raw

PATCH v8 5/14 Add a KUnit test for the storage free path

In reply to [email protected]

From: Wen Yang <[email protected]>

A per-object HA monitor whose timer is armed through a pointer resolved before da_destroy_storage() outlives the storage and reports against a gone entity. Real handlers resolve and use a storage under one guard(rcu)(), so the fixture issues the two steps separately to reach that window.

Add rv_test_rvtest_arm_after_destroy() to rv_ha_kunit.c to verify that the free hook properly disarms the timer before the storage is freed.

---
 kernel/trace/rv/rv_ha_kunit.c | 51 ++++++++++++++++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)
kernel/trace/rv/rv_ha_kunit.c +50 -1
index e8aaee110fae..29ee8a739739 100644
@@ -3,12 +3,16 @@
  * KUnit tests for RV hybrid automata (HA) framework
  */
 #include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/delay.h>
 #include <linux/rv.h>
 
 #if IS_ENABLED(CONFIG_RV_HA_MONITOR)
 
 #include <rv_trace.h>
 
+static atomic_t rvtest_expired;
+
 /* rvtest: a minimal per-object HA monitor driven directly by the tests. */
 #define MONITOR_NAME rvtest
 #define RV_MON_TYPE RV_MON_PER_OBJ
@@ -19,7 +23,7 @@ typedef void *monitor_target;
 #define trace_rv_retries_error(...)	do { } while (0)
 #define trace_event_rvtest(...)		do { } while (0)
 #define trace_error_rvtest(...)		do { } while (0)
-#define trace_error_env_rvtest(...)	do { } while (0)
+#define trace_error_env_rvtest(...)	atomic_inc(&rvtest_expired)
 
 enum states_rvtest {
 	idle_rvtest,
@@ -134,8 +138,53 @@ static void rv_test_rvtest_underflow(struct kunit *test)
 	rv_this.enabled = 0;
 }
 
+/*
+ * rv_test_rvtest_arm_after_destroy - a storage outlived by its own timer
+ *
+ * da_destroy_storage() cancels the timer, then unlinks:
+ *
+ *	da_monitor_reset_hook()		cancels
+ *	hash_del_rcu()
+ *	call_rcu()
+ *
+ * A reader that resolved the storage before the unlink can arm past that
+ * cancel.  Handlers resolve and use under one guard(rcu)(), so the two steps
+ * are issued separately here.
+ *
+ * The timer is still queued after the destroy, and it fires -- reporting a
+ * violation for an entity that is gone.
+ */
+static void rv_test_rvtest_arm_after_destroy(struct kunit *test)
+{
+	const int id = 4242;
+	struct da_monitor *stale;
+
+	atomic_set(&rvtest_expired, 0);
+	da_monitor_init();
+	rv_this.enabled = 1;
+
+	da_handle_start_run_event(id, NULL, start_rvtest);
+
+	scoped_guard(rcu)			/* resolve before the unlink */
+		stale = da_get_monitor(id, NULL);
+	KUNIT_ASSERT_NOT_NULL(test, stale);
+
+	da_destroy_storage(id);			/* cancel, unlink, call_rcu */
+	ha_start_timer_ns(to_ha_monitor(stale), clk_rvtest,
+			  RVTEST_BUDGET_NS, ha_get_ns());
+
+	rcu_barrier();
+
+	/* A timer the free path missed expires here. */
+	msleep(600);
+	KUNIT_EXPECT_EQ(test, atomic_read(&rvtest_expired), 0);
+	da_monitor_destroy();
+	rv_this.enabled = 0;
+}
+
 static struct kunit_case rv_ha_kunit_cases[] = {
 	KUNIT_CASE(rv_test_rvtest_underflow),
+	KUNIT_CASE(rv_test_rvtest_arm_after_destroy),
 	{}
 };
 
Signature · 1 line
2.25.1
#6 lore raw

PATCH v8 6/14 Make da_monitor_reset_hook, sync_hook and EVENT_NONE_LBL overridable

In reply to [email protected]

From: Wen Yang <[email protected]>

Wrap da_monitor_reset_hook, da_monitor_sync_hook, and EVENT_NONE_LBL with ifndef guards so HA-based monitors can substitute their own implementations before including this header.

Overrides of da_monitor_reset_hook must still call ha_monitor_reset_env() or cancel outstanding timers to avoid timer UAF.

No behaviour change for monitors that do not override these macros.

---
 include/rv/ha_monitor.h | 8 ++++++++
 1 file changed, 8 insertions(+)
include/rv/ha_monitor.h +8 -0
index 3bf1dd15544c..5372e5dfe3c5 100644
@@ -37,8 +37,14 @@ static bool ha_monitor_handle_constraint(struct da_monitor *da_mon,
 					 da_id_type id);
 #define da_monitor_event_hook ha_monitor_handle_constraint
 #define da_monitor_init_hook ha_monitor_init_env
+
+/* Overrides must still call ha_monitor_reset_env() or cancel the timer. */
+#ifndef da_monitor_reset_hook
 #define da_monitor_reset_hook ha_monitor_reset_env
+#endif
+#ifndef da_monitor_sync_hook
 #define da_monitor_sync_hook() synchronize_rcu()
+#endif
 #define da_monitor_free_hook ha_monitor_free_env
 
 #if !defined(HA_SKIP_AUTO_CLEANUP) && RV_MON_TYPE == RV_MON_PER_TASK
@@ -77,7 +83,9 @@ _Static_assert(offsetof(struct ha_monitor, da_mon) == 0,
 #define ENV_INVALID_VALUE U64_MAX
 /* Error with no event occurs only on timeouts */
 #define EVENT_NONE EVENT_MAX
+#ifndef EVENT_NONE_LBL
 #define EVENT_NONE_LBL "none"
+#endif
 #define ENV_BUFFER_SIZE 64
 
 #ifdef CONFIG_RV_REACTORS
Signature · 1 line
2.25.1
#7 lore raw

PATCH v8 7/14 Introduce DA_MON_ALLOCATION_STRATEGY

In reply to [email protected]

From: Wen Yang <[email protected]>

Per-object DA storage allocation is currently limited to kmalloc on demand. Add a compile-time selector so monitors can choose among three strategies:

DA_ALLOC_AUTO (default) - kmalloc per object on the monitor path
DA_ALLOC_POOL - pre-allocated fixed-size mempool;
selected by defining DA_MON_POOL_SIZE
DA_ALLOC_MANUAL - caller pre-inserts storage; framework
only links the target field

nomiss switches from the implicit DA_SKIP_AUTO_ALLOC override to an explicit DA_MON_ALLOCATION_STRATEGY DA_ALLOC_MANUAL; the behaviours are equivalent (pre-allocated storage, no allocation on the sched_switch start path).

---
 include/rv/da_monitor.h                  | 121 +++++++++++++++++++++--
 kernel/trace/rv/monitors/nomiss/nomiss.c |   2 +-
 2 files changed, 112 insertions(+), 11 deletions(-)
include/rv/da_monitor.h +111 -10
index c7f9733fb8a4..76169e41d4e5 100644
@@ -14,7 +14,45 @@
 #ifndef _RV_DA_MONITOR_H
 #define _RV_DA_MONITOR_H
 
+/*
+ * Allocation strategy for RV_MON_PER_OBJ monitors, selected before including
+ * this header.  Defining DA_MON_POOL_SIZE N implies DA_ALLOC_POOL.
+ *
+ * DA_ALLOC_AUTO   - kmalloc on demand; unbounded (default).
+ * DA_ALLOC_POOL   - DA_MON_POOL_SIZE pre-allocated slots.  The start handler
+ *                   must run in task context; other handlers only call
+ *                   da_handle_event() and never allocate.
+ * DA_ALLOC_MANUAL - caller pre-inserts storage; framework links the target.
+ */
+#define DA_ALLOC_AUTO   0
+#define DA_ALLOC_POOL   1
+#define DA_ALLOC_MANUAL 2
+
+#ifdef DA_MON_POOL_SIZE
+#if defined(DA_MON_ALLOCATION_STRATEGY) && \
+	DA_MON_ALLOCATION_STRATEGY != DA_ALLOC_POOL
+#error "DA_MON_POOL_SIZE implies DA_ALLOC_POOL"
+#endif
+#if DA_MON_POOL_SIZE == 0
+#error "DA_MON_POOL_SIZE must be non-zero"
+#endif
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_POOL
+#endif /* DA_MON_POOL_SIZE */
+
+#ifndef DA_MON_ALLOCATION_STRATEGY
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_AUTO
+#endif /* DA_MON_ALLOCATION_STRATEGY */
+
+/* Zero default keeps pool-mode conditionals compile-time constant. */
+#ifndef DA_MON_POOL_SIZE
+#if DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL
+#error "DA_ALLOC_POOL requires DA_MON_POOL_SIZE to be defined and non-zero"
+#endif
+#define DA_MON_POOL_SIZE 0
+#endif /* DA_MON_POOL_SIZE */
+
 #include <rv/automata.h>
+#include <linux/mempool.h>
 #include <linux/rv.h>
 #include <rv/kunit.h>
 #include <linux/stringify.h>
@@ -563,6 +601,46 @@ static void da_kfree_cb(struct rcu_head *head)
 	kfree(ms);
 }
 
+/*
+ * Zero-initialised for non-pool monitors, whose pool paths compile away.
+ */
+static mempool_t da_monitor_pool;
+
+static void da_pool_return_cb(struct rcu_head *head)
+{
+	struct da_monitor_storage *ms =
+		container_of(head, struct da_monitor_storage, rcu);
+
+	da_monitor_free_hook(&ms->rv.da_mon);
+	mempool_free(ms, &da_monitor_pool);
+}
+
+/*
+ * da_create_pool_storage - pop a free pool slot and insert it into the hash.
+ * Returns NULL if the pool is exhausted.  Caller holds an RCU read-side CS
+ * and the monitor's serialisation lock.
+ */
+static inline struct da_monitor *
+da_create_pool_storage(da_id_type id, monitor_target target,
+		       struct da_monitor *da_mon)
+{
+	struct da_monitor_storage *mon_storage;
+
+	if (da_mon)
+		return da_mon;
+
+	mon_storage = mempool_alloc_preallocated(&da_monitor_pool);
+	if (!mon_storage)
+		return NULL;
+	memset(mon_storage, 0, sizeof(*mon_storage));
+
+	mon_storage->id = id;
+	mon_storage->target = target;
+
+	hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
+	return &mon_storage->rv.da_mon;
+}
+
 /*
  * da_destroy_storage - destroy the per-object storage
  *
@@ -588,7 +666,10 @@ static inline void da_destroy_storage(da_id_type id)
 	 * A reader from before hash_del_rcu() may still arm; the free
 	 * callback runs past it, so the hook disarms there.
 	 */
-	call_rcu(&mon_storage->rcu, da_kfree_cb);
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+		call_rcu(&mon_storage->rcu, da_pool_return_cb);
+	else
+		call_rcu(&mon_storage->rcu, da_kfree_cb);
 }
 
 static void __da_monitor_reset_all(void (*reset)(struct da_monitor *))
@@ -614,6 +695,13 @@ static inline void da_monitor_reset_state_all(void)
 static inline int da_monitor_init(void)
 {
 	hash_init(da_monitor_ht);
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL) {
+		size_t storage_size = sizeof(struct da_monitor_storage);
+
+		return mempool_init_kmalloc_pool(&da_monitor_pool,
+						 DA_MON_POOL_SIZE,
+						 storage_size);
+	}
 	return 0;
 }
 
@@ -633,20 +721,33 @@ static inline void da_monitor_destroy(void)
 	hash_for_each_safe(da_monitor_ht, bkt, tmp, mon_storage, node) {
 		da_monitor_free_hook(&mon_storage->rv.da_mon);
 		hash_del_rcu(&mon_storage->node);
-		kfree(mon_storage);
+		if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+			mempool_free(mon_storage, &da_monitor_pool);
+		else
+			kfree(mon_storage);
+	}
+
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL) {
+		rcu_barrier();
+		mempool_exit(&da_monitor_pool);
 	}
 }
 
 /*
- * Allow the per-object monitors to run allocation manually, necessary if the
- * start condition is in a context problematic for allocation (e.g. scheduling).
- * In such case, if the storage was pre-allocated without a target, set it now.
+ * da_prepare_storage - allocate or link per-object monitor storage.
+ * Only the first start allocates; later starts reuse the existing da_mon.
+ * Must run in task context (see the allocation strategies above).
  */
-#ifdef DA_SKIP_AUTO_ALLOC
-#define da_prepare_storage da_fill_empty_storage
-#else
-#define da_prepare_storage da_create_storage
-#endif /* DA_SKIP_AUTO_ALLOC */
+static inline struct da_monitor *
+da_prepare_storage(da_id_type id, monitor_target target,
+		   struct da_monitor *da_mon)
+{
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_POOL)
+		return da_create_pool_storage(id, target, da_mon);
+	if (DA_MON_ALLOCATION_STRATEGY == DA_ALLOC_MANUAL)
+		return da_fill_empty_storage(id, target, da_mon);
+	return da_create_storage(id, target, da_mon);
+}
 
 #endif /* RV_MON_TYPE */
 
kernel/trace/rv/monitors/nomiss/nomiss.c +1 -1
index 6e47d379f777..c80a2c51c514 100644
@@ -18,7 +18,7 @@
 #define RV_MON_TYPE RV_MON_PER_OBJ
 #define HA_TIMER_TYPE HA_TIMER_WHEEL
 /* The start condition is on sched_switch, it's dangerous to allocate there */
-#define DA_SKIP_AUTO_ALLOC
+#define DA_MON_ALLOCATION_STRATEGY DA_ALLOC_MANUAL
 typedef struct sched_dl_entity *monitor_target;
 #include "nomiss.h"
 #include <rv/ha_monitor.h>
Signature · 1 line
2.25.1
#8 lore raw

PATCH v8 8/14 Support embedded target types in per-object DA storage

In reply to [email protected]

From: Wen Yang <[email protected]>

A PER_OBJ monitor usually typedefs monitor_target itself, as a pointer to the monitored object (borrowed: the object exists independently of the monitor and outlives the storage).

A monitor whose target is monitor-owned data can instead define DA_OBJ_EMBEDDED_TYPE with a complete struct type: the framework then derives monitor_target as a pointer to it, embeds one instance in each per-object storage, and allocates and frees it together with the storage. On storage creation the caller-supplied target is shallow-copied into the storage before it is published, so it may be a fully initialised stack object.

This allows per-object monitors to manage custom per-instance data without maintaining private mempools, complex multi-stage reference counting, or out-of-band teardown state machines.

---
 include/rv/da_monitor.h | 82 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 76 insertions(+), 6 deletions(-)
include/rv/da_monitor.h +76 -6
index 76169e41d4e5..ed9e7e740d80 100644
@@ -61,6 +61,10 @@
 #include <linux/slab.h>
 #include <linux/hashtable.h>
 
+#if defined(DA_OBJ_EMBEDDED_TYPE) && RV_MON_TYPE != RV_MON_PER_OBJ
+#error "DA_OBJ_EMBEDDED_TYPE is only supported by RV_MON_PER_OBJ monitors"
+#endif
+
 /*
  * Per-cpu variables require a unique name although static in some
  * configurations (e.g. CONFIG_DEBUG_FORCE_WEAK_PER_CPU or alpha modules).
@@ -453,9 +457,24 @@ static inline void da_monitor_destroy(void)
  * Functions to define, init and get a per-object monitor.
  */
 
+#ifdef DA_OBJ_EMBEDDED_TYPE
+/*
+ * Framework-owned target: one instance is embedded in each storage and
+ * allocated/freed with it.  monitor_target keeps pointer semantics, but
+ * da_get_target() points into the storage rather than to a borrowed object.
+ * The header derives the typedef: do not define monitor_target by hand.
+ */
+typedef DA_OBJ_EMBEDDED_TYPE * monitor_target;
+#endif
+
 struct da_monitor_storage {
 	da_id_type id;
+#ifdef DA_OBJ_EMBEDDED_TYPE
+	DA_OBJ_EMBEDDED_TYPE target;
+	bool filled;
+#else
 	monitor_target target;
+#endif
 	union rv_task_monitor rv;
 	struct hlist_node node;
 	struct rcu_head rcu;
@@ -466,6 +485,40 @@ struct da_monitor_storage {
 #endif
 static DEFINE_HASHTABLE(da_monitor_ht, DA_MONITOR_HT_BITS);
 
+#ifdef DA_OBJ_EMBEDDED_TYPE
+
+#define TARGET_PTR(storage) (&(storage)->target)
+
+static inline bool da_storage_is_empty(struct da_monitor_storage *mon_storage)
+{
+	/* Pairs with smp_store_release in da_storage_set_target(). */
+	return !smp_load_acquire(&mon_storage->filled);
+}
+static inline void da_storage_set_target(struct da_monitor_storage *mon_storage,
+					 monitor_target target)
+{
+	if (WARN_ON_ONCE(!target))
+		return;
+
+	memcpy(&mon_storage->target, target, sizeof(mon_storage->target));
+	/* Pairs with smp_load_acquire in da_storage_is_empty(). */
+	smp_store_release(&mon_storage->filled, true);
+}
+#else
+
+#define TARGET_PTR(storage) storage->target
+
+static inline bool da_storage_is_empty(struct da_monitor_storage *mon_storage)
+{
+	return !TARGET_PTR(mon_storage);
+}
+static inline void da_storage_set_target(struct da_monitor_storage *mon_storage,
+					 monitor_target target)
+{
+	mon_storage->target = target;
+}
+#endif
+
 /*
  * da_create_empty_storage - pre-allocate an empty storage
  */
@@ -503,7 +556,7 @@ static inline struct da_monitor *da_create_storage(da_id_type id,
 	if (!mon_storage)
 		return NULL;
 
-	mon_storage->target = target;
+	da_storage_set_target(mon_storage, target);
 	return &mon_storage->rv.da_mon;
 }
 
@@ -536,10 +589,20 @@ static struct da_monitor *da_get_monitor(da_id_type id, monitor_target target)
 
 /*
  * da_get_target - return the object associated to the monitor
+ *
+ * NULL for an embedded target whose storage was pre-allocated but not filled
+ * yet: a manual-allocation monitor publishes its storage before it has one.
  */
 static inline monitor_target da_get_target(struct da_monitor *da_mon)
 {
-	return container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target;
+	struct da_monitor_storage *mon_storage =
+		container_of(da_mon, struct da_monitor_storage, rv.da_mon);
+
+#ifdef DA_OBJ_EMBEDDED_TYPE
+	if (unlikely(da_storage_is_empty(mon_storage)))
+		return NULL;
+#endif
+	return TARGET_PTR(mon_storage);
 }
 
 /*
@@ -572,8 +635,11 @@ static inline struct da_monitor *da_fill_empty_storage(da_id_type id,
 						       monitor_target target,
 						       struct da_monitor *da_mon)
 {
-	if (unlikely(da_mon && !da_get_target(da_mon)))
-		container_of(da_mon, struct da_monitor_storage, rv.da_mon)->target = target;
+	struct da_monitor_storage *mon_storage =
+		container_of(da_mon, struct da_monitor_storage, rv.da_mon);
+
+	if (unlikely(da_mon && da_storage_is_empty(mon_storage)))
+		da_storage_set_target(mon_storage, target);
 	return da_mon;
 }
 
@@ -589,7 +655,11 @@ static inline monitor_target da_get_target_by_id(da_id_type id)
 
 	if (unlikely(!mon_storage))
 		return NULL;
-	return mon_storage->target;
+#ifdef DA_OBJ_EMBEDDED_TYPE
+	if (unlikely(da_storage_is_empty(mon_storage)))
+		return NULL;
+#endif
+	return TARGET_PTR(mon_storage);
 }
 
 static void da_kfree_cb(struct rcu_head *head)
@@ -635,7 +705,7 @@ da_create_pool_storage(da_id_type id, monitor_target target,
 	memset(mon_storage, 0, sizeof(*mon_storage));
 
 	mon_storage->id = id;
-	mon_storage->target = target;
+	da_storage_set_target(mon_storage, target);
 
 	hash_add_rcu(da_monitor_ht, &mon_storage->node, id);
 	return &mon_storage->rv.da_mon;
Signature · 1 line
2.25.1
#9 lore raw

PATCH v8 9/14 Add generic uprobe infrastructure for RV monitors

In reply to [email protected]

From: Wen Yang <[email protected]>

Monitors that instrument user-space function boundaries need to 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 an inode reference (acquired via igrab()) for the probed binary for the full uprobe lifetime, as uprobe_register() does not take an inode reference. On unregister, the inode reference is released with iput() only after rv_uprobe_unregister_sync().

rv_uprobe_unregister_sync() calls uprobe_unregister_sync(), draining all in-flight handlers before the containing struct may be freed.

The API provides register, nosync unregister, a global handler barrier (rv_uprobe_unregister_sync), and an active-state predicate.

---
 include/rv/rv_uprobe.h      | 35 +++++++++++++++++++++++
 kernel/trace/rv/Kconfig     |  4 +++
 kernel/trace/rv/Makefile    |  1 +
 kernel/trace/rv/rv_uprobe.c | 56 +++++++++++++++++++++++++++++++++++++
 4 files changed, 96 insertions(+)
 create mode 100644 include/rv/rv_uprobe.h
 create mode 100644 kernel/trace/rv/rv_uprobe.c
include/rv/rv_uprobe.h new file +35 -0
new file mode 100644
index 000000000000..b93c2a81bc22
@@ -0,0 +1,35 @@
+/* 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/types.h>
+#include <linux/uprobes.h>
+
+struct inode;
+struct pt_regs;
+
+/*
+ * Free the containing struct only after rv_uprobe_unregister_nosync() +
+ * rv_uprobe_unregister_sync() + iput(p->inode).
+ *
+ * The inode reference does not pin the dentry, so callers that re-resolve the
+ * probed path must hold a struct path: 9p cache=none remints an inode per
+ * lookup.
+ */
+struct rv_uprobe {
+	struct uprobe_consumer	uc;
+	struct uprobe		*uprobe;
+	struct inode		*inode;
+};
+
+bool rv_uprobe_is_registered(const struct rv_uprobe *p);
+int rv_uprobe_register(struct inode *inode, loff_t offset, struct rv_uprobe *p);
+void rv_uprobe_unregister_nosync(struct rv_uprobe *p);
+void rv_uprobe_unregister_sync(void);
+
+#endif /* _RV_UPROBE_H */
kernel/trace/rv/Kconfig +4 -0
index efa930f94ea4..2bc412304293 100644
@@ -86,6 +86,10 @@ source "kernel/trace/rv/monitors/nomiss/Kconfig"
 
 # Add new monitors here
 
+config RV_UPROBE
+	bool
+	depends on RV && UPROBES
+
 config RV_REACTORS
 	bool "Runtime verification reactors"
 	default y
kernel/trace/rv/Makefile +1 -0
index 4b84b83b942b..8dc14a1819b8 100644
@@ -22,6 +22,7 @@ obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
 obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
 obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o
 # Add new monitors here
+obj-$(CONFIG_RV_UPROBE) += rv_uprobe.o
 obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
 obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
 obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
kernel/trace/rv/rv_uprobe.c new file +56 -0

56 lines not on this page · raw message

Signature · 1 line
2.25.1
#10 lore raw

PATCH v8 10/14 Add tlob model DOT file

In reply to [email protected]

From: Wen Yang <[email protected]>

Add the Graphviz DOT specification of the tlob hybrid automaton to tools/verification/models/.

The model has four states (stopped, running, waiting, sleeping) and six events (start, stop, sleep, preempt, wakeup, switch_in). A single clock invariant, clk_elapsed < BUDGET_NS(), holds in running, waiting and sleeping; stopped has no invariant, hence no timer.

wakeup is a self-loop on running and waiting: a task that is already queued can still be the target of a traced wakeup, either by waking itself or through ttwu_runnable() while preempted, and neither case ends the budget window. stopped takes every scheduler event as a self-loop, so a task keeps going through the scheduler between windows.

---
 tools/verification/models/tlob.dot | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
 create mode 100644 tools/verification/models/tlob.dot
tools/verification/models/tlob.dot new file +27 -0
new file mode 100644
index 000000000000..021a4d823548
@@ -0,0 +1,27 @@
+digraph state_automaton {
+	center = true;
+	size = "7,11";
+	{node [shape = plaintext, style=invis, label=""] "__init_stopped"};
+	{node [shape = plaintext] "running"};
+	{node [shape = plaintext] "waiting"};
+	{node [shape = plaintext] "sleeping"};
+	{node [shape = ellipse] "stopped"};
+	"__init_stopped" -> "stopped";
+	"running"  [label = "running\nclk_elapsed <= BUDGET_NS()"];
+	"waiting"  [label = "waiting\nclk_elapsed <= BUDGET_NS()"];
+	"sleeping" [label = "sleeping\nclk_elapsed <= BUDGET_NS()"];
+	"stopped"  [label = "stopped", color = green3];
+	"running"  -> "sleeping" [ label = "sleep" ];
+	"running"  -> "waiting"  [ label = "preempt" ];
+	"running"  -> "running"  [ label = "wakeup" ];
+	"waiting"  -> "running"  [ label = "switch_in" ];
+	"waiting"  -> "waiting"  [ label = "wakeup" ];
+	"sleeping" -> "waiting"  [ label = "wakeup" ];
+	"running"  -> "stopped"  [ label = "stop" ];
+	"stopped"  -> "running"  [ label = "start;reset(clk_elapsed)" ];
+	"stopped"  -> "stopped"  [ label = "preempt\nsleep\nswitch_in\nwakeup" ];
+	{ rank = min ;
+		"__init_stopped";
+		"stopped";
+	}
+}
Signature · 1 line
2.25.1
#11 lore raw

PATCH v8 11/14 Add tlob hybrid automaton monitor

In reply to [email protected]

From: Wen Yang <[email protected]>

tlob (task latency over budget) is a per-task hybrid automaton RV monitor that tracks wall-clock time across a user-delimited code section and emits an error when the elapsed time exceeds a configurable budget.

The monitor tracks four states (running, waiting, sleeping, stopped) driven by sched_switch and sched_wakeup tracepoints. A single clock invariant, clk_elapsed < BUDGET_NS(), is enforced by a per-task hrtimer (HRTIMER_MODE_REL_HARD). On expiry error_env_tlob is emitted; which phase consumed the budget (running, waiting or sleeping) is recovered from event_tlob in user space.

Tasks are registered for monitoring by writing to the tracefs monitor file using the ftrace uprobe_events convention:
echo "p:myregion /path/to/binary:OFFSET_START OFFSET_STOP threshold=NS" \
>> /sys/kernel/tracing/rv/monitors/tlob/monitor

The per-task monitoring record is embedded directly into struct da_monitor_storage via DA_OBJ_EMBEDDED_TYPE, eliminating the need for a private mempool, separate RCU reclamation, or parallel OPEN/CLOSED/RETIRED states. Window transitions and reentrancy are managed directly by the automaton model and framework storage lifecycle.

---
 Documentation/trace/rv/index.rst           |   1 +
 Documentation/trace/rv/monitor_tlob.rst    | 162 +++++
 kernel/trace/rv/Kconfig                    |   1 +
 kernel/trace/rv/Makefile                   |   1 +
 kernel/trace/rv/monitors/tlob/Kconfig      |  12 +
 kernel/trace/rv/monitors/tlob/tlob.c       | 732 +++++++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob.h       | 143 ++++
 kernel/trace/rv/monitors/tlob/tlob_trace.h |  20 +
 kernel/trace/rv/rv_trace.h                 |   1 +
 9 files changed, 1073 insertions(+)
 create mode 100644 Documentation/trace/rv/monitor_tlob.rst
 create mode 100644 kernel/trace/rv/monitors/tlob/Kconfig
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob.h
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_trace.h
Documentation/trace/rv/index.rst +1 -0
index 29769f06bb0f..1501545b5f08 100644
@@ -16,5 +16,6 @@ Runtime Verification
    monitor_wwnr.rst
    monitor_sched.rst
    monitor_rtapp.rst
+   monitor_tlob.rst
    monitor_stall.rst
    monitor_deadline.rst
Documentation/trace/rv/monitor_tlob.rst new file +162 -0

162 lines not on this page · raw message

kernel/trace/rv/Kconfig +1 -0
index 2bc412304293..68f0b9bc2dc3 100644
@@ -82,6 +82,7 @@ source "kernel/trace/rv/monitors/wakeup/Kconfig"
 source "kernel/trace/rv/monitors/stall/Kconfig"
 source "kernel/trace/rv/monitors/deadline/Kconfig"
 source "kernel/trace/rv/monitors/nomiss/Kconfig"
+source "kernel/trace/rv/monitors/tlob/Kconfig"
 # Add new deadline monitors here
 
 # Add new monitors here
kernel/trace/rv/Makefile +1 -0
index 8dc14a1819b8..99a8dceb829f 100644
@@ -20,6 +20,7 @@ obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o
 obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o
 obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
 obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
+obj-$(CONFIG_RV_MON_TLOB) += monitors/tlob/tlob.o
 obj-$(CONFIG_RV_MON_WAKEUP) += monitors/wakeup/wakeup.o
 # Add new monitors here
 obj-$(CONFIG_RV_UPROBE) += rv_uprobe.o
kernel/trace/rv/monitors/tlob/Kconfig new file +12 -0
new file mode 100644
index 000000000000..4f072a9a4da2
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+config RV_MON_TLOB
+	bool "tlob monitor"
+	depends on RV && UPROBES && HIGH_RES_TIMERS
+	select HA_MON_EVENTS_ID
+	select RV_UPROBE
+	help
+	  Enable the tlob (task latency over budget) hybrid-automaton RV
+	  monitor.  tlob tracks per-task elapsed time across a
+	  user-delimited code section and emits a violation when the
+	  elapsed time exceeds a configurable per-invocation budget.
kernel/trace/rv/monitors/tlob/tlob.c new file +732 -0

732 lines not on this page · raw message

kernel/trace/rv/monitors/tlob/tlob.h new file +143 -0

143 lines not on this page · raw message

kernel/trace/rv/monitors/tlob/tlob_trace.h new file +20 -0
new file mode 100644
index 000000000000..cda3f39fc110
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Snippet to be included in rv_trace.h
+ */
+
+#ifdef CONFIG_RV_MON_TLOB
+DEFINE_EVENT(event_da_monitor_id, event_tlob,
+	     TP_PROTO(int id, char *state, char *event,
+		      char *next_state, bool final_state),
+	     TP_ARGS(id, state, event, next_state, final_state));
+
+DEFINE_EVENT(error_da_monitor_id, error_tlob,
+	     TP_PROTO(int id, char *state, char *event),
+	     TP_ARGS(id, state, event));
+
+DEFINE_EVENT(error_env_da_monitor_id, error_env_tlob,
+	     TP_PROTO(int id, char *state, char *event, char *env),
+	     TP_ARGS(id, state, event, env));
+#endif /* CONFIG_RV_MON_TLOB */
kernel/trace/rv/rv_trace.h +1 -0
index 2f8a932432c9..4bfa39717cef 100644
@@ -189,6 +189,7 @@ DECLARE_EVENT_CLASS(error_env_da_monitor_id,
 
 #include <monitors/stall/stall_trace.h>
 #include <monitors/nomiss/nomiss_trace.h>
+#include <monitors/tlob/tlob_trace.h>
 // Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
 
 #endif
Signature · 1 line
2.25.1
#12 lore raw

PATCH v8 12/14 Add KUnit tests for the tlob monitor

In reply to [email protected]

From: Wen Yang <[email protected]>

Add a test case to the shared rv_monitors_test.c suite covering the uprobe-line parser in the tlob monitor.

Cases cover valid inputs, malformed paths and offsets (including negative values), out-of-range thresholds, and valid and invalid remove lines.

---
 kernel/trace/rv/monitors/tlob/tlob.c       |  11 ++
 kernel/trace/rv/monitors/tlob/tlob_kunit.c | 151 +++++++++++++++++++++
 kernel/trace/rv/monitors/tlob/tlob_kunit.h |  18 +++
 kernel/trace/rv/rv_monitors_test.c         |   2 +
 4 files changed, 182 insertions(+)
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.c
 create mode 100644 kernel/trace/rv/monitors/tlob/tlob_kunit.h
kernel/trace/rv/monitors/tlob/tlob.c +11 -0
index fe7d88df62f1..62b0d3330be8 100644
@@ -593,6 +593,17 @@ static int tlob_parse_remove_line(char *buf, char **name_out)
 	return 0;
 }
 
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+#include <kunit/visibility.h>
+#include "tlob_kunit.h"
+
+const struct rv_tlob_kunit_ops rv_tlob_kunit_ops = {
+	.parse_uprobe_line = tlob_parse_uprobe_line,
+	.parse_remove_line = tlob_parse_remove_line,
+};
+EXPORT_SYMBOL_IF_KUNIT(rv_tlob_kunit_ops);
+#endif
+
 static int tlob_create_or_delete_uprobe(char *buf)
 {
 	loff_t offset_start, offset_stop;
kernel/trace/rv/monitors/tlob/tlob_kunit.c new file +151 -0

151 lines not on this page · raw message

kernel/trace/rv/monitors/tlob/tlob_kunit.h new file +18 -0
new file mode 100644
index 000000000000..c0c80c34f17b
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __TLOB_KUNIT_H
+#define __TLOB_KUNIT_H
+
+#if IS_ENABLED(CONFIG_RV_MONITORS_KUNIT_TEST)
+
+#include <linux/types.h>
+
+extern const struct rv_tlob_kunit_ops {
+	int (*parse_uprobe_line)(char *buf, char **name_out, char **path_out,
+				 loff_t *start_out, loff_t *stop_out,
+				 u64 *thr_out);
+	int (*parse_remove_line)(char *buf, char **name_out);
+} rv_tlob_kunit_ops;
+
+#endif
+
+#endif /* __TLOB_KUNIT_H */
kernel/trace/rv/rv_monitors_test.c +2 -0
index 3ad11195e664..548607e7c1a7 100644
@@ -151,6 +151,7 @@ static void rv_test_dummy(struct kunit *test)
 #include "monitors/sts/sts_kunit.c"
 #include "monitors/opid/opid_kunit.c"
 #include "monitors/nomiss/nomiss_kunit.c"
+#include "monitors/tlob/tlob_kunit.c"
 #include "monitors/pagefault/pagefault_kunit.c"
 #include "monitors/sleep/sleep_kunit.c"
 
@@ -161,6 +162,7 @@ static struct kunit_case rv_mon_test_cases[] = {
 	KUNIT_CASE(rv_test_sts),
 	KUNIT_CASE(rv_test_opid),
 	KUNIT_CASE(rv_test_nomiss),
+	KUNIT_CASE(rv_test_tlob),
 	KUNIT_CASE(rv_test_pagefault),
 	KUNIT_CASE(rv_test_sleep),
 	{}
Signature · 1 line
2.25.1
#13 lore raw

PATCH v8 13/14 selftests/verification: Add tlob selftests

In reply to [email protected]

From: Wen Yang <[email protected]>

Add thirteen ftrace-style test scripts for the tlob RV monitor under tools/testing/selftests/verification/test.d/tlob/. The tests cover uprobe binding management, budget violation detection, multi-binding, multi-task sharing of one binding, overlap registration rejection, unbind and disable on live tasks, start/stop restart (both normal and after a violation), task exit inside a live window, nested START reporting, and the 64-task pool exhaustion cap.

Helper binary tlob_target is built via TEST_GEN_FILES, following the standard kselftests convention. run_tlob_tests.sh verifies helper availability and delegates argument handling to ftracetest.

The multi-instance test, covering several tasks bound to the same uprobe pair, was contributed by Gabriele Monaco.

---
 .../testing/selftests/verification/.gitignore |   1 +
 tools/testing/selftests/verification/Makefile |   4 +
 tools/testing/selftests/verification/config   |   3 +
 .../selftests/verification/test.d/functions   |   9 +
 .../test.d/tlob/run_tlob_tests.sh             |  32 ++++
 .../verification/test.d/tlob/uprobe_bind.tc   |  47 ++++++
 .../test.d/tlob/uprobe_disable_live.tc        |  79 +++++++++
 .../verification/test.d/tlob/uprobe_multi.tc  |  58 +++++++
 .../test.d/tlob/uprobe_multi_instance.tc      |  65 ++++++++
 .../test.d/tlob/uprobe_nested_start.tc        |  62 +++++++
 .../test.d/tlob/uprobe_no_event.tc            |  17 ++
 .../test.d/tlob/uprobe_overlap_register.tc    |  66 ++++++++
 .../test.d/tlob/uprobe_pool_exhausted.tc      |  75 +++++++++
 .../test.d/tlob/uprobe_restart.tc             |  66 ++++++++
 .../test.d/tlob/uprobe_restart_violation.tc   |  48 ++++++
 .../test.d/tlob/uprobe_task_exit.tc           |  65 ++++++++
 .../test.d/tlob/uprobe_unbind_live.tc         |  65 ++++++++
 .../test.d/tlob/uprobe_violation.tc           |  53 ++++++
 .../selftests/verification/tlob_target.c      | 156 ++++++++++++++++++
 19 files changed, 971 insertions(+)
 create mode 100755 tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_nested_start.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_pool_exhausted.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_task_exit.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc
 create mode 100644 tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc
 create mode 100644 tools/testing/selftests/verification/tlob_target.c
tools/testing/selftests/verification/.gitignore +1 -0
index 2659417cb2c7..92d7ebdb1dd1 100644
@@ -1,2 +1,3 @@
 # SPDX-License-Identifier: GPL-2.0-only
 logs
+tlob_target
tools/testing/selftests/verification/Makefile +4 -0
index aa8790c22a71..dcdfe3ea3c06 100644
@@ -5,4 +5,8 @@ TEST_PROGS := verificationtest-ktap
 TEST_FILES := test.d settings
 EXTRA_CLEAN := $(OUTPUT)/logs/*
 
+TEST_GEN_FILES := tlob_target
+
 include ../lib.mk
+
+export VERIFICATIONTEST_BINDIR := $(OUTPUT)
tools/testing/selftests/verification/config +3 -0
index 43072c1c38f4..5869ae4a9217 100644
@@ -1 +1,4 @@
 CONFIG_RV=y
+CONFIG_RV_MON_TLOB=y
+CONFIG_UPROBES=y
+CONFIG_HIGH_RES_TIMERS=y
tools/testing/selftests/verification/test.d/functions +9 -0
index ec36a092f56e..43c119682c1f 100644
@@ -25,6 +25,15 @@ check_requires() { # Check required files, monitors and reactors
 	done
 }
 
+sym_offset() { # <binary> <symbol>: print the ELF file offset of <symbol>
+	readelf -W -S -s "$1" | awk -v symbol="$2" '
+		{ gsub(/\[ /, "[") }
+		$1 ~ /^\[[0-9]+\]$/ { sections[$1]="0x"$4; offsets[$1]="0x"$5 }
+		$1 ~ /^[0-9]+:$/ && $NF == symbol { addr="0x"$2; sec="["$7"]" }
+		END { printf "printf \"0x%%x\\n\" $((%s - %s + %s))\n", addr, sections[sec], offsets[sec] }
+	' | sh
+}
+
 initialize_system() { # Reset RV to initial-state
 	echo > enabled_monitors
 	for m in monitors/*; do
tools/testing/selftests/verification/test.d/tlob/run_tlob_tests.sh new file +32 -0
new file mode 100755
index 000000000000..c87484813fc8
@@ -0,0 +1,32 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+#
+# Standalone runner for tlob selftests
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+BINDIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
+FTRACETEST="$(cd "$SCRIPT_DIR/../../../ftrace" && pwd)/ftracetest"
+
+# The helper is built on the host by the selftests Makefile
+# (TEST_GEN_FILES in $BINDIR/Makefile); the VM (virtme-ng) mounts the
+# source read-only and may lack a compiler, so this runner never builds.
+# Build it on the host first:
+#   make -C "$BINDIR" tlob_target
+if [ ! -x "$BINDIR/tlob_target" ]; then
+	echo "ERROR: tlob_target not built in $BINDIR" >&2
+	echo "       build it on the host: make -C $BINDIR tlob_target" >&2
+	exit 1
+fi
+
+# Flag (not fail) the helper older than its source: git checkouts can bump
+# source mtimes, so a hard check would misfire; a host rebuild picks up
+# the new code.
+if [ -f "$BINDIR/tlob_target.c" ] &&
+   [ "$BINDIR/tlob_target" -ot "$BINDIR/tlob_target.c" ]; then
+	echo "WARNING: $BINDIR/tlob_target is older than $BINDIR/tlob_target.c;" >&2
+	echo "         rebuild on the host to test the current source" >&2
+fi
+
+export VERIFICATIONTEST_BINDIR="$BINDIR"
+
+exec "$FTRACETEST" -K --rv "$SCRIPT_DIR" "$@"
tools/testing/selftests/verification/test.d/tlob/uprobe_bind.tc new file +47 -0

47 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_disable_live.tc new file +79 -0

79 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_multi.tc new file +58 -0

58 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_multi_instance.tc new file +65 -0

65 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_nested_start.tc new file +62 -0

62 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_no_event.tc new file +17 -0
new file mode 100644
index 000000000000..23f8abeca090
@@ -0,0 +1,17 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# description: Test tlob monitor no spurious events without active uprobe binding
+# requires: tlob:monitor
+
+echo 1 > ../events/rv/error_env_tlob/enable
+echo 1 > ../tracing_on
+echo 1 > monitors/tlob/enable
+echo > ../trace
+
+sleep 0.5
+
+! grep -q "error_env_tlob" ../trace || false
+
+echo 0 > monitors/tlob/enable
+echo 0 > ../events/rv/error_env_tlob/enable
+echo > ../trace
tools/testing/selftests/verification/test.d/tlob/uprobe_overlap_register.tc new file +66 -0

66 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_pool_exhausted.tc new file +75 -0

75 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_restart.tc new file +66 -0

66 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_restart_violation.tc new file +48 -0

48 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_task_exit.tc new file +65 -0

65 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_unbind_live.tc new file +65 -0

65 lines not on this page · raw message

tools/testing/selftests/verification/test.d/tlob/uprobe_violation.tc new file +53 -0

53 lines not on this page · raw message

tools/testing/selftests/verification/tlob_target.c new file +156 -0

156 lines not on this page · raw message

Signature · 1 line
2.25.1
#14 lore raw

PATCH v8 14/14 selftests/ftrace: Walk up parent dirs to find test.d/functions

In reply to [email protected]

From: Wen Yang <[email protected]>

When a subdirectory is passed (e.g. an RV monitor's test directory), ftracetest fell back to its own functions file, losing the RV-specific check_requires handling for ':monitor' and ':reactor' requirements.

Walk up from OPT_TEST_DIR until a directory containing test.d/functions is found, stopping after five levels so a stray argument cannot reach an unrelated functions file further up the tree. When no such directory is found the previous default is kept.

Note that TOP_DIR also feeds LOG_TOP_DIR, so logs now follow the directory the tests were found in: an RV run writes to verification/logs rather than ftrace/logs.

---
 tools/testing/selftests/ftrace/ftracetest | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)
tools/testing/selftests/ftrace/ftracetest +16 -3
index 0a56bf209f6c..3d3c3a4dfa34 100755
@@ -159,9 +159,22 @@ parse_opts() { # opts
   if [ -n "$OPT_TEST_CASES" ]; then
     TEST_CASES=$OPT_TEST_CASES
   fi
-  if [ -n "$OPT_TEST_DIR" -a -f "$OPT_TEST_DIR"/test.d/functions ]; then
-    TOP_DIR=$OPT_TEST_DIR
-    TEST_DIR=$TOP_DIR/test.d
+  if [ -n "$OPT_TEST_DIR" ]; then
+    # A subdirectory of test.d can be given; walk up to the directory
+    # holding test.d/functions.  The bound keeps a stray directory from
+    # reaching an unrelated functions file further up the tree.
+    local dir=$OPT_TEST_DIR
+    local depth=0
+    while [ "$depth" -lt 5 ]; do
+      if [ -f "$dir/test.d/functions" ]; then
+        TOP_DIR=$dir
+        TEST_DIR=$TOP_DIR/test.d
+        break
+      fi
+      case "$dir" in /|//|.) break;; esac
+      dir=$(dirname "$dir")
+      depth=$((depth + 1))
+    done
   fi
 }
 
Signature · 1 line
2.25.1