* [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-01 15:49 ` Peter Zijlstra
2026-10-02 0:42 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
` (13 subsequent siblings)
14 siblings, 2 replies; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
Masami Hiramatsu, Ingo Molnar, Peter Zijlstra
Cc: Nam Cao, K Prateek Nayak, Gabriele Monaco, Alexei Starovoitov,
Wen Yang, Tobias Schaffner, Viktor Malik
From: Nam Cao <namcao@linutronix.de>
Add trace points into enqueue_task() and dequeue_task().
Signed-off-by: Nam Cao <namcao@linutronix.de>
Suggested-by: Peter Zijlstra <peterz@infradead.org>
Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
Co-developed-by: Gabriele Monaco <gmonaco@redhat.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/trace/events/sched.h | 8 ++++++++
kernel/sched/core.c | 12 +++++++++++-
kernel/sched/sched.h | 2 ++
3 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/include/trace/events/sched.h b/include/trace/events/sched.h
index 535860581f15..d6e41edc25d2 100644
--- a/include/trace/events/sched.h
+++ b/include/trace/events/sched.h
@@ -896,6 +896,14 @@ DECLARE_TRACE(sched_set_need_resched,
TP_PROTO(struct task_struct *tsk, int cpu, int tif),
TP_ARGS(tsk, cpu, tif));
+DECLARE_TRACE(sched_enqueue,
+ TP_PROTO(struct task_struct *tsk, int cpu),
+ TP_ARGS(tsk, cpu));
+
+DECLARE_TRACE(sched_dequeue,
+ TP_PROTO(struct task_struct *tsk, int cpu),
+ TP_ARGS(tsk, cpu));
+
#define DL_OTHER 0
#define DL_TASK 1
#define DL_SERVER_FAIR 2
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 7885ff76e69f..debe5290b72f 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -121,6 +121,8 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(sched_compute_energy_tp);
EXPORT_TRACEPOINT_SYMBOL_GPL(sched_entry_tp);
EXPORT_TRACEPOINT_SYMBOL_GPL(sched_exit_tp);
EXPORT_TRACEPOINT_SYMBOL_GPL(sched_set_need_resched_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_enqueue_tp);
+EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dequeue_tp);
EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_throttle_tp);
EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_replenish_tp);
EXPORT_TRACEPOINT_SYMBOL_GPL(sched_dl_update_tp);
@@ -2181,6 +2183,9 @@ unsigned long get_wchan(struct task_struct *p)
void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
+ if (trace_sched_enqueue_tp_enabled() && !(flags & ENQUEUE_DELAYED))
+ trace_call__sched_enqueue_tp(p, cpu_of(rq));
+
if (!(flags & ENQUEUE_NOCLOCK))
update_rq_clock(rq);
@@ -2207,6 +2212,8 @@ void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
*/
inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
{
+ bool ret;
+
if (sched_core_enabled(rq))
sched_core_dequeue(rq, p, flags);
@@ -2223,7 +2230,10 @@ inline bool dequeue_task(struct rq *rq, struct task_struct *p, int flags)
* and mark the task ->sched_delayed.
*/
uclamp_rq_dec(rq, p);
- return p->sched_class->dequeue_task(rq, p, flags);
+ ret = p->sched_class->dequeue_task(rq, p, flags);
+ if (trace_sched_dequeue_tp_enabled() && !(flags & DEQUEUE_SLEEP))
+ trace_call__sched_dequeue_tp(p, cpu_of(rq));
+ return ret;
}
void activate_task(struct rq *rq, struct task_struct *p, int flags)
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf8..a2cb06beafe6 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -3070,6 +3070,8 @@ static inline void sub_nr_running(struct rq *rq, unsigned count)
static inline void __block_task(struct rq *rq, struct task_struct *p)
{
+ trace_sched_dequeue_tp(p, cpu_of(rq));
+
if (p->sched_contributes_to_load)
rq->nr_uninterruptible++;
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points
2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
@ 2026-10-01 15:49 ` Peter Zijlstra
2026-10-02 0:42 ` bot+bpf-ci
1 sibling, 0 replies; 27+ messages in thread
From: Peter Zijlstra @ 2026-10-01 15:49 UTC (permalink / raw)
To: Gabriele Monaco
Cc: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt,
Masami Hiramatsu, Ingo Molnar, Nam Cao, K Prateek Nayak,
Alexei Starovoitov, Wen Yang, Tobias Schaffner, Viktor Malik
On Thu, Oct 01, 2026 at 05:20:28PM +0200, Gabriele Monaco wrote:
> From: Nam Cao <namcao@linutronix.de>
>
> Add trace points into enqueue_task() and dequeue_task().
>
> Signed-off-by: Nam Cao <namcao@linutronix.de>
> Suggested-by: Peter Zijlstra <peterz@infradead.org>
Obviously I don't remember anything at all. But also, you 'forgot' to
send me the rest of the patches which might hold a clue. As is I'm
clueless as to why we want this, since the Changelog also offers none.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points
2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
2026-10-01 15:49 ` Peter Zijlstra
@ 2026-10-02 0:42 ` bot+bpf-ci
1 sibling, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:42 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt,
mhiramat, mingo, peterz
Cc: namcao, kprateek.nayak, gmonaco, alexei.starovoitov, wen.yang,
tobias.schaffner, vmalik, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1374 bytes --]
> sched: Add task enqueue/dequeue trace points
TL;DR: Commit message only: the changelog just restates the diff and doesn't
explain why the new sched_enqueue/sched_dequeue tracepoints are needed, a
question Peter Zijlstra raised on v2 that went unanswered.
> sched: Add task enqueue/dequeue trace points
>
> Add trace points into enqueue_task() and dequeue_task().
>
> Signed-off-by: Nam Cao <namcao@linutronix.de>
> Suggested-by: Peter Zijlstra <peterz@infradead.org>
> Reviewed-by: K Prateek Nayak <kprateek.nayak@amd.com>
> Co-developed-by: Gabriele Monaco <gmonaco@redhat.com>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
Peter Zijlstra asked about the rationale for these tracepoints in the v2
thread:
"As is I'm clueless as to why we want this, since the Changelog also
offers none."
https://lore.kernel.org/all/20261001154934.GT88198@noisy.programming.kicks-ass.net/
No reply was found in the archive, and the changelog above still consists of
the single sentence restating the diff.
Could the changelog explain why the sched_enqueue and sched_dequeue
tracepoints are needed?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:42 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
` (12 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Some rv userspace selftests rely on the pid of the rv process to match
with the output and fail prematurely in case the pid was not found.
If the rv process failed, however, it's likely it will terminate before
catching the pid, and the test reports empty pid ignoring the error that
caused the process to fail.
Report empty pid only when the rv process succeeds (exit code not what
was expected) and continue showing exit code and output otherwise.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/tests/engine.sh | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/tools/verification/tests/engine.sh b/tools/verification/tests/engine.sh
index cfdf2180aad8..4dad43b0b822 100644
--- a/tools/verification/tests/engine.sh
+++ b/tools/verification/tests/engine.sh
@@ -60,8 +60,9 @@ _check() {
failbuf=''
fail=0
- # Suppress any other error if a needed pid is empty
- if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns"; then
+ # Suppress any other error if a needed pid is empty and there was no other error
+ if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns" \
+ && [ $exitcode -eq "$expected_exitcode" ]; then
result=''
failure "# Empty pid for $command"
return 1
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed
2026-10-01 15:20 ` [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
@ 2026-10-02 0:42 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:42 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2104 bytes --]
> tools/rv: Skip empty pid error in selftest if command failed
TL;DR: Commit message only: the parenthetical "(exit code not what was
expected)" looks inverted relative to the code, which reports the empty pid
only when the exit code matches the expected one. No code bug.
> tools/rv: Skip empty pid error in selftest if command failed
>
> Some rv userspace selftests rely on the pid of the rv process to match
> with the output and fail prematurely in case the pid was not found.
> If the rv process failed, however, it's likely it will terminate before
> catching the pid, and the test reports empty pid ignoring the error that
> caused the process to fail.
>
> Report empty pid only when the rv process succeeds (exit code not what
> was expected) and continue showing exit code and output otherwise.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
This isn't a bug in the code, but the parenthetical in the commit message
looks inverted. Is "(exit code not what was expected)" meant to read
"(exit code is what was expected)"?
The patch adds this condition to the empty-pid check in _check():
if [ -z "$pid" ] && grep -q "\$pid" <<< "$patterns" \
&& [ $exitcode -eq "$expected_exitcode" ]; then
result=''
failure "# Empty pid for $command"
return 1
fi
so "# Empty pid for $command" is reported only when the exit code equals the
expected exit code. When the exit code differs, the check is skipped and the
"# Expected exit code" failure is still recorded along with the output.
As written, the message describes the pre-patch behaviour that the change
removes. Something like this would match the code:
Report empty pid only when the rv process succeeds (exit code is what
was expected) and continue showing exit code and output otherwise.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 01/15] sched: Add task enqueue/dequeue trace points Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 02/15] tools/rv: Skip empty pid error in selftest if command failed Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 04/15] rv: Cast result of model_get_*_name() Gabriele Monaco
` (11 subsequent siblings)
14 siblings, 0 replies; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
The da_trace_event() and da_trace_error() functions are currently called
with strings and the callers are converting the state/event enums to the
corresponding string representation.
This is unnecessary and is problematic if an alternative implementation
is needed (e.g. BPF without using strings).
Change the functions to accept enums and pass the string representations
to the tracepoints only internally.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 40 ++++++++++++++++++++++------------------
1 file changed, 22 insertions(+), 18 deletions(-)
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 6fc5ef8142ac..7f0bdfd7cce3 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -633,19 +633,22 @@ static inline void da_monitor_destroy(void)
*/
static inline void da_trace_event(struct da_monitor *da_mon,
- char *curr_state, char *event,
- char *next_state, bool is_final,
+ enum states curr_state, enum events event,
+ enum states next_state,
da_id_type id)
{
- CONCATENATE(trace_event_, MONITOR_NAME)(curr_state, event, next_state,
- is_final);
+ CONCATENATE(trace_event_, MONITOR_NAME)(model_get_state_name(curr_state),
+ model_get_event_name(event),
+ model_get_state_name(next_state),
+ model_is_final_state(next_state));
}
static inline void da_trace_error(struct da_monitor *da_mon,
- char *curr_state, char *event,
+ enum states curr_state, enum events event,
da_id_type id)
{
- CONCATENATE(trace_error_, MONITOR_NAME)(curr_state, event);
+ CONCATENATE(trace_error_, MONITOR_NAME)(model_get_state_name(curr_state),
+ model_get_event_name(event));
}
/*
@@ -662,19 +665,24 @@ static inline da_id_type da_get_id(struct da_monitor *da_mon)
*/
static inline void da_trace_event(struct da_monitor *da_mon,
- char *curr_state, char *event,
- char *next_state, bool is_final,
+ enum states curr_state, enum events event,
+ enum states next_state,
da_id_type id)
{
- CONCATENATE(trace_event_, MONITOR_NAME)(id, curr_state, event,
- next_state, is_final);
+ CONCATENATE(trace_event_, MONITOR_NAME)(id,
+ model_get_state_name(curr_state),
+ model_get_event_name(event),
+ model_get_state_name(next_state),
+ model_is_final_state(next_state));
}
static inline void da_trace_error(struct da_monitor *da_mon,
- char *curr_state, char *event,
+ enum states curr_state, enum events event,
da_id_type id)
{
- CONCATENATE(trace_error_, MONITOR_NAME)(id, curr_state, event);
+ CONCATENATE(trace_error_, MONITOR_NAME)(id,
+ model_get_state_name(curr_state),
+ model_get_event_name(event));
}
#endif /* RV_MON_TYPE */
@@ -695,17 +703,13 @@ static inline bool da_event(struct da_monitor *da_mon, enum events event, da_id_
next_state = model_get_next_state(curr_state, event);
if (next_state == INVALID_STATE) {
react(curr_state, event);
- da_trace_error(da_mon, model_get_state_name(curr_state),
- model_get_event_name(event), id);
+ da_trace_error(da_mon, curr_state, event, id);
return false;
}
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
if (!da_monitor_event_hook(da_mon, curr_state, event, next_state, id))
return false;
- da_trace_event(da_mon, model_get_state_name(curr_state),
- model_get_event_name(event),
- model_get_state_name(next_state),
- model_is_final_state(next_state), id);
+ da_trace_event(da_mon, curr_state, event, next_state, id);
return true;
}
}
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* [PATCH v2 04/15] rv: Cast result of model_get_*_name()
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (2 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 03/15] rv: Refactor da_trace() functions to get strings internally Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
` (10 subsequent siblings)
14 siblings, 0 replies; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Functions like model_get_event_name() and model_get_state_name() are
shared with BPF monitors, however those programs don't play nice with
string pointers without fixed length and the event_name and state_name
arrays need to be defined differently. This gets the compiler to notice
those are const char and the above function discard the const qualifier.
Drop the warning by casting the result to (char *). Note, a cleaner
solution would require to modify all callers, but that would require
changing every single monitor's tracepoint and the advantage is minimal.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/automata.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/rv/automata.h b/include/rv/automata.h
index 4a4eb40cf09a..3c7070e91d11 100644
--- a/include/rv/automata.h
+++ b/include/rv/automata.h
@@ -27,7 +27,7 @@ static char *model_get_state_name(enum states state)
if ((state < 0) || (state >= STATE_MAX))
return "INVALID";
- return RV_AUTOMATON_NAME.state_names[state];
+ return (char *)RV_AUTOMATON_NAME.state_names[state];
}
/*
@@ -38,7 +38,7 @@ static char *model_get_event_name(enum events event)
if ((event < 0) || (event >= EVENT_MAX))
return "INVALID";
- return RV_AUTOMATON_NAME.event_names[event];
+ return (char *)RV_AUTOMATON_NAME.event_names[event];
}
/*
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (3 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 04/15] rv: Cast result of model_get_*_name() Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:25 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
` (9 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
The configuration of the in-kernel RV monitor is relying on global
variables, some static and some extern and the argument parsing occurs
there. That's not scalable especially if the configuration needs to be
shared by other monitor implementations (e.g. BPF).
Move the common argument parsing in utils and use a global struct for
the configuration. Implementation-specific configuration such as the
reactors are still handled in the in-kernel source.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/include/utils.h | 17 ++-
tools/verification/rv/src/in_kernel.c | 176 ++++++--------------------
tools/verification/rv/src/utils.c | 95 +++++++++++++-
3 files changed, 149 insertions(+), 139 deletions(-)
diff --git a/tools/verification/rv/include/utils.h b/tools/verification/rv/include/utils.h
index f24ae8282bd2..61f77a72a1a2 100644
--- a/tools/verification/rv/include/utils.h
+++ b/tools/verification/rv/include/utils.h
@@ -1,8 +1,23 @@
// SPDX-License-Identifier: GPL-2.0
+#include <stdbool.h>
+
#define MAX_PATH 1024
void debug_msg(const char *fmt, ...);
void err_msg(const char *fmt, ...);
+void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...);
+int parse_arguments(char *monitor_name, int argc, char **argv);
+
+void ikm_usage_print_reactors(void);
-extern int config_debug;
+struct config {
+ bool debug;
+ bool is_container;
+ bool trace;
+ int has_id;
+ int my_pid;
+ char *initial_reactor;
+ char *reactor;
+};
+extern struct config config;
diff --git a/tools/verification/rv/src/in_kernel.c b/tools/verification/rv/src/in_kernel.c
index e6dea4040f8f..90c340a73c76 100644
--- a/tools/verification/rv/src/in_kernel.c
+++ b/tools/verification/rv/src/in_kernel.c
@@ -4,7 +4,6 @@
*
* Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
-#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@@ -16,14 +15,6 @@
#include <utils.h>
#include <rv.h>
-static int config_has_id;
-static int config_is_container;
-static int config_my_pid;
-static int config_trace;
-
-static char *config_initial_reactor;
-static char *config_reactor;
-
/*
* __ikm_read_enable - reads monitor's enable status
*
@@ -66,7 +57,7 @@ static int __ikm_find_monitor_name(char *monitor_name, char *out_name)
if (!available_monitors)
return -1;
- config_is_container = 0;
+ config.is_container = 0;
cursor = available_monitors;
while ((line = strsep(&cursor, "\n"))) {
char *colon = strchr(line, ':');
@@ -83,7 +74,7 @@ static int __ikm_find_monitor_name(char *monitor_name, char *out_name)
/* If there are children, they are on the next line. */
line = strsep(&cursor, "\n");
if (line && !strncmp(line, monitor_name, len) && line[len] == ':')
- config_is_container = 1;
+ config.is_container = 1;
}
found = 1;
@@ -391,7 +382,7 @@ int ikm_list_monitors(char *container)
static void ikm_print_header(struct trace_seq *s)
{
trace_seq_printf(s, "%16s-%-8s %5s %5s ", "<TASK>", "PID", "[CPU]", "TYPE");
- if (config_has_id)
+ if (config.has_id)
trace_seq_printf(s, "%8s ", "ID");
trace_seq_printf(s, "%24s x %-24s -> %-24s %s\n",
@@ -402,7 +393,7 @@ static void ikm_print_header(struct trace_seq *s)
trace_seq_printf(s, "%16s %-8s %5s %5s ", " | ", " | ", " | ", " | ");
- if (config_has_id)
+ if (config.has_id)
trace_seq_printf(s, "%8s ", " | ");
trace_seq_printf(s, "%24s %-24s %-24s %s\n",
@@ -431,25 +422,25 @@ ikm_event_handler(struct trace_seq *s, struct tep_record *record,
int val;
bool missing_id;
- if (config_has_id)
+ if (config.has_id)
missing_id = tep_get_field_val(s, trace_event, "id", record, &id, 1);
tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1);
- if (config_has_id && (config_my_pid == id))
+ if (config.has_id && (config.my_pid == id))
return 0;
- else if (config_my_pid == pid)
+ else if (config.my_pid == pid)
return 0;
tep_print_event(trace_event->tep, s, record, "%16s-%-8d [%.3d] ",
TEP_PRINT_COMM, TEP_PRINT_PID, TEP_PRINT_CPU);
- if (config_is_container)
+ if (config.is_container)
tep_print_event(trace_event->tep, s, record, "%s ", TEP_PRINT_NAME);
else
trace_seq_printf(s, "event ");
- if (config_has_id) {
+ if (config.has_id) {
if (missing_id)
/* placeholder if we are dealing with a mixed-type container*/
trace_seq_printf(s, " ");
@@ -490,24 +481,24 @@ ikm_error_handler(struct trace_seq *s, struct tep_record *record,
int val;
bool missing_id;
- if (config_has_id)
+ if (config.has_id)
missing_id = tep_get_field_val(s, trace_event, "id", record, &id, 1);
tep_get_common_field_val(s, trace_event, "common_pid", record, &pid, 1);
- if (config_has_id && config_my_pid == id)
+ if (config.has_id && config.my_pid == id)
return 0;
- else if (config_my_pid == pid)
+ else if (config.my_pid == pid)
return 0;
trace_seq_printf(s, "%8lld [%03d] ", pid, cpu);
- if (config_is_container)
+ if (config.is_container)
tep_print_event(trace_event->tep, s, record, "%s ", TEP_PRINT_NAME);
else
trace_seq_printf(s, "error ");
- if (config_has_id) {
+ if (config.has_id) {
if (missing_id)
/* placeholder if we are dealing with a mixed-type container*/
trace_seq_printf(s, " ");
@@ -548,8 +539,8 @@ static int ikm_enable_trace_events(char *monitor_name, struct trace_instance *in
ikm_error_handler, NULL);
/* set if at least 1 monitor has id in case of a container */
- config_has_id = ikm_has_id(monitor_name);
- if (config_has_id < 0)
+ config.has_id = ikm_has_id(monitor_name);
+ if (config.has_id < 0)
return -1;
@@ -596,7 +587,7 @@ static struct trace_instance *ikm_setup_trace_instance(char *monitor_name)
struct trace_instance *inst;
int retval;
- if (!config_trace)
+ if (!config.trace)
return NULL;
/* alloc data */
@@ -610,7 +601,7 @@ static struct trace_instance *ikm_setup_trace_instance(char *monitor_name)
if (retval)
goto out_free;
- if (config_is_container)
+ if (config.is_container)
retval = ikm_enable_trace_container(monitor_name, inst);
else
retval = ikm_enable_trace_events(monitor_name, inst);
@@ -645,7 +636,7 @@ static void ikm_destroy_trace_instance(struct trace_instance *inst)
/*
* ikm_usage_print_reactors - print all available reactors, one per line.
*/
-static void ikm_usage_print_reactors(void)
+void ikm_usage_print_reactors(void)
{
char *reactors = tracefs_instance_file_read(NULL, "rv/available_reactors", NULL);
char *start, *end;
@@ -669,106 +660,6 @@ static void ikm_usage_print_reactors(void)
fprintf(stderr, "\n");
}
-/*
- * ikm_usage - print usage
- */
-static void ikm_usage(int exit_val, char *monitor_name, const char *fmt, ...)
-{
-
- char message[1024];
- va_list ap;
- int i;
-
- static const char *const usage[] = {
- "",
- " -h/--help: print this menu and the reactor list",
- " -r/--reactor 'reactor': enables the 'reactor'",
- " -s/--self: when tracing (-t), also trace rv command",
- " -t/--trace: trace monitor's event",
- " -v/--verbose: print debug messages",
- "",
- NULL,
- };
-
- va_start(ap, fmt);
- vsnprintf(message, sizeof(message), fmt, ap);
- va_end(ap);
-
- fprintf(stderr, " %s\n", message);
-
- fprintf(stderr, "\n usage: rv mon %s [-h] [-q] [-r reactor] [-s] [-v]", monitor_name);
-
- for (i = 0; usage[i]; i++)
- fprintf(stderr, "%s\n", usage[i]);
-
- ikm_usage_print_reactors();
- exit(exit_val);
-}
-
-/*
- * parse_arguments - parse arguments and set config
- */
-static int parse_arguments(char *monitor_name, int argc, char **argv)
-{
- int c, retval;
-
- config_my_pid = getpid();
-
- while (1) {
- static struct option long_options[] = {
- {"help", no_argument, 0, 'h'},
- {"reactor", required_argument, 0, 'r'},
- {"self", no_argument, 0, 's'},
- {"trace", no_argument, 0, 't'},
- {"verbose", no_argument, 0, 'v'},
- {0, 0, 0, 0}
- };
-
- /* getopt_long stores the option index here. */
- int option_index = 0;
-
- c = getopt_long(argc, argv, "hr:stv", long_options, &option_index);
-
- /* detect the end of the options. */
- if (c == -1)
- break;
-
- switch (c) {
- case 'h':
- ikm_usage(0, monitor_name, "help:");
- break;
- case 'r':
- config_reactor = optarg;
- break;
- case 's':
- config_my_pid = -1;
- break;
- case 't':
- config_trace = 1;
- break;
- case 'v':
- config_debug = 1;
- break;
- }
- }
-
- if (config_reactor) {
- config_initial_reactor = ikm_get_current_reactor(monitor_name);
- if (!config_initial_reactor)
- ikm_usage(1, monitor_name,
- "ikm: failed to read current reactor, are reactors enabled?");
-
- retval = ikm_write_reactor(monitor_name, config_reactor);
- if (retval <= 0)
- ikm_usage(1, monitor_name,
- "ikm: failed to set %s reactor, is it available?",
- config_reactor);
- }
-
- debug_msg("ikm: my pid is %d\n", config_my_pid);
-
- return 0;
-}
/**
* ikm_run_monitor - apply configs and run the monitor
@@ -805,9 +696,22 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
/* we should be good to go */
retval = parse_arguments(full_name, argc, argv);
if (retval)
- ikm_usage(1, nested_name, "ikm: failed parsing arguments");
+ mon_usage(1, nested_name, "ikm: failed parsing arguments");
+
+ if (config.reactor) {
+ config.initial_reactor = ikm_get_current_reactor(full_name);
+ if (!config.initial_reactor)
+ mon_usage(1, full_name,
+ "ikm: failed to read current reactor, are reactors enabled?");
+
+ retval = ikm_write_reactor(full_name, config.reactor);
+ if (retval <= 0)
+ mon_usage(1, full_name,
+ "ikm: failed to set %s reactor, is it available?",
+ config.reactor);
+ }
- if (config_trace) {
+ if (config.trace) {
inst = ikm_setup_trace_instance(nested_name);
if (!inst)
goto out_free_instance;
@@ -817,11 +721,11 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
if (retval < 0)
goto out_free_instance;
- if (config_trace)
+ if (config.trace)
ikm_print_header(inst->seq);
while (!should_stop()) {
- if (config_trace) {
+ if (config.trace) {
retval = tracefs_iterate_raw_events(inst->tep,
inst->inst,
NULL,
@@ -840,14 +744,14 @@ int ikm_run_monitor(char *monitor_name, int argc, char **argv)
ikm_disable(full_name);
ikm_destroy_trace_instance(inst);
- if (config_reactor && config_initial_reactor)
- ikm_write_reactor(full_name, config_initial_reactor);
+ if (config.reactor && config.initial_reactor)
+ ikm_write_reactor(full_name, config.initial_reactor);
return 1;
out_free_instance:
ikm_destroy_trace_instance(inst);
- if (config_reactor && config_initial_reactor)
- ikm_write_reactor(full_name, config_initial_reactor);
+ if (config.reactor && config.initial_reactor)
+ ikm_write_reactor(full_name, config.initial_reactor);
return -1;
}
diff --git a/tools/verification/rv/src/utils.c b/tools/verification/rv/src/utils.c
index 5677b439dc2f..e19dd65ec789 100644
--- a/tools/verification/rv/src/utils.c
+++ b/tools/verification/rv/src/utils.c
@@ -5,11 +5,14 @@
* Copyright (C) 2022 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
+#include <getopt.h>
#include <stdarg.h>
+#include <stdlib.h>
#include <stdio.h>
+#include <unistd.h>
#include <utils.h>
-int config_debug;
+struct config config;
#define MAX_MSG_LENGTH 1024
@@ -36,7 +39,7 @@ void debug_msg(const char *fmt, ...)
char message[MAX_MSG_LENGTH];
va_list ap;
- if (!config_debug)
+ if (!config.debug)
return;
va_start(ap, fmt);
@@ -45,3 +48,91 @@ void debug_msg(const char *fmt, ...)
fprintf(stderr, "%s", message);
}
+
+/*
+ * mon_usage - print usage
+ */
+void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...)
+{
+
+ char message[1024];
+ va_list ap;
+ int i;
+
+ static const char *const usage[] = {
+ "",
+ " -h/--help: print this menu and the reactor list",
+ " -r/--reactor 'reactor': enables the 'reactor'",
+ " -s/--self: when tracing (-t), also trace rv command",
+ " -t/--trace: trace monitor's event",
+ " -v/--verbose: print debug messages",
+ "",
+ NULL,
+ };
+
+ va_start(ap, fmt);
+ vsnprintf(message, sizeof(message), fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, " %s\n", message);
+
+ fprintf(stderr, "\n usage: rv mon %s [-h] [-q] [-r reactor] [-s] [-v]", monitor_name);
+
+ for (i = 0; usage[i]; i++)
+ fprintf(stderr, "%s\n", usage[i]);
+
+ ikm_usage_print_reactors();
+ exit(exit_val);
+}
+
+/*
+ * parse_arguments - parse arguments and set config
+ */
+int parse_arguments(char *monitor_name, int argc, char **argv)
+{
+ int c;
+
+ config.my_pid = getpid();
+
+ while (1) {
+ static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"reactor", required_argument, 0, 'r'},
+ {"self", no_argument, 0, 's'},
+ {"trace", no_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {0, 0, 0, 0}
+ };
+
+ /* getopt_long stores the option index here. */
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "hr:stv", long_options, &option_index);
+
+ /* detect the end of the options. */
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ mon_usage(0, monitor_name, "help:");
+ break;
+ case 'r':
+ config.reactor = optarg;
+ break;
+ case 's':
+ config.my_pid = -1;
+ break;
+ case 't':
+ config.trace = 1;
+ break;
+ case 'v':
+ config.debug = 1;
+ break;
+ }
+ }
+
+ debug_msg("ikm: my pid is %d\n", config.my_pid);
+
+ return 0;
+}
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils
2026-10-01 15:20 ` [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
@ 2026-10-02 0:25 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:25 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1770 bytes --]
> tools/rv: Move argument parsing from in_kernel to utils
TL;DR: Nit only: the shared parse_arguments() in utils.c still prints an
"ikm:" prefix in its debug message, which would mislabel -v output for BPF
monitors later in the series. Cosmetic, no behaviour change.
> diff --git a/tools/verification/rv/src/utils.c b/tools/verification/rv/src/utils.c
> index 5677b439dc2f..e19dd65ec789 100644
> --- a/tools/verification/rv/src/utils.c
> +++ b/tools/verification/rv/src/utils.c
[ ... ]
> @@ -45,3 +48,91 @@ void debug_msg(const char *fmt, ...)
[ ... ]
> + }
> + }
> +
> + debug_msg("ikm: my pid is %d\n", config.my_pid);
Is the "ikm: " prefix still appropriate in this debug message now that
parse_arguments() lives in the shared utils.c?
At this commit only ikm_run_monitor() calls parse_arguments(), so the output
is correct. Later in the series, 46210ac84ea4 ("tools/rv: Implement BPF
monitor loading and tracing") adds bpf_run_monitor(), which sets
config.is_bpf = true and then calls parse_arguments(monitor_name, argc,
argv).
With that, running "rv mon <bpf_monitor> -v" prints "ikm: my pid is N",
even though bpf_run_monitor() labels its own messages with "bpf:" (for
example "bpf: failed parsing arguments"). The message is still unchanged
at the series tip, 3b00bc9b2e54.
Would dropping the "ikm: " prefix from the shared helper work? The selftest
in tests/rv_mon.t only matches "my pid is $pid", so it should keep passing.
This is cosmetic, it only mislabels the -v output and does not change
behaviour.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (4 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 05/15] tools/rv: Move argument parsing from in_kernel to utils Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing Gabriele Monaco
` (8 subsequent siblings)
14 siblings, 0 replies; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf
Cc: Gabriele Monaco, Steven Rostedt, Alexei Starovoitov, Nam Cao,
Wen Yang, Tobias Schaffner, Viktor Malik
RV can run bpf monitors and also build them, only the latter requires
the vmlinux.h from bpftool btf.
Add a feature so that RV can be built with BPF support without having to
build in-tree BPF monitors if not supported on the system.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/build/Makefile.feature | 1 +
tools/build/feature/Makefile | 7 ++++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature
index 99eb0ea09537..18e3a7ae893a 100644
--- a/tools/build/Makefile.feature
+++ b/tools/build/Makefile.feature
@@ -129,6 +129,7 @@ FEATURE_TESTS_EXTRA := \
libdebuginfod \
clang-bpf-co-re \
bpftool-skeletons \
+ bpftool-btf \
libunwind \
libunwind-debug-frame \
$(foreach arch,$(LIBUNWIND_ARCHS),libunwind-$(arch) libunwind-debug-frame-$(arch))
diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile
index 7d165018116a..2f01203dda5c 100644
--- a/tools/build/feature/Makefile
+++ b/tools/build/feature/Makefile
@@ -71,7 +71,8 @@ FILES= \
test-libpfm4.bin \
test-rust.bin \
test-libopenssl.bin \
- test-bpftool-skeletons.bin
+ test-bpftool-skeletons.bin \
+ test-bpftool-btf.bin
FILES := $(addprefix $(OUTPUT),$(FILES))
@@ -383,6 +384,10 @@ $(OUTPUT)test-bpftool-skeletons.bin:
{ $(SYSTEM_BPFTOOL) version | grep '^features:.*skeletons'; } \
> $(@:.bin=.make.output) 2>&1 && touch $@
+$(OUTPUT)test-bpftool-btf.bin:
+ $(SYSTEM_BPFTOOL) btf help \
+ > $(@:.bin=.make.output) 2>&1 && touch $@
+
# Testing Rust is special: we don't compile anything, it's enough to check the
# compiler presence. Compiling a test code for this purposes is problematic,
# because Rust will emit a dependency file without any external references,
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (5 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 06/15] tools/build: Add a feature test for bpftool-btf Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:42 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
` (7 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Implement the BPF monitor listing functionality that scans for compiled
BPF monitor object files and displays them in the monitor list.
If enabled, running rv list shows in-kernel and BPF monitors found in
/usr/share/rv/bpf_monitors/ or in ./bpf_monitors/ relative to the RV
binary's location (used during development).
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/Makefile | 4 +-
tools/verification/rv/Makefile.config | 23 ++
tools/verification/rv/include/bpf_monitor.h | 14 ++
tools/verification/rv/src/Build | 5 +
tools/verification/rv/src/bpf_monitor.c | 223 ++++++++++++++++++++
tools/verification/rv/src/rv.c | 2 +
6 files changed, 270 insertions(+), 1 deletion(-)
create mode 100644 tools/verification/rv/include/bpf_monitor.h
create mode 100644 tools/verification/rv/src/bpf_monitor.c
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 8ae5fc0d1d17..0a8c7a656f7f 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -32,8 +32,10 @@ DOCSRC := ../../../Documentation/tools/rv/
FEATURE_TESTS := libtraceevent
FEATURE_TESTS += libtracefs
+FEATURE_TESTS += libbpf
FEATURE_DISPLAY := libtraceevent
FEATURE_DISPLAY += libtracefs
+FEATURE_DISPLAY += libbpf
all: $(RV)
@@ -57,7 +59,7 @@ endif
CFLAGS += $(INCLUDES) $(LIB_INCLUDES)
-export CFLAGS OUTPUT srctree
+export CFLAGS OUTPUT srctree BUILD_BPF
$(RV): $(RV_IN)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV) $(RV_IN) $(EXTLIBS)
diff --git a/tools/verification/rv/Makefile.config b/tools/verification/rv/Makefile.config
index 066302230eb2..0600170ac217 100644
--- a/tools/verification/rv/Makefile.config
+++ b/tools/verification/rv/Makefile.config
@@ -43,6 +43,29 @@ else
$(info libtracefs is missing. Please install libtracefs-dev/libtracefs-devel)
endif
+ifndef BUILD_BPF
+ # BPF monitors are optional but enabled by default
+ BUILD_BPF := 1
+endif
+
+ifeq ($(BUILD_BPF),0)
+ $(info BPF monitor support disabled, building without BPF monitor support.)
+endif
+
+$(call feature_check,libbpf)
+ifeq ($(feature-libbpf), 1)
+ $(call detected,CONFIG_LIBBPF)
+else
+ $(info libbpf is missing, building without BPF monitor support.)
+ $(info Please install libbpf-dev/libbpf-devel)
+ BUILD_BPF := 0
+endif
+
+ifeq ($(BUILD_BPF),1)
+ CFLAGS += -DHAVE_LIBBPF
+ $(call lib_setup,bpf)
+endif
+
ifeq ($(STOP_ERROR),1)
$(error Please, check the errors above.)
endif
diff --git a/tools/verification/rv/include/bpf_monitor.h b/tools/verification/rv/include/bpf_monitor.h
new file mode 100644
index 000000000000..e58bc45c5f75
--- /dev/null
+++ b/tools/verification/rv/include/bpf_monitor.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef _BPF_MONITOR_H
+#define _BPF_MONITOR_H
+
+#ifdef HAVE_LIBBPF
+int bpf_list_monitors(char *container);
+#else
+static inline int bpf_list_monitors(char *container)
+{
+ return 0;
+}
+#endif /* HAVE_LIBBPF */
+
+#endif
diff --git a/tools/verification/rv/src/Build b/tools/verification/rv/src/Build
index d781983c1a79..326503c22915 100644
--- a/tools/verification/rv/src/Build
+++ b/tools/verification/rv/src/Build
@@ -1,4 +1,9 @@
rv-y += trace.o
rv-y += utils.o
rv-y += in_kernel.o
+
+ifeq ($(BUILD_BPF),1)
+ rv-y += bpf_monitor.o
+endif
+
rv-y += rv.o
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
new file mode 100644
index 000000000000..a1de0c157712
--- /dev/null
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BPF monitor support: allows rv to control BPF monitors.
+ *
+ * Copyright (C) 2026 Red Hat Inc, Gabriele Monaco <gmonaco@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <errno.h>
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include <bpf/btf.h>
+
+#include <bpf_monitor.h>
+#include <utils.h>
+#include <rv.h>
+
+static char bpf_base_paths[][MAX_PATH] = {
+ ".",
+ "/etc/rv",
+ "/usr/local/share/rv",
+ "/usr/share/rv",
+ "", /* Marker */
+};
+
+/* Path used for development, searched first */
+#define DEVEL_PATH 0
+
+
+/*
+ * bpf_read_enable - reads monitor's enable status
+ *
+ * Iterate through all BPF maps in the system, if the rv_mon_NAME map is
+ * loaded, the monitor is enabled.
+ * Since map names have limited size and may get truncated, check that also the
+ * corresponding BTF matches.
+ */
+static int bpf_read_enable(const char *monitor_name)
+{
+ char ringbuf_name[2 * MAX_DA_NAME_LEN];
+ uint32_t id = 0;
+
+ snprintf(ringbuf_name, sizeof(ringbuf_name),
+ "rv_mon_%s", monitor_name);
+
+ while (bpf_map_get_next_id(id, &id) == 0) {
+ struct bpf_map_info info = { 0 };
+ uint32_t info_len = sizeof(info);
+ struct btf *btf;
+ int type_id;
+ int fd = bpf_map_get_fd_by_id(id);
+
+ if (fd < 0)
+ continue;
+
+ if (bpf_map_get_info_by_fd(fd, &info, &info_len) != 0) {
+ close(fd);
+ continue;
+ }
+ close(fd);
+
+ if (strncmp(info.name, ringbuf_name, BPF_OBJ_NAME_LEN - 1) != 0)
+ continue;
+
+ if (!info.btf_id)
+ continue;
+ btf = btf__load_from_kernel_by_id(info.btf_id);
+ if (!btf)
+ continue;
+
+ type_id = btf__find_by_name_kind(btf, ringbuf_name, BTF_KIND_VAR);
+ btf__free(btf);
+ if (type_id > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * bpf_read_desc - read monitors' description
+ *
+ * Return the provided string containing the monitor's description, NULL
+ * otherwise.
+ */
+static char *bpf_read_desc(char *desc, struct bpf_object *obj, const char *monitor_name)
+{
+ struct bpf_map *map = bpf_object__find_map_by_name(obj, ".rodata.description");
+ const char *desc_data;
+ size_t desc_size;
+
+ if (!map) {
+ debug_msg("bpf: cannot find description for %s\n",
+ monitor_name);
+ return NULL;
+ }
+ desc_data = bpf_map__initial_value(map, &desc_size);
+ if (!desc_data || desc_size == 0) {
+ debug_msg("bpf: empty description for %s\n", monitor_name);
+ *desc = 0;
+ return desc;
+ }
+
+ if (desc_size >= MAX_DESCRIPTION)
+ desc_size = MAX_DESCRIPTION - 1;
+ strncpy(desc, desc_data, desc_size);
+ desc[desc_size] = '\0';
+
+ return desc;
+}
+
+/*
+ * bpf_fill_base_paths - fill the path for development builds
+ *
+ * RV searches for BPF monitors on absolute paths on the system as well
+ * as in the same directory of the rv binary. This is useful when running
+ * rv from the kernel tree. This function resolves the right location.
+ */
+static void bpf_fill_base_paths(void)
+{
+ char tmp_path[MAX_PATH], *dir;
+ ssize_t len;
+
+ len = readlink("/proc/self/exe", tmp_path, MAX_PATH);
+ if (len > 0 && len != MAX_PATH) {
+ tmp_path[len] = '\0';
+ dir = dirname(tmp_path);
+ snprintf(bpf_base_paths[DEVEL_PATH], MAX_PATH, "%s", dir);
+ }
+}
+
+static void bpf_object_iterate_path(const char *base_path, const char *subdir,
+ void (*action)(const char *name, struct bpf_object *obj))
+{
+ char path[MAX_PATH];
+ struct dirent *entry;
+ DIR *dir;
+ char *ext;
+
+ snprintf(path, sizeof(path), "%s/%s", base_path, subdir);
+ dir = opendir(path);
+ if (!dir) {
+ debug_msg("bpf: error opening directory: %s\n", path);
+ return;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+ size_t size;
+ struct bpf_object *obj;
+ char name[MAX_DA_NAME_LEN], obj_path[MAX_PATH];
+
+ if (entry->d_name[0] == '.')
+ continue;
+
+ ext = strrchr(entry->d_name, '.');
+ if (!ext || strcmp(ext, ".o") != 0)
+ continue;
+
+ size = snprintf(obj_path, sizeof(obj_path), "%s/%s", path,
+ entry->d_name);
+ obj = bpf_object__open_file(obj_path, NULL);
+ if (!obj || size > MAX_PATH) {
+ err_msg("bpf: error opening object file %s: %s\n",
+ obj_path, strerror(errno));
+ continue;
+ }
+
+ strncpy(name, entry->d_name, sizeof(name));
+ ext = strrchr(name, '.');
+ if (ext)
+ *ext = '\0';
+
+ action(name, obj);
+
+ bpf_object__close(obj);
+ }
+
+ closedir(dir);
+}
+
+static void list_monitor_action(const char *name, struct bpf_object *obj)
+{
+ char desc[MAX_DESCRIPTION];
+
+ if (!bpf_read_desc(desc, obj, name)) {
+ err_msg("bpf: monitor %s does not have desc map, bug?\n", name);
+ return;
+ }
+
+ printf("%-*s %s %s\n", MAX_DA_NAME_LEN, name,
+ desc, bpf_read_enable(name) ? "[ON]" : "[OFF]");
+}
+
+/*
+ * list_monitors_from_path - list monitors from a specific base path
+ */
+static void list_monitors_from_path(const char *base_path)
+{
+ bpf_object_iterate_path(base_path, "bpf_monitors", list_monitor_action);
+}
+
+/*
+ * bpf_list_monitors - list available BPF monitors from all sources
+ *
+ * @container: BPF monitors are not nested, skip listing.
+ *
+ * Returns 0 on success
+ */
+int bpf_list_monitors(char *container)
+{
+ if (container)
+ return 0;
+ bpf_fill_base_paths();
+ for (int i = 0; bpf_base_paths[i][0]; i++)
+ list_monitors_from_path(bpf_base_paths[i]);
+
+ return 0;
+}
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index 09e0d8598619..7c4a2e49ff9d 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -13,6 +13,7 @@
#include <trace.h>
#include <utils.h>
#include <in_kernel.h>
+#include <bpf_monitor.h>
static int stop_session;
@@ -76,6 +77,7 @@ static void rv_list(int argc, char **argv)
}
ikm_list_monitors(container);
+ bpf_list_monitors(container);
exit(EXIT_SUCCESS);
}
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing
2026-10-01 15:20 ` [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing Gabriele Monaco
@ 2026-10-02 0:42 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:42 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3746 bytes --]
> tools/rv: Implement BPF monitor discovery and listing
TL;DR:
- "make static" may now fail to link libbpf (missing -lelf/-lz) now that BPF
is on by default
- Changelog lists fewer search paths than the code scans, including a cwd
fallback
> tools/rv: Implement BPF monitor discovery and listing
>
> Implement the BPF monitor listing functionality that scans for compiled
> BPF monitor object files and displays them in the monitor list.
>
> If enabled, running rv list shows in-kernel and BPF monitors found in
> /usr/share/rv/bpf_monitors/ or in ./bpf_monitors/ relative to the RV
> binary's location (used during development).
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
The commit message names /usr/share/rv/bpf_monitors/ and ./bpf_monitors/
relative to the rv binary. Is that the complete search list?
bpf_list_monitors() scans every entry of bpf_base_paths[] with /bpf_monitors
appended:
static char bpf_base_paths[][MAX_PATH] = {
".",
"/etc/rv",
"/usr/local/share/rv",
"/usr/share/rv",
"", /* Marker */
};
so /etc/rv/bpf_monitors and /usr/local/share/rv/bpf_monitors are searched
as well.
Entry 0 is replaced with the binary's directory only when
readlink("/proc/self/exe") succeeds in bpf_fill_base_paths(). Otherwise it
stays ".", and the current working directory's bpf_monitors is scanned
instead.
All of the directories are scanned, not one or the other, so a monitor
present in more than one of them is listed more than once.
Should the changelog describe the actual search list and the working
directory fallback, or should the list be trimmed to what is documented?
Is /etc/rv an appropriate place to look for BPF object files?
> diff --git a/tools/verification/rv/Makefile.config b/tools/verification/rv/Makefile.config
> index 066302230eb2..0600170ac217 100644
> --- a/tools/verification/rv/Makefile.config
> +++ b/tools/verification/rv/Makefile.config
[ ... ]
> @@ -43,6 +43,29 @@ else
> $(info libtracefs is missing. Please install libtracefs-dev/libtracefs-devel)
> endif
>
> +ifndef BUILD_BPF
> + # BPF monitors are optional but enabled by default
> + BUILD_BPF := 1
> +endif
> +
> +ifeq ($(BUILD_BPF),0)
> + $(info BPF monitor support disabled, building without BPF monitor support.)
> +endif
> +
> +$(call feature_check,libbpf)
> +ifeq ($(feature-libbpf), 1)
> + $(call detected,CONFIG_LIBBPF)
> +else
> + $(info libbpf is missing, building without BPF monitor support.)
> + $(info Please install libbpf-dev/libbpf-devel)
> + BUILD_BPF := 0
> +endif
> +
> +ifeq ($(BUILD_BPF),1)
> + CFLAGS += -DHAVE_LIBBPF
> + $(call lib_setup,bpf)
> +endif
> +
Can this break "make static" on systems where it worked before this patch?
BPF support is now on by default whenever the libbpf feature test passes,
and lib_setup only adds the output of pkg-config --libs-only-l libbpf, which
is just -lbpf. libbpf.pc declares:
Requires.private: libelf zlib
and pkg-config only reports those with --static. The existing static target
in tools/verification/rv/Makefile links with -static and $(EXTLIBS):
static: $(RV_IN)
$(eval LDFLAGS += -static)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
Would that link libbpf.a without -lelf and -lz and fail with unresolved
libelf and zlib symbols, so that users now need BUILD_BPF=0 to get a static
build?
Could pkg-config be called with --static for the static target, the private
dependencies be added, or BPF support be disabled for static builds?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (6 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 07/15] tools/rv: Implement BPF monitor discovery and listing Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:43 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
` (6 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Implement BPF monitor loading, execution, and optional tracing
functionality.
Running rv mon MON loads the MON monitor into the kernel and passing the
-t parameter also attaches to the event ring buffer printing events and
errors to standard output.
Event and state names as well as the ring buffer entry format are
dynamically parsed from BTF.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/include/bpf_monitor.h | 8 +
tools/verification/rv/include/utils.h | 2 +
tools/verification/rv/src/bpf_monitor.c | 650 ++++++++++++++++++++
tools/verification/rv/src/rv.c | 3 +
tools/verification/rv/src/utils.c | 5 +-
5 files changed, 667 insertions(+), 1 deletion(-)
diff --git a/tools/verification/rv/include/bpf_monitor.h b/tools/verification/rv/include/bpf_monitor.h
index e58bc45c5f75..55b2f05666a3 100644
--- a/tools/verification/rv/include/bpf_monitor.h
+++ b/tools/verification/rv/include/bpf_monitor.h
@@ -4,11 +4,19 @@
#ifdef HAVE_LIBBPF
int bpf_list_monitors(char *container);
+int bpf_run_monitor(char *monitor_name, int argc, char **argv);
#else
static inline int bpf_list_monitors(char *container)
{
return 0;
}
+
+static inline int bpf_run_monitor(char *monitor_name, int argc, char **argv)
+{
+ return 0;
+}
+
+void bpf_usage_print_reactors(void) { }
#endif /* HAVE_LIBBPF */
#endif
diff --git a/tools/verification/rv/include/utils.h b/tools/verification/rv/include/utils.h
index 61f77a72a1a2..d6b4e60df7ff 100644
--- a/tools/verification/rv/include/utils.h
+++ b/tools/verification/rv/include/utils.h
@@ -10,11 +10,13 @@ void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...);
int parse_arguments(char *monitor_name, int argc, char **argv);
void ikm_usage_print_reactors(void);
+void bpf_usage_print_reactors(void);
struct config {
bool debug;
bool is_container;
bool trace;
+ bool is_bpf;
int has_id;
int my_pid;
char *initial_reactor;
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index a1de0c157712..9a3f00febfeb 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -8,10 +8,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <libgen.h>
#include <errno.h>
+#include <inttypes.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <bpf/btf.h>
@@ -31,6 +33,62 @@ static char bpf_base_paths[][MAX_PATH] = {
/* Path used for development, searched first */
#define DEVEL_PATH 0
+#define MAX_ENUMS 64
+#define MAX_LINKS 16
+#define PROG_ENABLE_MON "enable_monitor"
+#define RV_TRACE_STRUCT "rv_trace_entry"
+#define RV_TRACE_TYPE_ENUM "rv_trace_type"
+
+enum trace_type_id {
+ TRACE_TYPE_ERROR,
+ TRACE_TYPE_EVENT,
+ TRACE_TYPE_MAX,
+};
+
+static const char *const trace_type_names[] = {
+ [TRACE_TYPE_ERROR] = "RV_TRACE_ERROR",
+ [TRACE_TYPE_EVENT] = "RV_TRACE_EVENT",
+};
+
+enum field_id {
+ FIELD_EVENT_TYPE,
+ FIELD_ID,
+ FIELD_CPU,
+ FIELD_PID,
+ FIELD_COMM,
+ FIELD_IS_FINAL,
+ FIELD_CURR_STATE,
+ FIELD_EVENT,
+ FIELD_NEXT_STATE,
+ FIELD_MAX,
+};
+
+static const char *const field_names[] = {
+ [FIELD_EVENT_TYPE] = "event_type",
+ [FIELD_ID] = "id",
+ [FIELD_CPU] = "cpu",
+ [FIELD_PID] = "pid",
+ [FIELD_COMM] = "comm",
+ [FIELD_IS_FINAL] = "is_final",
+ [FIELD_CURR_STATE] = "curr_state",
+ [FIELD_EVENT] = "event",
+ [FIELD_NEXT_STATE] = "next_state",
+};
+
+struct field {
+ size_t offset;
+ size_t size;
+};
+
+struct bpf_monitor_ctx {
+ char monitor_name[MAX_DA_NAME_LEN];
+ char state_names[MAX_ENUMS][MAX_DA_NAME_LEN];
+ char event_names[MAX_ENUMS][MAX_DA_NAME_LEN];
+ int num_states;
+ int num_events;
+ struct field field_metadata[FIELD_MAX];
+ int trace_types[TRACE_TYPE_MAX];
+};
/*
* bpf_read_enable - reads monitor's enable status
@@ -221,3 +279,595 @@ int bpf_list_monitors(char *container)
return 0;
}
+
+static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
+ va_list args)
+{
+ if (level == LIBBPF_DEBUG && !config.debug)
+ return 0;
+ return vfprintf(stderr, format, args);
+}
+
+/*
+ * Helper functions for state/event name lookup
+ */
+static const char *get_state_name(struct bpf_monitor_ctx *ctx, uint32_t state)
+{
+ if (state < ctx->num_states)
+ return ctx->state_names[state];
+
+ return "<invalid>";
+}
+
+static const char *get_event_name(struct bpf_monitor_ctx *ctx, uint32_t event)
+{
+ if (event < ctx->num_events)
+ return ctx->event_names[event];
+
+ return "<invalid>";
+}
+
+/*
+ * extract_field_metadata - extract field metadata from BTF for efficient parsing
+ *
+ * Introspect the rv_trace_entry structure via BTF and store field offsets and
+ * sizes for direct memory access during event processing.
+ * This allows for parsing known fields without having to stick to a particular
+ * memory layout in future releases.
+ *
+ * Returns 0 on success, -1 on error
+ */
+static int extract_field_metadata(const struct btf *btf, struct bpf_monitor_ctx *ctx)
+{
+ const struct btf_type *trace_type;
+ const struct btf_member *members;
+ int type_id, vlen;
+
+ type_id = btf__find_by_name_kind(btf, RV_TRACE_STRUCT, BTF_KIND_STRUCT);
+ if (type_id <= 0) {
+ debug_msg("bpf: could not find struct '%s' in BTF\n", RV_TRACE_STRUCT);
+ return -1;
+ }
+
+ trace_type = btf__type_by_id(btf, type_id);
+ if (!trace_type) {
+ debug_msg("bpf: could not get type for '%s'\n", RV_TRACE_STRUCT);
+ return -1;
+ }
+
+ members = btf_members(trace_type);
+ vlen = btf_vlen(trace_type);
+
+ for (int i = 0; i < vlen; i++) {
+ const char *name = btf__name_by_offset(btf, members[i].name_off);
+ size_t offset = btf_member_bit_offset(trace_type, i) / 8;
+ size_t size = btf__resolve_size(btf, members[i].type);
+
+ if (!name || (ssize_t)size < 0)
+ continue;
+
+ debug_msg("bpf: field '%s' at offset %zu, size %lld\n", name,
+ offset, (long long)size);
+
+ for (int j = 0; j < FIELD_MAX; j++) {
+ if (strcmp(name, field_names[j]) == 0) {
+ ctx->field_metadata[j].offset = offset;
+ ctx->field_metadata[j].size = size;
+ if (j == FIELD_ID)
+ config.has_id = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * extract_trace_type_metadata - extract trace type enum values from BTF
+ *
+ * Introspect the rv_trace_type enum via BTF and store enum values for
+ * event type dispatch and configuration.
+ *
+ * Returns 0 on success, -1 on error
+ */
+static int extract_trace_type_metadata(const struct btf *btf, struct bpf_monitor_ctx *ctx)
+{
+ const struct btf_type *enum_type;
+ const struct btf_enum *enums;
+ int type_id, vlen, filled = 0;
+
+ for (int i = 0; i < TRACE_TYPE_MAX; i++)
+ ctx->trace_types[i] = -1;
+
+ type_id = btf__find_by_name_kind(btf, RV_TRACE_TYPE_ENUM, BTF_KIND_ENUM);
+ if (type_id <= 0) {
+ debug_msg("bpf: could not find enum '%s' in BTF\n", RV_TRACE_TYPE_ENUM);
+ return -1;
+ }
+
+ enum_type = btf__type_by_id(btf, type_id);
+ if (!enum_type) {
+ debug_msg("bpf: could not get type for '%s'\n", RV_TRACE_TYPE_ENUM);
+ return -1;
+ }
+
+ enums = btf_enum(enum_type);
+ vlen = btf_vlen(enum_type);
+
+ for (int i = 0; i < vlen; i++) {
+ const char *name = btf__name_by_offset(btf, enums[i].name_off);
+
+ if (!name)
+ continue;
+
+ for (int j = 0; j < TRACE_TYPE_MAX; j++) {
+ if (strcmp(name, trace_type_names[j]) == 0) {
+ ctx->trace_types[j] = enums[i].val;
+ ++filled;
+ break;
+ }
+ }
+ }
+
+ if (filled < TRACE_TYPE_MAX) {
+ debug_msg("bpf: could not find all trace types in BTF\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * bpf_print_header - print trace output header
+ */
+static void bpf_print_header(void)
+{
+ printf("%16s-%-8s %5s %5s ", "<TASK>", "PID", "[CPU]", "TYPE");
+ if (config.has_id)
+ printf(" %8s", "ID");
+
+ printf("%24s x %-24s -> %-24s %s\n",
+ "STATE",
+ "EVENT",
+ "NEXT_STATE",
+ "FINAL");
+
+ printf("%16s %-8s %5s %5s ", " | ", " | ", " | ", " | ");
+
+ if (config.has_id)
+ printf(" %8s", " | ");
+ printf("%24s %-24s %-24s %s\n", " | ", " | ", " | ", "|");
+}
+
+static inline uint64_t read_field(uint64_t *entry, enum field_id id,
+ const uint8_t *raw,
+ const struct bpf_monitor_ctx *ctx)
+{
+ const struct field *field = &ctx->field_metadata[id];
+
+ switch (field->size) {
+ case 1:
+ return entry[id] = *(const uint8_t *)(raw + field->offset);
+ case 2:
+ return entry[id] = *(const uint16_t *)(raw + field->offset);
+ case 4:
+ return entry[id] = *(const uint32_t *)(raw + field->offset);
+ case 8:
+ return entry[id] = *(const uint64_t *)(raw + field->offset);
+ }
+ return 0;
+}
+
+/*
+ * handle_event - ring buffer callback for trace events
+ */
+static int handle_event(void *ctx, void *data, size_t data_sz)
+{
+ struct bpf_monitor_ctx *mon_ctx = ctx;
+ const uint8_t *raw = data;
+ uint64_t entry[FIELD_MAX] = {0};
+ const char *comm;
+
+ if (should_stop())
+ return 1;
+
+ if (config.has_id)
+ read_field(entry, FIELD_ID, raw, mon_ctx);
+ read_field(entry, FIELD_PID, raw, mon_ctx);
+
+ if (config.has_id && (config.my_pid == entry[FIELD_ID]))
+ return 0;
+ else if (config.my_pid == entry[FIELD_PID])
+ return 0;
+
+ read_field(entry, FIELD_EVENT_TYPE, raw, mon_ctx);
+ read_field(entry, FIELD_CPU, raw, mon_ctx);
+ comm = (const char *)(raw + mon_ctx->field_metadata[FIELD_COMM].offset);
+ read_field(entry, FIELD_CURR_STATE, raw, mon_ctx);
+ read_field(entry, FIELD_EVENT, raw, mon_ctx);
+
+ printf("%16s-%-8"PRIu64" [%.3"PRIu64"] ", comm, entry[FIELD_PID], entry[FIELD_CPU]);
+ if (entry[FIELD_EVENT_TYPE] == mon_ctx->trace_types[TRACE_TYPE_ERROR]) {
+ printf("error ");
+ if (config.has_id)
+ printf(" %8"PRIu64"", entry[FIELD_ID]);
+ printf(" %24s x %-24s\n",
+ get_state_name(mon_ctx, entry[FIELD_CURR_STATE]),
+ get_event_name(mon_ctx, entry[FIELD_EVENT]));
+ } else if (entry[FIELD_EVENT_TYPE] == mon_ctx->trace_types[TRACE_TYPE_EVENT]) {
+ printf("event ");
+ read_field(entry, FIELD_IS_FINAL, raw, mon_ctx);
+ read_field(entry, FIELD_NEXT_STATE, raw, mon_ctx);
+
+ if (config.has_id)
+ printf(" %8"PRIu64"", entry[FIELD_ID]);
+ printf(" %24s x %-24s -> %-24s %c\n",
+ get_state_name(mon_ctx, entry[FIELD_CURR_STATE]),
+ get_event_name(mon_ctx, entry[FIELD_EVENT]),
+ get_state_name(mon_ctx, entry[FIELD_NEXT_STATE]),
+ entry[FIELD_IS_FINAL] ? 'Y' : 'N');
+ }
+
+ return 0;
+}
+
+/*
+ * extract_enum_names - extract names from a BTF enum
+ *
+ * Reads enum member names from BTF and stores them in dest array.
+ * Returns the number of enum members extracted (excluding the
+ * {state/event}_max_NAME entry and trimming the _NAME padding)
+ * on success, or -1 on error.
+ */
+static int extract_enum_names(const struct btf *btf, const char *enum_kind,
+ char dest[][MAX_DA_NAME_LEN], struct bpf_monitor_ctx *ctx)
+{
+ const struct btf_type *enum_type;
+ const struct btf_enum *enums;
+ char buf[2 * MAX_DA_NAME_LEN];
+ bool arrived_at_last = false;
+ int type_id, vlen;
+ int count = 0;
+
+ snprintf(buf, sizeof(buf), "%ss_%s", enum_kind, ctx->monitor_name);
+ type_id = btf__find_by_name_kind(btf, buf, BTF_KIND_ENUM);
+ if (type_id <= 0) {
+ debug_msg("bpf: could not find enum '%s' in BTF\n", buf);
+ return -1;
+ }
+ enum_type = btf__type_by_id(btf, type_id);
+ if (!enum_type)
+ return -1;
+
+ enums = btf_enum(enum_type);
+ vlen = btf_vlen(enum_type);
+
+ snprintf(buf, sizeof(buf), "%s_max_%s", enum_kind, ctx->monitor_name);
+ for (int i = 0; i < vlen; i++) {
+ const char *name = btf__name_by_offset(btf, enums[i].name_off);
+ const char *padding;
+ size_t name_len;
+
+ if (!name || count >= MAX_ENUMS)
+ break;
+
+ /* max value must be the last */
+ if (!strcmp(name, buf)) {
+ if (i == vlen - 1)
+ arrived_at_last = true;
+ break;
+ }
+
+ padding = strrchr(name, '_');
+ name_len = strlen(name);
+ if (padding && !strcmp(ctx->monitor_name, padding + 1))
+ name_len = (size_t)(padding - name);
+
+ if (!name_len)
+ break;
+
+ if (name_len >= MAX_DA_NAME_LEN)
+ name_len = MAX_DA_NAME_LEN - 1;
+ strncpy(dest[count], name, name_len);
+ dest[count][name_len] = '\0';
+ count++;
+ }
+
+ if (!arrived_at_last) {
+ debug_msg("bpf: malformed %ss enum, could fill %d\n", enum_kind, count);
+ return -1;
+ }
+
+ return count;
+}
+
+/*
+ * extract_btf_info - extract BTF types information from the monitor
+ *
+ * Extract state and event names from enums using BTF and extract field
+ * offsets for flexible event parsing.
+ */
+static int extract_btf_info(struct bpf_object *obj, struct bpf_monitor_ctx *ctx)
+{
+ const struct btf *btf;
+
+ btf = bpf_object__btf(obj);
+ if (!btf) {
+ err_msg("bpf: no BTF found in BPF object\n");
+ return -1;
+ }
+
+ if (extract_trace_type_metadata(btf, ctx)) {
+ err_msg("bpf: failed to extract trace type metadata\n");
+ return -1;
+ }
+
+ if (extract_field_metadata(btf, ctx)) {
+ err_msg("bpf: failed to extract field metadata\n");
+ return -1;
+ }
+
+ ctx->num_states = extract_enum_names(btf, "state", ctx->state_names, ctx);
+ ctx->num_events = extract_enum_names(btf, "event", ctx->event_names, ctx);
+ if (ctx->num_states < 0 || ctx->num_events < 0) {
+ err_msg("bpf: failed to extract states (%d) or events names (%d)\n",
+ ctx->num_states, ctx->num_events);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * find_bpf_file - search for a BPF object file in a specific subdirectory
+ */
+static int find_bpf_file(const char *subdir, const char *name, char *path_out, size_t path_len)
+{
+ char path[MAX_PATH];
+
+ for (int i = 0; bpf_base_paths[i][0]; i++) {
+ size_t size = snprintf(path, sizeof(path), "%s/%s/%s.o",
+ bpf_base_paths[i], subdir, name);
+
+ if (size < MAX_PATH && access(path, R_OK) == 0) {
+ strncpy(path_out, path, path_len - 1);
+ path_out[path_len - 1] = '\0';
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * find_bpf_monitor - search for BPF monitor object file in all directories
+ */
+static int find_bpf_monitor(const char *monitor_name, char *path_out, size_t path_len)
+{
+ return find_bpf_file("bpf_monitors", monitor_name, path_out, path_len);
+}
+
+/*
+ * bpf_setup_ring_buffer - set up the ring buffer to trace events
+ *
+ * Find the ring buffer map and set up the events handler.
+ */
+static struct ring_buffer *bpf_setup_ring_buffer(struct bpf_object *obj,
+ struct bpf_monitor_ctx *ctx)
+{
+ struct ring_buffer *rb;
+ struct bpf_map *map;
+ char ringbuf_name[2 * MAX_DA_NAME_LEN];
+
+ snprintf(ringbuf_name, sizeof(ringbuf_name), "rv_rb_%s", ctx->monitor_name);
+ map = bpf_object__find_map_by_name(obj, ringbuf_name);
+ if (!map) {
+ err_msg("bpf: error finding ring buffer %s\n", ringbuf_name);
+ return NULL;
+ }
+
+ rb = ring_buffer__new(bpf_map__fd(map), handle_event, ctx, NULL);
+ if (!rb) {
+ err_msg("bpf: error opening ring buffer: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ return rb;
+}
+
+/*
+ * bpf_usage_print_reactors - print available BPF reactors
+ */
+void bpf_usage_print_reactors(void)
+{
+ fprintf(stderr, " available BPF reactors: nop\n");
+}
+
+/*
+ * bpf_enable_tracing - set the trace_level variable in .rodata
+ *
+ * Find trace_level in a special section of .rodata and set its value before
+ * loading.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int bpf_enable_tracing(struct bpf_object *obj, int val)
+{
+ struct bpf_map *map = bpf_object__find_map_by_name(obj, ".rodata.trace_level");
+ size_t data_size;
+ int *trace_level;
+
+ if (!map)
+ return -1;
+
+ trace_level = bpf_map__initial_value(map, &data_size);
+ if (!trace_level || data_size != sizeof(*trace_level))
+ return -1;
+
+ *trace_level = val;
+ return 0;
+}
+
+/*
+ * open_bpf_monitor - open and load a BPF monitor object from a file path
+ *
+ * Returns loaded BPF object on success, NULL on error.
+ */
+static struct bpf_object *open_bpf_monitor(const char *path, struct bpf_monitor_ctx *ctx)
+{
+ struct bpf_object *obj = NULL;
+ int res;
+
+ obj = bpf_object__open_file(path, NULL);
+ if (!obj) {
+ err_msg("bpf: error opening object: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ if (config.trace) {
+ res = extract_btf_info(obj, ctx);
+ if (res || bpf_enable_tracing(obj, ctx->trace_types[TRACE_TYPE_EVENT])) {
+ err_msg("bpf: failed to enable tracing\n");
+ bpf_object__close(obj);
+ return NULL;
+ }
+ }
+
+ res = bpf_object__load(obj);
+ if (res) {
+ err_msg("bpf: error loading object: %s\n", strerror(-res));
+ bpf_object__close(obj);
+ return NULL;
+ }
+
+ return obj;
+}
+
+/*
+ * attach_bpf_handlers - attach all BPF programs
+ *
+ * Attaches all non-struct_ops programs and stores links in the provided array.
+ *
+ * Returns fd of enable program on success, -1 on error.
+ */
+static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
+ struct bpf_link **links, int *link_count)
+{
+ struct bpf_program *prog;
+ int enable_mon_fd = -1;
+
+ bpf_object__for_each_program(prog, obj) {
+ struct bpf_link *link = NULL;
+ const char *prog_name;
+
+ /* Special program to initialise the monitor */
+ if (!strcmp(bpf_program__name(prog), PROG_ENABLE_MON)) {
+ enable_mon_fd = bpf_program__fd(prog);
+ continue;
+ }
+
+ if (*link_count >= MAX_LINKS) {
+ err_msg("bpf: too many programs to attach (%d)\n", *link_count);
+ return -1;
+ }
+
+ prog_name = bpf_program__name(prog);
+ link = bpf_program__attach(prog);
+ if (!link) {
+ err_msg("bpf: error attaching program '%s': %s\n",
+ prog_name, strerror(errno));
+ return -1;
+ }
+ links[(*link_count)++] = link;
+ }
+ if (enable_mon_fd < 0)
+ err_msg("bpf: could not find program %s\n", PROG_ENABLE_MON);
+ return enable_mon_fd;
+}
+
+/*
+ * bpf_run_monitor - load and run a BPF monitor
+ *
+ * Returns 1 if monitor was found and executed, 0 if not found, -1 on error
+ */
+int bpf_run_monitor(char *monitor_name, int argc, char **argv)
+{
+ struct bpf_link *links[MAX_LINKS] = {0};
+ struct bpf_monitor_ctx ctx = {0};
+ struct ring_buffer *rb = NULL;
+ struct bpf_object *obj = NULL;
+ int res, link_count = 0, enable_mon_fd, retval = -1;
+ char monitor_path[MAX_PATH];
+
+ libbpf_set_print(libbpf_print_fn);
+ bpf_fill_base_paths();
+
+ if (!find_bpf_monitor(monitor_name, monitor_path, sizeof(monitor_path)))
+ return 0;
+
+ config.is_bpf = true;
+
+ res = bpf_read_enable(monitor_name);
+ if (res) {
+ err_msg("bpf: monitor %s (BPF) is already enabled\n", monitor_name);
+ return -1;
+ }
+
+ /* we should be good to go */
+ res = parse_arguments(monitor_name, argc, argv);
+ if (res)
+ mon_usage(1, monitor_name, "bpf: failed parsing arguments");
+
+ strncpy(ctx.monitor_name, monitor_name, sizeof(ctx.monitor_name) - 1);
+
+
+ obj = open_bpf_monitor(monitor_path, &ctx);
+ if (!obj)
+ goto cleanup;
+
+ if (config.trace) {
+ rb = bpf_setup_ring_buffer(obj, &ctx);
+ if (!rb)
+ goto cleanup;
+ }
+
+ enable_mon_fd = attach_bpf_handlers(monitor_name, obj, links, &link_count);
+ if (enable_mon_fd < 0)
+ goto cleanup;
+
+ res = bpf_prog_test_run_opts(enable_mon_fd, NULL);
+ if (res) {
+ err_msg("bpf: error enabling the monitor: %s\n", strerror(-res));
+ goto cleanup;
+ }
+
+ if (config.trace)
+ bpf_print_header();
+
+ while (!should_stop()) {
+ if (!config.trace) {
+ sleep(1);
+ continue;
+ }
+ res = ring_buffer__poll(rb, 100);
+ if (res == -EINTR)
+ break;
+ if (res < 0) {
+ err_msg("bpf: error polling ring buffer: %s\n", strerror(-res));
+ goto cleanup;
+ }
+ }
+ retval = 1;
+
+cleanup:
+ for (int i = 0; i < link_count; i++)
+ bpf_link__destroy(links[i]);
+
+ if (config.trace)
+ ring_buffer__free(rb);
+
+ bpf_object__close(obj);
+
+ return retval;
+}
diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
index 7c4a2e49ff9d..571c9a14c1a2 100644
--- a/tools/verification/rv/src/rv.c
+++ b/tools/verification/rv/src/rv.c
@@ -127,6 +127,9 @@ static void rv_mon(int argc, char **argv)
*/
run += ikm_run_monitor(monitor_name, argc-1, &argv[1]);
+ if (!run)
+ run += bpf_run_monitor(monitor_name, argc-1, &argv[1]);
+
if (!run)
err_msg("rv: monitor %s does not exist\n", monitor_name);
exit(run > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
diff --git a/tools/verification/rv/src/utils.c b/tools/verification/rv/src/utils.c
index e19dd65ec789..8ab5d1560223 100644
--- a/tools/verification/rv/src/utils.c
+++ b/tools/verification/rv/src/utils.c
@@ -81,7 +81,10 @@ void mon_usage(int exit_val, char *monitor_name, const char *fmt, ...)
for (i = 0; usage[i]; i++)
fprintf(stderr, "%s\n", usage[i]);
- ikm_usage_print_reactors();
+ if (config.is_bpf)
+ bpf_usage_print_reactors();
+ else
+ ikm_usage_print_reactors();
exit(exit_val);
}
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing
2026-10-01 15:20 ` [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
@ 2026-10-02 0:43 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:43 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 10573 bytes --]
> tools/rv: Implement BPF monitor loading and tracing
TL;DR:
- Tracing may end early on -EINTR (e.g. Ctrl-Z then fg), unloading the monitor
- BPF monitors may not start without CONFIG_RV
- Enum with exactly 64 members possibly rejected
- Trace column misalignment, misleading comment, unused parameter (nits)
> diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
> index a1de0c157712..9a3f00febfeb 100644
> --- a/tools/verification/rv/src/bpf_monitor.c
> +++ b/tools/verification/rv/src/bpf_monitor.c
[ ... ]
> @@ -221,3 +279,595 @@ int bpf_list_monitors(char *container)
[ ... ]
> +static void bpf_print_header(void)
> +{
> + printf("%16s-%-8s %5s %5s ", "<TASK>", "PID", "[CPU]", "TYPE");
> + if (config.has_id)
> + printf(" %8s", "ID");
> +
> + printf("%24s x %-24s -> %-24s %s\n",
> + "STATE",
> + "EVENT",
> + "NEXT_STATE",
> + "FINAL");
[ ... ]
> + printf("%16s-%-8"PRIu64" [%.3"PRIu64"] ", comm, entry[FIELD_PID], entry[FIELD_CPU]);
> + if (entry[FIELD_EVENT_TYPE] == mon_ctx->trace_types[TRACE_TYPE_ERROR]) {
> + printf("error ");
> + if (config.has_id)
> + printf(" %8"PRIu64"", entry[FIELD_ID]);
> + printf(" %24s x %-24s\n",
> + get_state_name(mon_ctx, entry[FIELD_CURR_STATE]),
> + get_event_name(mon_ctx, entry[FIELD_EVENT]));
> + } else if (entry[FIELD_EVENT_TYPE] == mon_ctx->trace_types[TRACE_TYPE_EVENT]) {
> + printf("event ");
> + read_field(entry, FIELD_IS_FINAL, raw, mon_ctx);
> + read_field(entry, FIELD_NEXT_STATE, raw, mon_ctx);
> +
> + if (config.has_id)
> + printf(" %8"PRIu64"", entry[FIELD_ID]);
> + printf(" %24s x %-24s -> %-24s %c\n",
> + get_state_name(mon_ctx, entry[FIELD_CURR_STATE]),
> + get_event_name(mon_ctx, entry[FIELD_EVENT]),
> + get_state_name(mon_ctx, entry[FIELD_NEXT_STATE]),
> + entry[FIELD_IS_FINAL] ? 'Y' : 'N');
Is the extra leading space before %24s in the data rows intentional?
bpf_print_header() prints the STATE column with no leading space:
printf("%24s x %-24s -> %-24s %s\n", "STATE", ...);
while handle_event() prints:
printf(" %24s x %-24s -> %-24s %c\n", ...);
The prefixes before that point have the same width ("comm-pid [cpu] event "
versus the TYPE header, plus the 9 column ID field), so every STATE, EVENT,
NEXT_STATE and FINAL value lands one column to the right of its header label
and of the " | " separator row. With has_id the header " x " sits at column
71 and the data rows at column 72; without an id it is 62 versus 63.
The error line in the TRACE_TYPE_ERROR branch has the same extra space.
The in-kernel ikm_print_header() and ikm_event_handler() line up because
both use "%8s " / "%8llu " followed by "%24s". Should the data rows use
"%24s x ..." or the header " %24s x ...", so the columns match?
No later commit in the series (up to 3b00bc9b2e54) changes
bpf_print_header() or handle_event().
[ ... ]
> +static int extract_enum_names(const struct btf *btf, const char *enum_kind,
> + char dest[][MAX_DA_NAME_LEN], struct bpf_monitor_ctx *ctx)
[ ... ]
> + if (!name || count >= MAX_ENUMS)
> + break;
> +
> + /* max value must be the last */
> + if (!strcmp(name, buf)) {
> + if (i == vlen - 1)
> + arrived_at_last = true;
> + break;
> + }
[ ... ]
> + if (!arrived_at_last) {
> + debug_msg("bpf: malformed %ss enum, could fill %d\n", enum_kind, count);
> + return -1;
> + }
Can this reject a valid enum with exactly MAX_ENUMS members? The capacity
check runs before the terminator check:
if (!name || count >= MAX_ENUMS)
break;
/* max value must be the last */
if (!strcmp(name, buf)) {
Take an enum with 64 real members followed by state_max_X (vlen = 65).
Iterations i = 0..63 fill dest[0..63] and leave count == 64. At i == 64
the name is "state_max_X", but count >= MAX_ENUMS is already true, so the
loop breaks before the strcmp() can set arrived_at_last.
extract_enum_names() then logs "malformed states enum, could fill 64" and
returns -1. That propagates:
extract_enum_names() -> extract_btf_info() -> open_bpf_monitor()
open_bpf_monitor() prints "bpf: failed to enable tracing" and returns NULL,
so "rv mon X -t" fails entirely.
ctx->state_names and ctx->event_names are char [MAX_ENUMS][MAX_DA_NAME_LEN],
so they have room for all 64 names. Should the *_max_* name be checked
before the capacity check? As written the real limit is 63 entries, with a
misleading "malformed" diagnostic for a well-formed enum.
No shipped monitor (tqueue, nohz) is this large, but rvgen -b (8c5ba90eb059,
later in the series) generates BPF monitors from arbitrary models. The code
is unchanged at the end of the series (3b00bc9b2e54).
[ ... ]
> +/*
> + * attach_bpf_handlers - attach all BPF programs
> + *
> + * Attaches all non-struct_ops programs and stores links in the provided array.
> + *
> + * Returns fd of enable program on success, -1 on error.
> + */
> +static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
> + struct bpf_link **links, int *link_count)
This isn't a bug, but the comment says this "Attaches all non-struct_ops
programs", and the code never checks for struct_ops. The only program
skipped is PROG_ENABLE_MON ("enable_monitor"); every other program from
bpf_object__for_each_program() is passed to bpf_program__attach().
In libbpf, SEC_DEF("struct_ops+", STRUCT_OPS, 0, SEC_NONE) has no
prog_attach_fn, so bpf_program__attach() returns NULL with errno set to
EOPNOTSUPP for such a program. attach_bpf_handlers() would then print
"bpf: error attaching program" and return -1, so the monitor would fail to
start, which is the opposite of what the comment says.
The comment also does not mention that enable_monitor is skipped and its fd
returned. Should the comment describe what the code does (attach everything
except enable_monitor), or should the code skip struct_ops programs, for
example by checking bpf_program__type(prog) == BPF_PROG_TYPE_STRUCT_OPS?
This mismatch is still present at the end of the series (3b00bc9b2e54).
Also, this isn't a bug, but the monitor_name parameter of
attach_bpf_handlers() is never used in the body. The function only uses
obj, links and link_count, and its error messages print prog_name,
*link_count and PROG_ENABLE_MON. bpf_run_monitor() still passes it:
enable_mon_fd = attach_bpf_handlers(monitor_name, obj, links, &link_count);
Could the parameter be dropped, or used in the error messages (for example
"bpf: %s: error attaching program '%s'") so failures say which monitor
they belong to? It is also unused at the end of the series (3b00bc9b2e54).
[ ... ]
> + while (!should_stop()) {
> + if (!config.trace) {
> + sleep(1);
> + continue;
> + }
> + res = ring_buffer__poll(rb, 100);
> + if (res == -EINTR)
> + break;
> + if (res < 0) {
> + err_msg("bpf: error polling ring buffer: %s\n", strerror(-res));
> + goto cleanup;
> + }
> + }
Can this end the trace session without a stop having been requested?
The loop condition already checks should_stop(), which is set by the
SIGINT/SIGTERM handler stop_rv(), but any -EINTR from ring_buffer__poll()
breaks out of the loop, even when the interruption did not come from those
signals.
ring_buffer__poll() returns libbpf_err(-errno) when epoll_wait() fails, and
ep_poll() returns -EINTR whenever signal_pending() is true. That is not
one of the -ERESTART* codes, so the kernel does not restart the call. On
Linux, epoll_wait() returns EINTR after a process is stopped by a job
control signal and then resumed with SIGCONT (see signal(7)), and rv
installs no handler for SIGTSTP or SIGCONT.
For example, with "rv mon tqueue -t", press Ctrl-Z and then run "fg":
epoll_wait() returns EINTR
-> ring_buffer__poll() returns -EINTR
-> break, with should_stop() still false
-> cleanup: bpf_link__destroy(), bpf_object__close()
The monitor is unloaded and rv exits with success instead of resuming the
trace. The in-kernel path (ikm_run_monitor) handles this case, because its
sleep(1)/tracefs loop just re-checks should_stop(). Should -EINTR use
"continue" here and let should_stop() decide? The code is unchanged at the
end of the series (3b00bc9b2e54).
[ ... ]
> diff --git a/tools/verification/rv/src/rv.c b/tools/verification/rv/src/rv.c
> index 7c4a2e49ff9d..571c9a14c1a2 100644
> --- a/tools/verification/rv/src/rv.c
> +++ b/tools/verification/rv/src/rv.c
[ ... ]
> @@ -127,6 +127,9 @@ static void rv_mon(int argc, char **argv)
> */
> run += ikm_run_monitor(monitor_name, argc-1, &argv[1]);
>
> + if (!run)
> + run += bpf_run_monitor(monitor_name, argc-1, &argv[1]);
> +
> if (!run)
> err_msg("rv: monitor %s does not exist\n", monitor_name);
> exit(run > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
Can BPF monitors still be started on a kernel without CONFIG_RV? rv list
still shows them there, but ikm_run_monitor() does not return 0 ("not
found") when the in-kernel RV interface is missing. It returns -1
("error"):
__ikm_find_monitor_name():
available_monitors = tracefs_instance_file_read(NULL, "rv/available_monitors", NULL);
if (!available_monitors)
return -1;
ikm_run_monitor():
retval = __ikm_find_monitor_name(monitor_name, full_name);
...
if (retval < 0) {
err_msg("ikm: error finding monitor %s\n", nested_name);
return -1;
}
With no tracefs rv/ directory, run is -1 and "if (!run)" is false, so
bpf_run_monitor() is never called. "rv mon nohz" prints "ikm: error finding
monitor nohz" and exits with failure.
The listing path treats the same situation differently: rv_list() calls
bpf_list_monitors() even after ikm_list_monitors() fails with "is CONFIG_RV
enabled?", so the BPF monitors are listed but then cannot be run.
The BPF monitors added later in the series only attach to generic hooks:
nohz uses fentry/sched_tick, fentry/tick_nohz_restart_sched_tick and
tp_btf/tick_stop, and tqueue uses tp_btf/sched_{en,de}queue_tp, which are
exported unconditionally in kernel/sched/core.c. Neither needs CONFIG_RV.
Should the dispatch fall through to the BPF path when the in-kernel
interface is unavailable, rather than only on run == 0? No later commit in
the series (up to 3b00bc9b2e54) changes this.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (7 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 08/15] tools/rv: Implement BPF monitor loading and tracing Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:42 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 10/15] tools/rv: Add BPF monitors Gabriele Monaco
` (5 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Add bpf_atomic.h including atomic read/write macros like READ_ONCE() and
try_cmpxchg(). This version is a stripped down copy of the one currently
present in selftests/bpf/libarena.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
.../verification/rv/bpf_monitors/bpf_atomic.h | 105 ++++++++++++++++++
1 file changed, 105 insertions(+)
create mode 100644 tools/verification/rv/bpf_monitors/bpf_atomic.h
diff --git a/tools/verification/rv/bpf_monitors/bpf_atomic.h b/tools/verification/rv/bpf_monitors/bpf_atomic.h
new file mode 100644
index 000000000000..f834ea91c9d3
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/bpf_atomic.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+#ifndef BPF_ATOMIC_H
+#define BPF_ATOMIC_H
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+extern bool CONFIG_X86_64 __kconfig __weak;
+
+/*
+ * __unqual_typeof(x) - Declare an unqualified scalar type, leaving
+ * non-scalar types unchanged,
+ *
+ * Prefer C11 _Generic for better compile-times and simpler code. Note: 'char'
+ * is not type-compatible with 'signed char', and we define a separate case.
+ *
+ * This is copied verbatim from kernel's include/linux/compiler_types.h, but
+ * with default expression (for pointers) changed from (x) to (typeof(x)0).
+ *
+ * This is because LLVM has a bug where for lvalue (x), it does not get rid of
+ * an extra address_space qualifier, but does in case of rvalue (typeof(x)0).
+ * Hence, for pointers, we need to create an rvalue expression to get the
+ * desired type. See https://github.com/llvm/llvm-project/issues/53400.
+ */
+#define __scalar_type_to_expr_cases(type) \
+ unsigned type : (unsigned type)0, signed type : (signed type)0
+
+#define __unqual_typeof(x) \
+ typeof(_Generic((x), \
+ char: (char)0, \
+ __scalar_type_to_expr_cases(char), \
+ __scalar_type_to_expr_cases(short), \
+ __scalar_type_to_expr_cases(int), \
+ __scalar_type_to_expr_cases(long), \
+ __scalar_type_to_expr_cases(long long), \
+ default: (typeof(x))0))
+
+/* No-op for BPF */
+#define cpu_relax() ({})
+
+#define READ_ONCE(x) (*(volatile typeof(x) *)&(x))
+
+#ifndef WRITE_ONCE
+#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *)&(x)) = (val))
+#endif
+
+#define cmpxchg(p, old, new) __sync_val_compare_and_swap((p), old, new)
+
+#define try_cmpxchg(p, pold, new) \
+ ({ \
+ __unqual_typeof(*(pold)) __o = *(pold); \
+ __unqual_typeof(*(p)) __r = cmpxchg(p, __o, new); \
+ if (__r != __o) \
+ *(pold) = __r; \
+ __r == __o; \
+ })
+
+#define try_cmpxchg_relaxed(p, pold, new) try_cmpxchg(p, pold, new)
+
+#define try_cmpxchg_acquire(p, pold, new) try_cmpxchg(p, pold, new)
+
+#define smp_mb() \
+ ({ \
+ volatile unsigned long __val; \
+ __sync_fetch_and_add(&__val, 0); \
+ })
+
+#define smp_rmb() \
+ ({ \
+ if (!CONFIG_X86_64) \
+ smp_mb(); \
+ else \
+ barrier(); \
+ })
+
+#define smp_wmb() \
+ ({ \
+ if (!CONFIG_X86_64) \
+ smp_mb(); \
+ else \
+ barrier(); \
+ })
+
+/* Control dependency provides LOAD->STORE, provide LOAD->LOAD */
+#define smp_acquire__after_ctrl_dep() ({ smp_rmb(); })
+
+#define smp_load_acquire(p) \
+ ({ \
+ __unqual_typeof(*(p)) __v = READ_ONCE(*(p)); \
+ if (!CONFIG_X86_64) \
+ smp_mb(); \
+ barrier(); \
+ __v; \
+ })
+
+#define smp_store_release(p, val) \
+ ({ \
+ if (!CONFIG_X86_64) \
+ smp_mb(); \
+ barrier(); \
+ WRITE_ONCE(*(p), val); \
+ })
+
+#endif /* BPF_ATOMIC_H */
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena
2026-10-01 15:20 ` [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
@ 2026-10-02 0:42 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:42 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2680 bytes --]
> tools/rv: Copy stripped bpf_atomic.h from libarena
TL;DR: The CONFIG_X86_64 kconfig extern in bpf_atomic.h may make BPF monitors
fail to load (-EINVAL) on hosts without a readable kernel config, but only in
the bisect window until a later patch fixes it.
> diff --git a/tools/verification/rv/bpf_monitors/bpf_atomic.h b/tools/verification/rv/bpf_monitors/bpf_atomic.h
> new file mode 100644
> index 000000000000..f834ea91c9d3
> --- /dev/null
> +++ b/tools/verification/rv/bpf_monitors/bpf_atomic.h
> @@ -0,0 +1,105 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
> +#ifndef BPF_ATOMIC_H
> +#define BPF_ATOMIC_H
> +
> +#include <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +
> +extern bool CONFIG_X86_64 __kconfig __weak;
Can this extern make every BPF monitor fail to load on a host that has no
readable kernel config?
smp_load_acquire() and smp_store_release() reference CONFIG_X86_64, and
include/rv/da_monitor.h uses these macros (da_monitoring(),
da_monitor_start(), da_monitor_reset_state()), so each monitor object ends
up with a CONFIG_ kconfig extern.
The __weak attribute does not make it optional in that case. In libbpf,
bpf_object__resolve_externs() sets need_config for any extern whose name
starts with "CONFIG_", and then requires the config file to be readable:
if (str_has_pfx(ext->name, "CONFIG_")) {
need_config = true;
continue;
}
...
if (need_config) {
err = bpf_object__read_kconfig_file(obj, kcfg_data);
if (err)
return -EINVAL;
}
bpf_object__read_kconfig_file() returns -ENOENT when neither
/boot/config-$(uname -r) nor /proc/config.gz exists.
Once f5fc86049b84 ("tools/rv: Add BPF monitors") includes this header via
da_monitor_bpf.h, open_bpf_monitor() calls bpf_object__open_file(path, NULL)
followed by bpf_object__load(obj). That load would fail with -EINVAL on
self-built kernels without CONFIG_IKCONFIG_PROC, or in containers without
/boot/config-*.
The series fixes this later in 3b66b9d4a13a ("tools/rv: Define CONFIG_X86_64
statically for BPF monitors"), which passes .kconfig = "CONFIG_X86_64=y|n"
through bpf_object_open_opts. So the failure is limited to the bisect
window f5fc86049b84..3b66b9d4a13a^.
Could that fix be folded in before f5fc86049b84, or could this header
resolve the architecture at compile time, so that every step of the series
stays loadable?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 10/15] tools/rv: Add BPF monitors
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (8 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 09/15] tools/rv: Copy stripped bpf_atomic.h from libarena Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:43 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
` (4 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Add the code for 2 initial BPF monitors, both DA (the only currently
supported type):
* queue: per-task monitor stating tasks alternate enqueue and dequeue.
* nohz: per-cpu monitor stating the scheduler tick don't run when
stopped and can run only after being resumed.
BPF monitors can include the in-kernel da_monitor.h, which is now
adapted to share as much common code and conditionally compile what
needs to be BPF or kernel specific.
Integrate BPF monitor building into main rv Makefile, if all tools are
available on the system.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 21 +-
tools/verification/models/nohz.dot | 16 +
tools/verification/models/tqueue.dot | 15 +
tools/verification/rv/Makefile | 42 +-
tools/verification/rv/Makefile.config | 26 ++
tools/verification/rv/Makefile.rv | 5 +
tools/verification/rv/bpf_monitors/.gitignore | 2 +
.../rv/bpf_monitors/da_monitor_bpf.h | 368 ++++++++++++++++++
tools/verification/rv/bpf_monitors/nohz.c | 42 ++
tools/verification/rv/bpf_monitors/nohz.h | 49 +++
tools/verification/rv/bpf_monitors/tqueue.c | 30 ++
tools/verification/rv/bpf_monitors/tqueue.h | 47 +++
12 files changed, 655 insertions(+), 8 deletions(-)
create mode 100644 tools/verification/models/nohz.dot
create mode 100644 tools/verification/models/tqueue.dot
create mode 100644 tools/verification/rv/bpf_monitors/.gitignore
create mode 100644 tools/verification/rv/bpf_monitors/da_monitor_bpf.h
create mode 100644 tools/verification/rv/bpf_monitors/nohz.c
create mode 100644 tools/verification/rv/bpf_monitors/nohz.h
create mode 100644 tools/verification/rv/bpf_monitors/tqueue.c
create mode 100644 tools/verification/rv/bpf_monitors/tqueue.h
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index 7f0bdfd7cce3..ee1cec73ec85 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -14,14 +14,19 @@
#ifndef _RV_DA_MONITOR_H
#define _RV_DA_MONITOR_H
-#include <rv/automata.h>
-#include <linux/rv.h>
+#ifndef __BPF__
+/* Kernel includes */
#include <rv/kunit.h>
-#include <linux/stringify.h>
#include <linux/bug.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/hashtable.h>
+#endif /* __BPF__ */
+
+#include <linux/args.h>
+#include <rv/automata.h>
+#include <linux/rv.h>
+#include <linux/stringify.h>
/*
* Per-cpu variables require a unique name although static in some
@@ -77,6 +82,9 @@ static struct rv_monitor rv_this;
#define da_id_type int
#endif
+#ifdef __BPF__
+#include "da_monitor_bpf.h"
+#else
static void react(enum states curr_state, enum events event)
{
rv_react(&rv_this,
@@ -85,6 +93,7 @@ static void react(enum states curr_state, enum events event)
model_get_event_name(event),
model_get_state_name(curr_state));
}
+#endif
/*
* da_monitor_reset_state - reset a monitor and setting it to init state
@@ -159,6 +168,7 @@ static inline bool da_monitor_handling_event(struct da_monitor *da_mon)
return 1;
}
+#ifndef __BPF__
#if RV_MON_TYPE == RV_MON_GLOBAL
/*
* Functions to define, init and get a global monitor.
@@ -685,6 +695,7 @@ static inline void da_trace_error(struct da_monitor *da_mon,
model_get_event_name(event));
}
#endif /* RV_MON_TYPE */
+#endif /* __BPF__ */
/*
* da_event - handle an event for the da_mon
@@ -806,7 +817,7 @@ static inline bool da_handle_start_run_event(enum events event)
return __da_handle_start_run_event(da_get_monitor(), event, 0);
}
-#elif RV_MON_TYPE == RV_MON_PER_TASK
+#elif !defined(__BPF__) && RV_MON_TYPE == RV_MON_PER_TASK
/*
* Handle event for per task.
*/
@@ -847,7 +858,7 @@ static inline bool da_handle_start_run_event(struct task_struct *tsk,
return __da_handle_start_run_event(da_get_monitor(tsk), event, tsk->pid);
}
-#elif RV_MON_TYPE == RV_MON_PER_OBJ
+#elif !defined(__BPF__) && RV_MON_TYPE == RV_MON_PER_OBJ
/*
* Handle event for per object.
*/
diff --git a/tools/verification/models/nohz.dot b/tools/verification/models/nohz.dot
new file mode 100644
index 000000000000..49adb25e32d8
--- /dev/null
+++ b/tools/verification/models/nohz.dot
@@ -0,0 +1,16 @@
+digraph state_automaton {
+ {node [shape = circle] "stopped"};
+ {node [shape = plaintext, style=invis, label=""] "__init_running"};
+ {node [shape = doublecircle] "running"};
+ {node [shape = circle] "running"};
+ "__init_running" -> "running";
+ "stopped" [label = "stopped"];
+ "stopped" -> "running" [ label = "tick_restart" ];
+ "running" [label = "running"];
+ "running" -> "running" [ label = "sched_tick" ];
+ "running" -> "stopped" [ label = "tick_stop" ];
+ { rank = min ;
+ "__init_running";
+ "running";
+ }
+}
diff --git a/tools/verification/models/tqueue.dot b/tools/verification/models/tqueue.dot
new file mode 100644
index 000000000000..7aadf99ed29d
--- /dev/null
+++ b/tools/verification/models/tqueue.dot
@@ -0,0 +1,15 @@
+digraph state_automaton {
+ {node [shape = circle] "enqueued"};
+ {node [shape = plaintext, style=invis, label=""] "__init_dequeued"};
+ {node [shape = doublecircle] "dequeued"};
+ {node [shape = circle] "dequeued"};
+ "__init_dequeued" -> "dequeued";
+ "enqueued" [label = "enqueued"];
+ "enqueued" -> "dequeued" [ label = "sched_dequeue" ];
+ "dequeued" [label = "dequeued"];
+ "dequeued" -> "enqueued" [ label = "sched_enqueue" ];
+ { rank = min ;
+ "__init_dequeued";
+ "dequeued";
+ }
+}
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index 0a8c7a656f7f..bb81593acd71 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -33,9 +33,13 @@ DOCSRC := ../../../Documentation/tools/rv/
FEATURE_TESTS := libtraceevent
FEATURE_TESTS += libtracefs
FEATURE_TESTS += libbpf
+FEATURE_TESTS += clang-bpf-co-re
+FEATURE_TESTS += bpftool-btf
FEATURE_DISPLAY := libtraceevent
FEATURE_DISPLAY += libtracefs
FEATURE_DISPLAY += libbpf
+FEATURE_DISPLAY += clang-bpf-co-re
+FEATURE_DISPLAY += bpftool-btf
all: $(RV)
@@ -43,7 +47,7 @@ include $(srctree)/tools/build/Makefile.include
include Makefile.rv
# check for dependencies only on required targets
-NON_CONFIG_TARGETS := clean install doc doc_clean doc_install
+NON_CONFIG_TARGETS := clean install doc doc_clean doc_install bpf
config := 1
ifdef MAKECMDGOALS
@@ -68,19 +72,51 @@ static: $(RV_IN)
$(eval LDFLAGS += -static)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
+BPF_DIR := bpf_monitors
+BPF_SOURCES := $(wildcard $(BPF_DIR)/*.c)
+BPF_OBJECTS := $(BPF_SOURCES:.c=.o)
+VMLINUX_H := $(BPF_DIR)/vmlinux.h
+
+BPF_INCLUDES := -I$(srctree)/include/uapi
+BPF_INCLUDES += -I$(srctree)/include
+BPF_INCLUDES += -I$(BPF_DIR)
+
+BPF_CFLAGS := -g -O2 -target bpf -MMD -MP $(BPF_INCLUDES)
+BPF_CFLAGS += -Wall -Wno-unused-function -Wno-missing-declarations
+
+VMLINUX := ../../../vmlinux
+VMLINUX_BTF ?= $(if $(wildcard $(VMLINUX)) , $(VMLINUX), /sys/kernel/btf/vmlinux)
+
+$(VMLINUX_H): $(VMLINUX_BTF)
+ $(QUIET_GEN)$(SYSTEM_BPFTOOL) btf dump file $< format c > $@
+
+$(BPF_DIR)/%.o: $(BPF_DIR)/%.c $(VMLINUX_H)
+ $(QUIET_CLANG)$(CLANG) $(BPF_CFLAGS) -c $< -o $@
+ $(Q)$(LLVM_STRIP) -g $@
+ $(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
+
+-include $(BPF_SOURCES:.c=.d)
+
+bpf: $(BPF_OBJECTS)
+
rv.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
$(RV_IN): fixdep FORCE
make $(build)=rv
+ifeq ($(BUILD_BPF_OBJS),1)
+all: bpf
+endif
+
clean: doc_clean fixdep-clean
$(call QUIET_CLEAN, rv)
- $(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
+ $(Q)find . -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '*.d' -delete
$(Q)rm -f rv rv-static fixdep FEATURE-DUMP rv-*
+ $(Q)rm -f $(VMLINUX_H)
$(Q)rm -rf feature
check: $(RV)
RV=$(RV) prove -o --directives -f tests/
-.PHONY: FORCE clean check
+.PHONY: FORCE clean check bpf
diff --git a/tools/verification/rv/Makefile.config b/tools/verification/rv/Makefile.config
index 0600170ac217..1caad39ab0c9 100644
--- a/tools/verification/rv/Makefile.config
+++ b/tools/verification/rv/Makefile.config
@@ -66,6 +66,32 @@ ifeq ($(BUILD_BPF),1)
$(call lib_setup,bpf)
endif
+# Building BPF monitors requires clang and bpftool, RV with BPF monitors
+# support can still run without building monitors
+ifndef BUILD_BPF_OBJS
+ BUILD_BPF_OBJS := $(BUILD_BPF)
+endif
+
+ifeq ($(BUILD_BPF_OBJS),1)
+ $(call feature_check,clang-bpf-co-re)
+ ifeq ($(feature-clang-bpf-co-re), 1)
+ $(call detected,CONFIG_CLANG_BPF_CO_RE)
+ else
+ $(info clang is missing or does not support BPF CO-RE, cannot build BPF monitors.)
+ $(info Please install clang)
+ BUILD_BPF_OBJS := 0
+ endif
+
+ $(call feature_check,bpftool-btf)
+ ifeq ($(feature-bpftool-btf), 1)
+ $(call detected,CONFIG_BPFTOOL_BTF)
+ else
+ $(info bpftool is missing or does not support btf, cannot build BPF monitors.)
+ $(info Please install bpftool)
+ BUILD_BPF_OBJS := 0
+ endif
+endif
+
ifeq ($(STOP_ERROR),1)
$(error Please, check the errors above.)
endif
diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
index 2497fb96c83d..418748bc3aa1 100644
--- a/tools/verification/rv/Makefile.rv
+++ b/tools/verification/rv/Makefile.rv
@@ -33,12 +33,17 @@ INSTALL := install
MKDIR := mkdir
STRIP := strip
BINDIR := /usr/bin
+BPF_MON_DIR := /usr/share/rv/bpf_monitors
.PHONY: install
install: doc_install
$(Q)$(MKDIR) -p $(DESTDIR)$(BINDIR)
$(call QUIET_INSTALL,rv)$(INSTALL) $(OUTPUT)rv -m 755 $(DESTDIR)$(BINDIR)
$(Q)@$(STRIP) $(DESTDIR)$(BINDIR)/rv
+ifneq ($(wildcard bpf_monitors/*.o),)
+ $(Q)$(MKDIR) -p $(DESTDIR)$(BPF_MON_DIR)
+ $(call QUIET_INSTALL,bpf_monitors)$(INSTALL) bpf_monitors/*.o -m 644 $(DESTDIR)$(BPF_MON_DIR)
+endif
.PHONY: doc doc_clean doc_install
doc:
diff --git a/tools/verification/rv/bpf_monitors/.gitignore b/tools/verification/rv/bpf_monitors/.gitignore
new file mode 100644
index 000000000000..e5add9141ef5
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/.gitignore
@@ -0,0 +1,2 @@
+*.d
+vmlinux.h
diff --git a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
new file mode 100644
index 000000000000..12f0f78b9c9d
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * BPF support for DA monitors.
+ *
+ * BPF programs can include the in-kernel da_monitor directly, this
+ * header contains all the BPF compatibility layer.
+ *
+ * Copyright (C) 2026 Red Hat Inc, Gabriele Monaco <gmonaco@redhat.com>
+ */
+
+#ifndef _DA_MONITOR_BPF_H
+#define _DA_MONITOR_BPF_H
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include "bpf_atomic.h"
+
+/* BPF monitors don't support these */
+#define rv_react(...) do {} while (0)
+#define trace_rv_retries_error(...) do {} while (0)
+#define pr_warn(fmt, ...) bpf_printk(fmt, ##__VA_ARGS__)
+#define rv_monitoring_on() 1
+#define da_implicit_guard()
+#define IS_ENABLED(conf) 0
+
+/*
+ * Also used to filter out events higher values imply lower ones.
+ * Userspace will parse this enum but expects EVENT to imply ERROR(s) and ERROR
+ * to imply any other ERROR* type.
+ */
+enum rv_trace_type {
+ RV_TRACE_NONE,
+ RV_TRACE_ERROR,
+ RV_TRACE_EVENT,
+};
+
+/* Configuration set from userspace before loading */
+const volatile SEC(".rodata.trace_level") enum rv_trace_type trace_level = RV_TRACE_NONE;
+
+/*
+ * BPF ring buffer for trace events
+ * Events and errors are sent to userspace via this ringbuf
+ */
+struct rv_trace_entry {
+ uint8_t event_type;
+ uint8_t is_final;
+ char comm[TASK_COMM_LEN];
+#if RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
+ uint32_t id;
+#endif
+ uint32_t pid;
+ uint32_t cpu;
+ uint32_t curr_state;
+ uint32_t event;
+ uint32_t next_state;
+};
+
+#define rv_mon_map CONCATENATE(rv_mon_, MONITOR_NAME)
+#define rv_rb CONCATENATE(rv_rb_, MONITOR_NAME)
+
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 256 * 1024);
+} rv_rb SEC(".maps");
+
+#ifndef __used
+#define __used __attribute__((used))
+#endif
+
+/* Force types to be included in BTF for userspace parsing */
+static const enum states __used _btf_states;
+static const enum events __used _btf_events;
+static const struct rv_trace_entry __used *_btf_trace;
+
+static inline void da_monitor_reset(struct da_monitor *da_mon);
+
+/* BPF monitors do not use reactors */
+static inline void react(enum states curr_state, enum events event) { }
+
+/*
+ * BPF monitor implementations
+ * These use BPF maps instead of kernel data structures
+ */
+
+#if RV_MON_TYPE == RV_MON_GLOBAL
+/*
+ * BPF Global monitor - uses a single-entry BPF array map
+ */
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, union rv_task_monitor);
+} rv_mon_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor(void)
+{
+ __u32 key = 0;
+ union rv_task_monitor *mon = bpf_map_lookup_elem(&rv_mon_map, &key);
+
+ return &mon->da_mon;
+}
+
+#elif RV_MON_TYPE == RV_MON_PER_CPU
+/*
+ * BPF Per-CPU monitor - uses BPF per-cpu array map
+ */
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, union rv_task_monitor);
+} rv_mon_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor(void)
+{
+ __u32 key = 0;
+ union rv_task_monitor *mon = bpf_map_lookup_elem(&rv_mon_map, &key);
+
+ return &mon->da_mon;
+}
+
+#elif RV_MON_TYPE == RV_MON_PER_OBJ || RV_MON_TYPE == RV_MON_PER_TASK
+/*
+ * BPF Per-Object monitor - uses BPF hash map
+ * Note: monitor_target_bpf type must be compatible with BPF
+ * Types and structs must be different not to collide with vmlinux.h
+ */
+
+#if RV_MON_TYPE == RV_MON_PER_TASK
+/*
+ * BPF Per-Task monitor - uses BPF hash map indexed by PID
+ *
+ * Just a special case of per-object monitor with API consistent with in-kernel
+ * monitors (no need to pass the id).
+ */
+
+#define da_get_monitor(tsk) da_get_monitor_bpf(BPF_CORE_READ(tsk, pid), tsk)
+#define da_handle_event(tsk, event) \
+ da_handle_event_bpf(BPF_CORE_READ(tsk, pid), tsk, event)
+#define da_handle_start_event(tsk, event) \
+ da_handle_start_event_bpf(BPF_CORE_READ(tsk, pid), tsk, event)
+#define da_handle_start_run_event(tsk, event) \
+ da_handle_start_run_event_bpf(BPF_CORE_READ(tsk, pid), tsk, event)
+
+typedef struct task_struct *monitor_target_bpf;
+static inline void da_destroy_storage(da_id_type id);
+
+SEC("tp_btf/sched_process_exit")
+int BPF_PROG(handle_obj_cleanup, struct task_struct *p, bool group_dead)
+{
+ da_destroy_storage(p->pid);
+ return 0;
+}
+
+#else
+
+#define da_get_monitor da_get_monitor_bpf
+#define da_handle_event da_handle_event_bpf
+#define da_handle_start_event da_handle_start_event_bpf
+#define da_handle_start_run_event da_handle_start_run_event_bpf
+
+#endif /* RV_MON_PER_TASK */
+
+struct da_monitor_storage_bpf {
+ da_id_type id;
+ monitor_target_bpf target;
+ union rv_task_monitor rv;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 10240);
+ __type(key, da_id_type);
+ __type(value, struct da_monitor_storage_bpf);
+} rv_mon_map SEC(".maps");
+
+static inline struct da_monitor *da_get_monitor_bpf(da_id_type id, monitor_target_bpf target)
+{
+ struct da_monitor_storage_bpf *storage;
+
+ storage = bpf_map_lookup_elem(&rv_mon_map, &id);
+ return storage ? &storage->rv.da_mon : NULL;
+}
+
+static inline struct da_monitor *da_create_storage(da_id_type id,
+ monitor_target_bpf target,
+ struct da_monitor *da_mon)
+{
+ struct da_monitor_storage_bpf new_storage = {
+ .id = id,
+ .target = target,
+ };
+
+ if (da_mon)
+ return da_mon;
+
+ bpf_map_update_elem(&rv_mon_map, &id, &new_storage, BPF_NOEXIST);
+ return da_get_monitor_bpf(id, target);
+}
+
+static inline void da_destroy_storage(da_id_type id)
+{
+ bpf_map_delete_elem(&rv_mon_map, &id);
+}
+
+static inline da_id_type da_get_id(struct da_monitor *da_mon)
+{
+ return container_of(da_mon, struct da_monitor_storage_bpf, rv.da_mon)->id;
+}
+
+static inline monitor_target_bpf da_get_target(struct da_monitor *da_mon)
+{
+ return container_of(da_mon, struct da_monitor_storage_bpf, rv.da_mon)->target;
+}
+
+/*
+ * Handle event for per object and per task
+ */
+
+static inline void __da_handle_event(struct da_monitor *da_mon,
+ enum events event, da_id_type id);
+static inline bool __da_handle_start_event(struct da_monitor *da_mon,
+ enum events event, da_id_type id);
+static inline bool __da_handle_start_run_event(struct da_monitor *da_mon,
+ enum events event, da_id_type id);
+
+/*
+ * da_handle_event - handle an event
+ */
+static inline void da_handle_event_bpf(da_id_type id, monitor_target_bpf target, enum events event)
+{
+ struct da_monitor *da_mon;
+
+ da_mon = da_get_monitor_bpf(id, target);
+ if (likely(da_mon))
+ __da_handle_event(da_mon, event, id);
+}
+
+/*
+ * da_handle_start_event - start monitoring or handle event
+ *
+ * This function is used to notify the monitor that the system is returning
+ * to the initial state, so the monitor can start monitoring in the next event.
+ * Thus:
+ *
+ * If the monitor already started, handle the event.
+ * If the monitor did not start yet, start the monitor but skip the event.
+ */
+static inline bool da_handle_start_event_bpf(da_id_type id, monitor_target_bpf target,
+ enum events event)
+{
+ struct da_monitor *da_mon;
+
+ da_mon = da_get_monitor_bpf(id, target);
+ da_mon = da_create_storage(id, target, da_mon);
+ if (unlikely(!da_mon))
+ return 0;
+ return __da_handle_start_event(da_mon, event, id);
+}
+
+/*
+ * da_handle_start_run_event - start monitoring and handle event
+ *
+ * This function is used to notify the monitor that the system is in the
+ * initial state, so the monitor can start monitoring and handling event.
+ */
+static inline bool da_handle_start_run_event_bpf(da_id_type id, monitor_target_bpf target,
+ enum events event)
+{
+ struct da_monitor *da_mon;
+
+ da_mon = da_get_monitor_bpf(id, target);
+ da_mon = da_create_storage(id, target, da_mon);
+ if (unlikely(!da_mon))
+ return 0;
+ return __da_handle_start_run_event(da_mon, event, id);
+}
+
+static inline void da_reset_bpf(da_id_type id, monitor_target_bpf target)
+{
+ struct da_monitor *da_mon;
+
+ da_mon = da_get_monitor_bpf(id, target);
+ if (likely(da_mon))
+ da_monitor_reset(da_mon);
+}
+
+#endif /* RV_MON_TYPE */
+
+static inline void *_da_trace_common(enum states curr_state, enum events event,
+ enum rv_trace_type type)
+{
+ struct rv_trace_entry *entry;
+ static const char stub_comm[] = "<XXX>";
+
+ if (trace_level < type)
+ return NULL;
+
+ entry = bpf_ringbuf_reserve(&rv_rb, sizeof(*entry), 0);
+ if (!entry)
+ return NULL;
+ entry->event_type = type;
+ entry->cpu = bpf_get_smp_processor_id();
+ entry->pid = bpf_get_current_pid_tgid() & 0xffffffff;
+ if (bpf_get_current_comm(entry->comm, TASK_COMM_LEN))
+ __builtin_memcpy(entry->comm, stub_comm, sizeof(stub_comm));
+ entry->curr_state = curr_state;
+ entry->event = event;
+
+ return entry;
+}
+
+#if RV_MON_TYPE == RV_MON_PER_TASK || RV_MON_TYPE == RV_MON_PER_OBJ
+static inline void _da_trace_id(struct rv_trace_entry *entry, da_id_type id)
+{
+ entry->id = id;
+}
+#else
+static inline void _da_trace_id(struct rv_trace_entry *entry, da_id_type id) { }
+#endif
+
+/*
+ * BPF trace events implementation using ring buffer
+ */
+static inline void da_trace_event(struct da_monitor *da_mon,
+ enum states curr_state, enum events event,
+ enum states next_state,
+ da_id_type id)
+{
+ struct rv_trace_entry *entry = _da_trace_common(curr_state, event, RV_TRACE_EVENT);
+
+ if (!entry)
+ return;
+ _da_trace_id(entry, id);
+ entry->is_final = model_is_final_state(next_state);
+ entry->next_state = next_state;
+
+ bpf_ringbuf_submit(entry, 0);
+}
+
+static inline void da_trace_error(struct da_monitor *da_mon,
+ enum states curr_state, enum events event,
+ da_id_type id)
+{
+ struct rv_trace_entry *entry = _da_trace_common(curr_state, event, RV_TRACE_ERROR);
+
+ if (!entry)
+ return;
+ _da_trace_id(entry, id);
+
+ bpf_ringbuf_submit(entry, 0);
+}
+
+/*
+ * enable_monitor - BPF program to be called manually after all handlers are attached
+ */
+SEC("syscall")
+int enable_monitor(void *ctx)
+{
+ rv_this.enabled = 1;
+ return 0;
+}
+
+#endif // _DA_MONITOR_BPF_H
diff --git a/tools/verification/rv/bpf_monitors/nohz.c b/tools/verification/rv/bpf_monitors/nohz.c
new file mode 100644
index 000000000000..cb19944fad77
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/nohz.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "nohz.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This monitor is broken on purpose to test errors, sched_tick can run with
+ * stopped ticks for one last time (deferred tick reprogram).
+ * A way to fix this monitor is to handle the sched_tick event only when
+ * tick_nohz_handler returns HRTIMER_RESTART (i.e. it isn't stopping the tick).
+ */
+SEC("fentry/sched_tick")
+int BPF_PROG(handle_sched_tick)
+{
+ da_handle_start_event(sched_tick_nohz);
+ return 0;
+}
+
+SEC("fentry/tick_nohz_restart_sched_tick")
+int BPF_PROG(handle_tick_restart)
+{
+ da_handle_start_event(tick_restart_nohz);
+ return 0;
+}
+
+SEC("tp_btf/tick_stop")
+int BPF_PROG(handle_tick_stop, int success, int dependency)
+{
+ if (success)
+ da_handle_event(tick_stop_nohz);
+ return 0;
+}
+
+static struct rv_monitor rv_this = {
+ .enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "tick does not run when stopped.";
diff --git a/tools/verification/rv/bpf_monitors/nohz.h b/tools/verification/rv/bpf_monitors/nohz.h
new file mode 100644
index 000000000000..ef1c88f31f51
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/nohz.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of nohz automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME nohz
+
+enum states_nohz {
+ running_nohz,
+ stopped_nohz,
+ state_max_nohz,
+};
+
+#define INVALID_STATE state_max_nohz
+
+enum events_nohz {
+ sched_tick_nohz,
+ tick_restart_nohz,
+ tick_stop_nohz,
+ event_max_nohz,
+};
+
+struct automaton_nohz {
+ char state_names[state_max_nohz][32];
+ char event_names[event_max_nohz][32];
+ unsigned char function[state_max_nohz][event_max_nohz];
+ unsigned char initial_state;
+ bool final_states[state_max_nohz];
+};
+
+static const struct automaton_nohz automaton_nohz = {
+ .state_names = {
+ "running",
+ "stopped",
+ },
+ .event_names = {
+ "sched_tick",
+ "tick_restart",
+ "tick_stop",
+ },
+ .function = {
+ { running_nohz, INVALID_STATE, stopped_nohz },
+ { INVALID_STATE, running_nohz, INVALID_STATE },
+ },
+ .initial_state = running_nohz,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rv/bpf_monitors/tqueue.c b/tools/verification/rv/bpf_monitors/tqueue.c
new file mode 100644
index 000000000000..e05c5c13c634
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/tqueue.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_TASK
+#include "tqueue.h"
+#include <rv/da_monitor.h>
+#define PF_EXITING 0x00000004
+
+SEC("tp_btf/sched_dequeue_tp")
+int BPF_PROG(handle_sched_dequeue, struct task_struct *tsk, int cpu)
+{
+ if (!(BPF_CORE_READ(tsk, flags) & PF_EXITING))
+ da_handle_start_event(tsk, sched_dequeue_tqueue);
+ return 0;
+}
+
+SEC("tp_btf/sched_enqueue_tp")
+int BPF_PROG(handle_sched_enqueue, struct task_struct *tsk, int cpu)
+{
+ da_handle_event(tsk, sched_enqueue_tqueue);
+ return 0;
+}
+
+static struct rv_monitor rv_this = {
+ .enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "enqueue and dequeue tasks.";
diff --git a/tools/verification/rv/bpf_monitors/tqueue.h b/tools/verification/rv/bpf_monitors/tqueue.h
new file mode 100644
index 000000000000..1dac577d6bc4
--- /dev/null
+++ b/tools/verification/rv/bpf_monitors/tqueue.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of tqueue automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME tqueue
+
+enum states_tqueue {
+ dequeued_tqueue,
+ enqueued_tqueue,
+ state_max_tqueue,
+};
+
+#define INVALID_STATE state_max_tqueue
+
+enum events_tqueue {
+ sched_dequeue_tqueue,
+ sched_enqueue_tqueue,
+ event_max_tqueue,
+};
+
+struct automaton_tqueue {
+ char state_names[state_max_tqueue][32];
+ char event_names[event_max_tqueue][32];
+ unsigned char function[state_max_tqueue][event_max_tqueue];
+ unsigned char initial_state;
+ bool final_states[state_max_tqueue];
+};
+
+static const struct automaton_tqueue automaton_tqueue = {
+ .state_names = {
+ "dequeued",
+ "enqueued",
+ },
+ .event_names = {
+ "sched_dequeue",
+ "sched_enqueue",
+ },
+ .function = {
+ { INVALID_STATE, enqueued_tqueue },
+ { dequeued_tqueue, INVALID_STATE },
+ },
+ .initial_state = dequeued_tqueue,
+ .final_states = { 1, 0 },
+};
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 10/15] tools/rv: Add BPF monitors
2026-10-01 15:20 ` [PATCH v2 10/15] tools/rv: Add BPF monitors Gabriele Monaco
@ 2026-10-02 0:43 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:43 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 9742 bytes --]
> tools/rv: Add BPF monitors
TL;DR:
- Possible rv build breakage: requires host kernel BTF, old libbpf/clang may
lack likely() or -mcpu=v3
- Intentionally broken nohz monitor gets installed, changelog omits this
- O= builds write into source tree; install wildcard evaluated too early
> tools/rv: Add BPF monitors
>
> Add the code for 2 initial BPF monitors, both DA (the only currently
> supported type):
>
> * queue: per-task monitor stating tasks alternate enqueue and dequeue.
> * nohz: per-cpu monitor stating the scheduler tick don't run when
> stopped and can run only after being resumed.
>
> BPF monitors can include the in-kernel da_monitor.h, which is now
> adapted to share as much common code and conditionally compile what
> needs to be BPF or kernel specific.
>
> Integrate BPF monitor building into main rv Makefile, if all tools are
> available on the system.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
The changelog describes nohz as a monitor for a valid property, but nohz.c
says the opposite:
tools/verification/rv/bpf_monitors/nohz.c:
/*
* This monitor is broken on purpose to test errors, sched_tick can run with
* stopped ticks for one last time (deferred tick reprogram).
...
*/
Should the changelog say that nohz is an intentionally broken test monitor?
The new install rule in Makefile.rv copies every bpf_monitors/*.o, so 'rv
list' would show nohz to users as a normal monitor that is known to report
false violations. A later patch in this series (f5a3c821fa88 "tools/rv: Add
reactors support to BPF monitors") adds a panic reactor, so 'rv mon nohz -r
panic' would panic on a known false positive.
Should nohz be kept out of the installed set or clearly marked as a test
monitor?
Also, the first monitor is called 'queue' in the message but 'tqueue' in the
code (MONITOR_NAME tqueue, tqueue.dot, tqueue.c).
> diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
> index 7f0bdfd7cce3..ee1cec73ec85 100644
> --- a/include/rv/da_monitor.h
> +++ b/include/rv/da_monitor.h
[ ... ]
> @@ -77,6 +82,9 @@ static struct rv_monitor rv_this;
> #define da_id_type int
> #endif
>
> +#ifdef __BPF__
> +#include "da_monitor_bpf.h"
> +#else
> static void react(enum states curr_state, enum events event)
Can the BPF build find likely() and unlikely()?
The helpers in da_monitor.h that are now shared with BPF use them, for
example in da_monitor_enabled():
if (unlikely(!rv_monitoring_on()))
return 0;
The same applies to da_monitor_handling_event(), da_event() and the
__da_handle_start*_event() helpers, plus the per-task and per-obj helpers in
da_monitor_bpf.h.
Nothing in the BPF include path defines these macros. They are not in
da_monitor_bpf.h or bpf_atomic.h, and vmlinux.h only has types.
BPF_INCLUDES doesn't point at tools/lib, so <bpf/bpf_helpers.h> comes from
the system libbpf, and libbpf only gained likely()/unlikely() in commit
dafae1ae2ad3 ("libbpf: Add likely/unlikely macros and use them in
selftests").
With libbpf 1.5 or older headers, compiling bpf_monitors/*.c fails with
'call to undeclared function unlikely'. The libbpf feature test only checks
that libbpf links (bpf_prog_load), so BUILD_BPF_OBJS stays 1 and a plain
'make' of rv breaks.
Could da_monitor_bpf.h provide '#ifndef likely' fallbacks, or could
Makefile.config check the libbpf version before enabling BUILD_BPF_OBJS?
No later patch in the series addresses this.
[ ... ]
> diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
> index 0a8c7a656f7f..bb81593acd71 100644
> --- a/tools/verification/rv/Makefile
> +++ b/tools/verification/rv/Makefile
[ ... ]
> @@ -68,19 +72,51 @@ static: $(RV_IN)
> $(eval LDFLAGS += -static)
> $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
>
> +BPF_DIR := bpf_monitors
> +BPF_SOURCES := $(wildcard $(BPF_DIR)/*.c)
> +BPF_OBJECTS := $(BPF_SOURCES:.c=.o)
> +VMLINUX_H := $(BPF_DIR)/vmlinux.h
> +
> +BPF_INCLUDES := -I$(srctree)/include/uapi
> +BPF_INCLUDES += -I$(srctree)/include
> +BPF_INCLUDES += -I$(BPF_DIR)
> +
> +BPF_CFLAGS := -g -O2 -target bpf -MMD -MP $(BPF_INCLUDES)
> +BPF_CFLAGS += -Wall -Wno-unused-function -Wno-missing-declarations
Do the BPF monitors need -mcpu=v3 here?
The shared da_monitor.h code that is now built for BPF calls try_cmpxchg()
from da_event():
if (likely(try_cmpxchg(&da_mon->curr_state, &curr_state, next_state))) {
bpf_atomic.h implements try_cmpxchg() with __sync_val_compare_and_swap() on
the 32-bit 'unsigned int curr_state' of struct da_monitor.
Documentation/bpf/clang-notes.rst says:
Clang can generate atomic instructions by default when -mcpu=v3 is
enabled. If a lower version for -mcpu is set, the only atomic
instruction Clang can generate is BPF_ADD *without* BPF_FETCH.
A clang whose default BPF CPU is older than v3 (LLVM before 20 defaulted to
the v1 'generic' CPU) therefore cannot compile nohz.c or tqueue.c. Both
reach da_event() through da_handle_start_event()/da_handle_event() ->
__da_handle_event_common().
Those clang versions still pass the clang-bpf-co-re feature test, so
BUILD_BPF_OBJS stays 1, 'all: bpf' is added, and a plain 'make' of
tools/verification/rv fails where it succeeded before this patch.
Other in-tree BPF tools pass the flag explicitly, for example
tools/sched_ext/Makefile uses '-O2 -mcpu=v3'. Could -mcpu=v3 be added to
BPF_CFLAGS?
No later patch in the series changes this.
> +
> +VMLINUX := ../../../vmlinux
> +VMLINUX_BTF ?= $(if $(wildcard $(VMLINUX)) , $(VMLINUX), /sys/kernel/btf/vmlinux)
> +
> +$(VMLINUX_H): $(VMLINUX_BTF)
> + $(QUIET_GEN)$(SYSTEM_BPFTOOL) btf dump file $< format c > $@
Does the default rv build now depend on BTF from either the in-tree vmlinux
or the build host's running kernel, with that BTF also needing to contain
the RV types?
Makefile.config doesn't check either. The bpftool-btf feature test only
runs 'bpftool btf help' and clang-bpf-co-re only checks the compiler, so
BUILD_BPF_OBJS stays 1 and 'all: bpf' is added whenever the tools are
installed.
Failure cases for 'make -C tools/verification/rv':
- No ../../../vmlinux and no /sys/kernel/btf/vmlinux (for example a chroot
or container build, or a host kernel without CONFIG_DEBUG_INFO_BTF): make
stops with "No rule to make target '/sys/kernel/btf/vmlinux', needed by
'bpf_monitors/vmlinux.h'".
- The BTF source is a kernel without CONFIG_RV: struct rv_monitor, struct
da_monitor and union rv_task_monitor are missing from vmlinux.h, so
'static struct rv_monitor rv_this;' in da_monitor.h and the map value
types in da_monitor_bpf.h fail to compile.
- The in-tree vmlinux was built without BTF: bpftool fails, but '> $@' has
already created bpf_monitors/vmlinux.h. There is no .DELETE_ON_ERROR, so
the empty header stays newer than vmlinux and later make runs keep failing
until 'make clean'.
Before this patch, building rv did not depend on the build host's kernel at
all. This also seems to contradict the commit message ("if all tools are
available on the system") and the Makefile.config comment that RV can run
without the monitors.
Should the BTF input (and possibly the RV types in it) be checked, falling
back to BUILD_BPF_OBJS=0 the way the missing clang/bpftool cases already
are?
No later patch in the series addresses this.
> +
> +$(BPF_DIR)/%.o: $(BPF_DIR)/%.c $(VMLINUX_H)
> + $(QUIET_CLANG)$(CLANG) $(BPF_CFLAGS) -c $< -o $@
> + $(Q)$(LLVM_STRIP) -g $@
> + $(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
Should these rules honour OUTPUT like the rest of this Makefile does (RV :=
$(OUTPUT)rv)?
With 'make O=/some/dir' or 'make -C tools O=... rv', the rv binary and its
objects go to $(OUTPUT), but bpf_monitors/vmlinux.h, *.o and *.d are still
written into the source tree. The build now writes into srctree and fails
on a read-only source tree.
The objects also end up where rv cannot find them. bpf_fill_base_paths() in
src/bpf_monitor.c overwrites the "." search entry with
dirname(/proc/self/exe), i.e. $(OUTPUT), so rv looks for
$(OUTPUT)/bpf_monitors, which doesn't exist. Freshly built monitors are not
found in an O= build until they are installed.
Could the BPF objects and vmlinux.h be placed under $(OUTPUT)bpf_monitors/,
with the install rule updated to match?
[ ... ]
> diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
> index 2497fb96c83d..418748bc3aa1 100644
> --- a/tools/verification/rv/Makefile.rv
> +++ b/tools/verification/rv/Makefile.rv
[ ... ]
> @@ -33,12 +33,17 @@ INSTALL := install
[ ... ]
> install: doc_install
> $(Q)$(MKDIR) -p $(DESTDIR)$(BINDIR)
> $(call QUIET_INSTALL,rv)$(INSTALL) $(OUTPUT)rv -m 755 $(DESTDIR)$(BINDIR)
> $(Q)@$(STRIP) $(DESTDIR)$(BINDIR)/rv
> +ifneq ($(wildcard bpf_monitors/*.o),)
> + $(Q)$(MKDIR) -p $(DESTDIR)$(BPF_MON_DIR)
> + $(call QUIET_INSTALL,bpf_monitors)$(INSTALL) bpf_monitors/*.o -m 644 $(DESTDIR)$(BPF_MON_DIR)
> +endif
Is this ifneq/$(wildcard) check evaluated too early?
It runs when Makefile.rv is parsed (it is included from Makefile), not when
the install recipe runs. In a single invocation such as 'make all install'
(or 'make bpf install') on a clean tree, the wildcard is empty at parse
time, so the BPF install lines are dropped from the recipe. The rv binary
is installed but the monitors that were just built are silently skipped.
Would a shell-level test inside the recipe, for example 'if ls
bpf_monitors/*.o >/dev/null 2>&1; then ...', be more robust?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for BPF monitors
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (9 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 10/15] tools/rv: Add BPF monitors Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 12/15] tools/rv: Add reactors support to " Gabriele Monaco
` (3 subsequent siblings)
14 siblings, 0 replies; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
BPF atomic macros require CONFIG_X86_64 to determine barrier
instructions. Reading this from the system Kconfig can fail if the
configuration is missing or in an unexpected location, causing the
loading phase to fail.
Since the rv tool is compiled for the target architecture, the
architecture is known at compile-time. Define the CONFIG_X86_64 value
statically via the libbpf open options to bypass Kconfig dependency and
ensure reliable loading.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/src/bpf_monitor.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index 9a3f00febfeb..d43ee75d3904 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -719,7 +719,16 @@ static struct bpf_object *open_bpf_monitor(const char *path, struct bpf_monitor_
struct bpf_object *obj = NULL;
int res;
- obj = bpf_object__open_file(path, NULL);
+ LIBBPF_OPTS(bpf_object_open_opts, opts,
+ /* Define statically as arch is known, Kconfig may not be available */
+#ifdef __x86_64__
+ .kconfig = "CONFIG_X86_64=y\n",
+#else
+ .kconfig = "CONFIG_X86_64=n\n",
+#endif
+ );
+
+ obj = bpf_object__open_file(path, &opts);
if (!obj) {
err_msg("bpf: error opening object: %s\n", strerror(errno));
return NULL;
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* [PATCH v2 12/15] tools/rv: Add reactors support to BPF monitors
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (10 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 11/15] tools/rv: Define CONFIG_X86_64 statically for " Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:43 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 13/15] verification/rvgen: Add support for " Gabriele Monaco
` (2 subsequent siblings)
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
The BPF implementation of RV monitors cannot use standard reactors in
the kernel and currently reactions are skipped.
Add rv_react() to build the printk format using BPF macros and pass that
to a BPF function with a single message parameter, since BPF doesn't
support variable arguments.
This function is defined as weak, so the rv tool can link a different
implementation on top at load time.
Implement a printk reactor that is simply reimplementing this function
passing the message to bpf_printk().
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
include/rv/da_monitor.h | 4 +-
tools/verification/rv/Makefile | 12 ++-
tools/verification/rv/Makefile.rv | 5 +
.../rv/bpf_monitors/da_monitor_bpf.h | 12 ++-
tools/verification/rv/bpf_reactors/.gitignore | 2 +
tools/verification/rv/bpf_reactors/panic.c | 15 +++
tools/verification/rv/bpf_reactors/printk.c | 13 +++
tools/verification/rv/src/bpf_monitor.c | 102 +++++++++++++++++-
8 files changed, 157 insertions(+), 8 deletions(-)
create mode 100644 tools/verification/rv/bpf_reactors/.gitignore
create mode 100644 tools/verification/rv/bpf_reactors/panic.c
create mode 100644 tools/verification/rv/bpf_reactors/printk.c
diff --git a/include/rv/da_monitor.h b/include/rv/da_monitor.h
index ee1cec73ec85..28cff26aecfe 100644
--- a/include/rv/da_monitor.h
+++ b/include/rv/da_monitor.h
@@ -84,7 +84,8 @@ static struct rv_monitor rv_this;
#ifdef __BPF__
#include "da_monitor_bpf.h"
-#else
+#endif
+
static void react(enum states curr_state, enum events event)
{
rv_react(&rv_this,
@@ -93,7 +94,6 @@ static void react(enum states curr_state, enum events event)
model_get_event_name(event),
model_get_state_name(curr_state));
}
-#endif
/*
* da_monitor_reset_state - reset a monitor and setting it to init state
diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
index bb81593acd71..44b0e3af1488 100644
--- a/tools/verification/rv/Makefile
+++ b/tools/verification/rv/Makefile
@@ -73,8 +73,11 @@ static: $(RV_IN)
$(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
BPF_DIR := bpf_monitors
+BPF_REACT_DIR := bpf_reactors
BPF_SOURCES := $(wildcard $(BPF_DIR)/*.c)
BPF_OBJECTS := $(BPF_SOURCES:.c=.o)
+BPF_REACT_SOURCES := $(wildcard $(BPF_REACT_DIR)/*.c)
+BPF_REACT_OBJECTS := $(BPF_REACT_SOURCES:.c=.o)
VMLINUX_H := $(BPF_DIR)/vmlinux.h
BPF_INCLUDES := -I$(srctree)/include/uapi
@@ -95,9 +98,14 @@ $(BPF_DIR)/%.o: $(BPF_DIR)/%.c $(VMLINUX_H)
$(Q)$(LLVM_STRIP) -g $@
$(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
--include $(BPF_SOURCES:.c=.d)
+$(BPF_REACT_DIR)/%.o: $(BPF_REACT_DIR)/%.c $(VMLINUX_H)
+ $(QUIET_CLANG)$(CLANG) $(BPF_CFLAGS) -c $< -o $@
+ $(Q)$(LLVM_STRIP) -g $@
+ $(Q)$(LLVM_OBJCOPY) --remove-section=.rel.rodata $@
+
+-include $(BPF_SOURCES:.c=.d) $(BPF_REACT_SOURCES:.c=.d)
-bpf: $(BPF_OBJECTS)
+bpf: $(BPF_OBJECTS) $(BPF_REACT_OBJECTS)
rv.%: fixdep FORCE
make -f $(srctree)/tools/build/Makefile.build dir=. $@
diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
index 418748bc3aa1..3ac329851914 100644
--- a/tools/verification/rv/Makefile.rv
+++ b/tools/verification/rv/Makefile.rv
@@ -34,6 +34,7 @@ MKDIR := mkdir
STRIP := strip
BINDIR := /usr/bin
BPF_MON_DIR := /usr/share/rv/bpf_monitors
+BPF_REACT_DIR := /usr/share/rv/bpf_reactors
.PHONY: install
install: doc_install
@@ -44,6 +45,10 @@ ifneq ($(wildcard bpf_monitors/*.o),)
$(Q)$(MKDIR) -p $(DESTDIR)$(BPF_MON_DIR)
$(call QUIET_INSTALL,bpf_monitors)$(INSTALL) bpf_monitors/*.o -m 644 $(DESTDIR)$(BPF_MON_DIR)
endif
+ifneq ($(wildcard bpf_reactors/*.o),)
+ $(Q)$(MKDIR) -p $(DESTDIR)$(BPF_REACT_DIR)
+ $(call QUIET_INSTALL,bpf_reactors)$(INSTALL) bpf_reactors/*.o -m 644 $(DESTDIR)$(BPF_REACT_DIR)
+endif
.PHONY: doc doc_clean doc_install
doc:
diff --git a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
index 12f0f78b9c9d..a37d11fef6f1 100644
--- a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
+++ b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
@@ -17,7 +17,6 @@
#include "bpf_atomic.h"
/* BPF monitors don't support these */
-#define rv_react(...) do {} while (0)
#define trace_rv_retries_error(...) do {} while (0)
#define pr_warn(fmt, ...) bpf_printk(fmt, ##__VA_ARGS__)
#define rv_monitoring_on() 1
@@ -75,8 +74,15 @@ static const struct rv_trace_entry __used *_btf_trace;
static inline void da_monitor_reset(struct da_monitor *da_mon);
-/* BPF monitors do not use reactors */
-static inline void react(enum states curr_state, enum events event) { }
+__noinline __weak void bpf_rv_react(char *msg) { }
+
+#define rv_react(mon, fmt, ...) \
+ ({ \
+ char ___msg[256]; \
+ \
+ if (BPF_SNPRINTF(___msg, sizeof(___msg), fmt, ##__VA_ARGS__) > 0) \
+ bpf_rv_react(___msg); \
+ })
/*
* BPF monitor implementations
diff --git a/tools/verification/rv/bpf_reactors/.gitignore b/tools/verification/rv/bpf_reactors/.gitignore
new file mode 100644
index 000000000000..e5add9141ef5
--- /dev/null
+++ b/tools/verification/rv/bpf_reactors/.gitignore
@@ -0,0 +1,2 @@
+*.d
+vmlinux.h
diff --git a/tools/verification/rv/bpf_reactors/panic.c b/tools/verification/rv/bpf_reactors/panic.c
new file mode 100644
index 000000000000..775f91a300f0
--- /dev/null
+++ b/tools/verification/rv/bpf_reactors/panic.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+void bpf_rv_react(char *msg)
+{
+ struct pt_regs regs = { 0 };
+
+ crash_kexec(®s);
+}
+
+char LICENSE[] SEC("license") = "GPL";
+static char DESCRIPTION[] SEC(".rodata.description") =
+ "panic the system if an exception is found.";
diff --git a/tools/verification/rv/bpf_reactors/printk.c b/tools/verification/rv/bpf_reactors/printk.c
new file mode 100644
index 000000000000..580410d2a1fc
--- /dev/null
+++ b/tools/verification/rv/bpf_reactors/printk.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+void bpf_rv_react(char *msg)
+{
+ bpf_printk("%s", msg);
+}
+
+char LICENSE[] SEC("license") = "GPL";
+static char DESCRIPTION[] SEC(".rodata.description") =
+ "prints the exception msg to the trace buffer.";
diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
index d43ee75d3904..35bd1f40a109 100644
--- a/tools/verification/rv/src/bpf_monitor.c
+++ b/tools/verification/rv/src/bpf_monitor.c
@@ -5,6 +5,7 @@
* Copyright (C) 2026 Red Hat Inc, Gabriele Monaco <gmonaco@redhat.com>
*/
+#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -14,6 +15,7 @@
#include <libgen.h>
#include <errno.h>
#include <inttypes.h>
+#include <sys/mman.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <bpf/btf.h>
@@ -36,6 +38,7 @@ static char bpf_base_paths[][MAX_PATH] = {
#define MAX_ENUMS 64
#define MAX_LINKS 16
#define PROG_ENABLE_MON "enable_monitor"
+#define BPF_REACTOR "bpf_rv_react"
#define RV_TRACE_STRUCT "rv_trace_entry"
#define RV_TRACE_TYPE_ENUM "rv_trace_type"
@@ -676,12 +679,31 @@ static struct ring_buffer *bpf_setup_ring_buffer(struct bpf_object *obj,
return rb;
}
+static void list_reactor_action(const char *name, struct bpf_object *obj)
+{
+ const struct btf *btf = bpf_object__btf(obj);
+
+ if (btf__find_by_name_kind(btf, BPF_REACTOR, BTF_KIND_FUNC) >= 0)
+ fprintf(stderr, "%s ", name);
+}
+
+/*
+ * list_reactors_from_path - list reactors from a specific base path
+ */
+static void list_reactors_from_path(const char *base_path)
+{
+ bpf_object_iterate_path(base_path, "bpf_reactors", list_reactor_action);
+}
+
/*
* bpf_usage_print_reactors - print available BPF reactors
*/
void bpf_usage_print_reactors(void)
{
- fprintf(stderr, " available BPF reactors: nop\n");
+ fprintf(stderr, " available BPF reactors: nop ");
+ for (int i = 0; bpf_base_paths[i][0]; i++)
+ list_reactors_from_path(bpf_base_paths[i]);
+ fprintf(stderr, "\n");
}
/*
@@ -720,6 +742,7 @@ static struct bpf_object *open_bpf_monitor(const char *path, struct bpf_monitor_
int res;
LIBBPF_OPTS(bpf_object_open_opts, opts,
+ .object_name = ctx->monitor_name,
/* Define statically as arch is known, Kconfig may not be available */
#ifdef __x86_64__
.kconfig = "CONFIG_X86_64=y\n",
@@ -795,6 +818,65 @@ static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
return enable_mon_fd;
}
+static int find_bpf_reactor(const char *reactor_name, char *path_out, size_t path_len)
+{
+ return find_bpf_file("bpf_reactors", reactor_name, path_out, path_len);
+}
+
+/*
+ * link_bpf_reactor - link the reactor function to the monitor
+ *
+ * Reactors are objects defining the BPF_REACTOR function, link that over the
+ * weak definition present in the monitor and return a file descriptor to the
+ * final linked object in memory.
+ *
+ * Returns memfd of final object on success, -1 on error.
+ */
+static int link_bpf_reactor(const char *monitor_path, const char *reactor_path)
+{
+ struct bpf_linker *linker = NULL;
+ int memfd = -1;
+ int err = 0;
+
+ memfd = memfd_create("linked_bpf", 0);
+ if (memfd < 0) {
+ err_msg("bpf: failed to create memfd: %s\n", strerror(errno));
+ return -1;
+ }
+
+ linker = bpf_linker__new_fd(memfd, NULL);
+ if (!linker) {
+ err_msg("bpf: failed to create BPF linker\n");
+ goto out;
+ }
+
+ err = bpf_linker__add_file(linker, monitor_path, NULL);
+ if (err) {
+ err_msg("bpf: failed to add monitor file to linker: %s\n", strerror(-err));
+ goto out;
+ }
+
+ err = bpf_linker__add_file(linker, reactor_path, NULL);
+ if (err) {
+ err_msg("bpf: failed to add reactor file to linker: %s\n", strerror(-err));
+ goto out;
+ }
+
+ err = bpf_linker__finalize(linker);
+ if (err) {
+ err_msg("bpf: failed to finalize BPF linker: %s\n", strerror(-err));
+ goto out;
+ }
+
+ bpf_linker__free(linker);
+ return memfd;
+
+out:
+ bpf_linker__free(linker);
+ close(memfd);
+ return -1;
+}
+
/*
* bpf_run_monitor - load and run a BPF monitor
*
@@ -808,6 +890,7 @@ int bpf_run_monitor(char *monitor_name, int argc, char **argv)
struct bpf_object *obj = NULL;
int res, link_count = 0, enable_mon_fd, retval = -1;
char monitor_path[MAX_PATH];
+ int memfd = -1;
libbpf_set_print(libbpf_print_fn);
bpf_fill_base_paths();
@@ -830,8 +913,25 @@ int bpf_run_monitor(char *monitor_name, int argc, char **argv)
strncpy(ctx.monitor_name, monitor_name, sizeof(ctx.monitor_name) - 1);
+ if (config.reactor && strcmp(config.reactor, "nop")) {
+ char reactor_path[MAX_PATH];
+
+ if (!find_bpf_reactor(config.reactor, reactor_path, sizeof(reactor_path))) {
+ mon_usage(1, monitor_name,
+ "bpf: failed to set %s reactor, is it available?",
+ config.reactor);
+ goto cleanup;
+ }
+
+ memfd = link_bpf_reactor(monitor_path, reactor_path);
+ if (memfd < 0)
+ goto cleanup;
+
+ snprintf(monitor_path, sizeof(monitor_path), "/proc/self/fd/%d", memfd);
+ }
obj = open_bpf_monitor(monitor_path, &ctx);
+ close(memfd);
if (!obj)
goto cleanup;
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 12/15] tools/rv: Add reactors support to BPF monitors
2026-10-01 15:20 ` [PATCH v2 12/15] tools/rv: Add reactors support to " Gabriele Monaco
@ 2026-10-02 0:43 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:43 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 9098 bytes --]
> tools/rv: Add reactors support to BPF monitors
TL;DR:
- bpf_reactors install dir variable clashes, so make install may fail or
misplace reactors
- void global bpf_rv_react() may break monitor loading on older kernels
- bpf_linker__new_fd() may break builds on libbpf before 1.6
- unused DESCRIPTION nit
> diff --git a/tools/verification/rv/Makefile b/tools/verification/rv/Makefile
> index bb81593acd71..44b0e3af1488 100644
> --- a/tools/verification/rv/Makefile
> +++ b/tools/verification/rv/Makefile
> @@ -73,8 +73,11 @@ static: $(RV_IN)
> $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RV)-static $(RV_IN) $(EXTLIBS)
>
> BPF_DIR := bpf_monitors
> +BPF_REACT_DIR := bpf_reactors
> BPF_SOURCES := $(wildcard $(BPF_DIR)/*.c)
[ ... ]
> diff --git a/tools/verification/rv/Makefile.rv b/tools/verification/rv/Makefile.rv
> index 418748bc3aa1..3ac329851914 100644
> --- a/tools/verification/rv/Makefile.rv
> +++ b/tools/verification/rv/Makefile.rv
> @@ -34,6 +34,7 @@ MKDIR := mkdir
> STRIP := strip
> BINDIR := /usr/bin
> BPF_MON_DIR := /usr/share/rv/bpf_monitors
> +BPF_REACT_DIR := /usr/share/rv/bpf_reactors
>
> .PHONY: install
> install: doc_install
> @@ -44,6 +45,10 @@ ifneq ($(wildcard bpf_monitors/*.o),)
> $(Q)$(MKDIR) -p $(DESTDIR)$(BPF_MON_DIR)
> $(call QUIET_INSTALL,bpf_monitors)$(INSTALL) bpf_monitors/*.o -m 644 $(DESTDIR)$(BPF_MON_DIR)
> endif
> +ifneq ($(wildcard bpf_reactors/*.o),)
> + $(Q)$(MKDIR) -p $(DESTDIR)$(BPF_REACT_DIR)
> + $(call QUIET_INSTALL,bpf_reactors)$(INSTALL) bpf_reactors/*.o -m 644 $(DESTDIR)$(BPF_REACT_DIR)
> +endif
Is BPF_REACT_DIR defined twice with different meanings? Makefile.rv sets it
to the install destination (/usr/share/rv/bpf_reactors), but Makefile
includes Makefile.rv first and then redefines it as the source directory:
include Makefile.rv
...
BPF_REACT_DIR := bpf_reactors
make expands recipe lines after the whole makefile has been parsed, so the
install recipe sees the last value, "bpf_reactors". The monitors are not
affected because they use two different names (BPF_DIR for the source,
BPF_MON_DIR for the destination).
Once the reactors are built (the default whenever clang and bpftool are
found), the wildcard check is true and make install runs:
mkdir -p bpf_reactors
install bpf_reactors/*.o -m 644 bpf_reactors
With an empty DESTDIR this copies each file onto itself, which GNU install
rejects with "are the same file", so make install fails. With
DESTDIR=/tmp/pkg the reactors land in /tmp/pkgbpf_reactors instead of
/tmp/pkg/usr/share/rv/bpf_reactors. Either way the installed rv cannot find
reactors under the /usr/share/rv base path that bpf_monitor.c searches.
Would renaming one of the variables (for example BPF_REACT_INST_DIR,
matching the BPF_DIR/BPF_MON_DIR split) fix this? I didn't see a later patch
in this series that changes Makefile or Makefile.rv.
> diff --git a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
> index 12f0f78b9c9d..a37d11fef6f1 100644
> --- a/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
> +++ b/tools/verification/rv/bpf_monitors/da_monitor_bpf.h
[ ... ]
> @@ -75,8 +74,15 @@ static const struct rv_trace_entry __used *_btf_trace;
>
> static inline void da_monitor_reset(struct da_monitor *da_mon);
>
> -/* BPF monitors do not use reactors */
> -static inline void react(enum states curr_state, enum events event) { }
> +__noinline __weak void bpf_rv_react(char *msg) { }
> +
> +#define rv_react(mon, fmt, ...) \
> + ({ \
> + char ___msg[256]; \
> + \
> + if (BPF_SNPRINTF(___msg, sizeof(___msg), fmt, ##__VA_ARGS__) > 0) \
> + bpf_rv_react(___msg); \
> + })
Can this break loading of every BPF monitor on older kernels?
bpf_rv_react() is a non-static (weak) function, so clang gives it
BTF_FUNC_GLOBAL linkage and the verifier checks it as a global subprog.
Every BPF monitor now calls it on the da_event() -> react() path, even with
no reactor or with -r nop.
Global subprogs returning void have only been accepted since commit
8446ded1e1a0 ("bpf: Allow void global functions in the verifier"). Before
that commit, btf_prepare_func_args() rejects them with:
Global function bpf_rv_react() doesn't return scalar. Only those are
supported.
So after this patch, monitors that only use long-standing attach points
(nohz, for example) fail to load on kernels older than that commit. Before
this patch they had no global subprog and did not have that requirement.
Would declaring the hook as "int bpf_rv_react(char *msg)", returning 0 in
the weak default and in the printk and panic reactors, keep the monitors
loadable on those kernels?
> diff --git a/tools/verification/rv/bpf_reactors/panic.c b/tools/verification/rv/bpf_reactors/panic.c
> new file mode 100644
> index 000000000000..775f91a300f0
> --- /dev/null
> +++ b/tools/verification/rv/bpf_reactors/panic.c
[ ... ]
> +char LICENSE[] SEC("license") = "GPL";
> +static char DESCRIPTION[] SEC(".rodata.description") =
> + "panic the system if an exception is found.";
> diff --git a/tools/verification/rv/bpf_reactors/printk.c b/tools/verification/rv/bpf_reactors/printk.c
> new file mode 100644
> index 000000000000..580410d2a1fc
> --- /dev/null
> +++ b/tools/verification/rv/bpf_reactors/printk.c
[ ... ]
> +char LICENSE[] SEC("license") = "GPL";
> +static char DESCRIPTION[] SEC(".rodata.description") =
> + "prints the exception msg to the trace buffer.";
Is the static DESCRIPTION in these two reactors ever used?
The monitors define DESCRIPTION as a non-static global (nohz.c, tqueue.c),
but here it has internal linkage and no "used" attribute. That triggers
-Wunused-variable under the -Wall in BPF_CFLAGS, and the optimizer can drop
it at -O2, so the reactor objects may end up with no .rodata.description
section.
Nothing in the series reads a reactor description either, since
list_reactor_action() in bpf_monitor.c prints only the name. Should the
variable be dropped, or made non-static like the monitors' if reactor
descriptions are meant to be listed?
> diff --git a/tools/verification/rv/src/bpf_monitor.c b/tools/verification/rv/src/bpf_monitor.c
> index d43ee75d3904..35bd1f40a109 100644
> --- a/tools/verification/rv/src/bpf_monitor.c
> +++ b/tools/verification/rv/src/bpf_monitor.c
[ ... ]
> @@ -795,6 +818,65 @@ static int attach_bpf_handlers(const char *monitor_name, struct bpf_object *obj,
> return enable_mon_fd;
> }
>
> +static int find_bpf_reactor(const char *reactor_name, char *path_out, size_t path_len)
> +{
> + return find_bpf_file("bpf_reactors", reactor_name, path_out, path_len);
> +}
> +
> +/*
> + * link_bpf_reactor - link the reactor function to the monitor
> + *
> + * Reactors are objects defining the BPF_REACTOR function, link that over the
> + * weak definition present in the monitor and return a file descriptor to the
> + * final linked object in memory.
> + *
> + * Returns memfd of final object on success, -1 on error.
> + */
> +static int link_bpf_reactor(const char *monitor_path, const char *reactor_path)
> +{
> + struct bpf_linker *linker = NULL;
> + int memfd = -1;
> + int err = 0;
> +
> + memfd = memfd_create("linked_bpf", 0);
> + if (memfd < 0) {
> + err_msg("bpf: failed to create memfd: %s\n", strerror(errno));
> + return -1;
> + }
> +
> + linker = bpf_linker__new_fd(memfd, NULL);
Does this raise the minimum libbpf version needed to build rv?
bpf_linker__new_fd() was added in libbpf 1.6 (it is in the LIBBPF_1.6.0
block of libbpf.map). rv builds against the system libbpf through
pkg-config, and the only libbpf gate is the feature test, which only
requires LIBBPF_MAJOR_VERSION >= 1:
#if !defined(LIBBPF_MAJOR_VERSION) || (LIBBPF_MAJOR_VERSION < 1)
#error At least libbpf 1.0 is required for Linux tools.
Before this patch, the newest libbpf API used by bpf_monitor.c was
bpf_map_get_info_by_fd() (LIBBPF_1.2.0). With libbpf 1.2 to 1.5 installed
(for example Ubuntu 24.04 ships 1.3, RHEL 9 ships 1.3/1.4, Debian 13 and
Fedora 42 ship 1.5), feature-libbpf still passes, so HAVE_LIBBPF is set and
bpf_monitor.c is compiled.
The build then fails: GCC 14 and later reject the implicit declaration of
bpf_linker__new_fd(), and older compilers stop at link time with an
undefined reference. On those systems 'make -C tools/verification/rv' used
to build rv with BPF monitor support and now fails outright, instead of
falling back to the "building without BPF monitor support" path.
Could this use bpf_linker__new() (LIBBPF_0.4.0) on a path such as
"/proc/self/fd/<memfd>" or a temporary file, or add a libbpf >= 1.6 version
check to Makefile.config, the way libtraceevent and libtracefs are already
handled there? I didn't see a later patch in this series that touches
tools/verification/rv/src/, the rv Makefiles or tools/build/feature/.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 13/15] verification/rvgen: Add support for BPF monitors
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (11 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 12/15] tools/rv: Add reactors support to " Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:25 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors Gabriele Monaco
2026-10-01 15:20 ` [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Add the -b flag to rvgen monitor to generate a bpf monitor.
By default the code is generated in a folder with the same name as the
model just like in-kernel monitors, although only the source and header
are meaningful here. Passing -a moves the sources to
tools/verification/rv/bpf_monitors/ where they can be built directly.
Currently BPF monitors are only supported for DA.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rvgen/__main__.py | 15 ++++++++---
tools/verification/rvgen/rvgen/dot2c.py | 15 ++++++++---
tools/verification/rvgen/rvgen/dot2k.py | 22 +++++++++++++---
tools/verification/rvgen/rvgen/generator.py | 26 ++++++++++++++-----
.../rvgen/rvgen/templates/dot2k/main_bpf.c | 21 +++++++++++++++
5 files changed, 80 insertions(+), 19 deletions(-)
create mode 100644 tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
index 246b43fa29f1..16ce9b512fbb 100644
--- a/tools/verification/rvgen/__main__.py
+++ b/tools/verification/rvgen/__main__.py
@@ -39,6 +39,8 @@ if __name__ == '__main__':
help="Monitor specification file")
monitor_parser.add_argument('-t', "--monitor_type", dest="monitor_type", required=True,
help=f"Available options: {', '.join(Monitor.monitor_types.keys())}")
+ monitor_parser.add_argument('-b', "--bpf", dest="bpf", action="store_true",
+ required=False, help="Generate a BPF monitor")
container_parser = subparsers.add_parser("container", parents=[parent_parser])
container_parser.add_argument('-n', "--model_name", dest="model_name", required=True)
@@ -50,6 +52,9 @@ if __name__ == '__main__':
params = parser.parse_args()
+ if params.subcmd == "monitor" and params.bpf and params.monitor_class != "da":
+ parser.error("BPF monitors (-b/--bpf) are only supported for deterministic automaton (-c da)")
+
try:
if params.subcmd == "monitor":
print(f"Opening and parsing the specification file {params.spec}")
@@ -80,7 +85,9 @@ if __name__ == '__main__':
print("Almost done, checklist")
if params.subcmd == "monitor":
print(f" - Edit the {monitor.name}/{monitor.name}.c to add the instrumentation")
- print(monitor.fill_tracepoint_tooltip())
- print(monitor.fill_makefile_tooltip())
- print(monitor.fill_kconfig_tooltip())
- print(monitor.fill_monitor_tooltip())
+ if not params.bpf:
+ print(monitor.fill_tracepoint_tooltip())
+ if not params.subcmd == "monitor" or not params.bpf:
+ print(monitor.fill_makefile_tooltip())
+ print(monitor.fill_kconfig_tooltip())
+ print(monitor.fill_monitor_tooltip())
diff --git a/tools/verification/rvgen/rvgen/dot2c.py b/tools/verification/rvgen/rvgen/dot2c.py
index 22938ce1bf6c..5be9fe44a19b 100644
--- a/tools/verification/rvgen/rvgen/dot2c.py
+++ b/tools/verification/rvgen/rvgen/dot2c.py
@@ -111,10 +111,17 @@ class Dot2c(Automata):
min_type = self.get_minimun_type()
buff = []
buff.append(f"struct {self.struct_automaton_def} {{")
- buff.append(f"\tchar *state_names[state_max{self.enum_suffix}];")
- buff.append(f"\tchar *event_names[event_max{self.enum_suffix}];")
- if self.is_hybrid_automata():
- buff.append(f"\tchar *env_names[env_max{self.enum_suffix}];")
+ if self.bpf:
+ # BPF struggles with non-fixed string pointers
+ buff.append(f"\tchar state_names[state_max{self.enum_suffix}][32];")
+ buff.append(f"\tchar event_names[event_max{self.enum_suffix}][32];")
+ if self.is_hybrid_automata():
+ buff.append(f"\tchar env_names[env_max{self.enum_suffix}][32];")
+ else:
+ buff.append(f"\tchar *state_names[state_max{self.enum_suffix}];")
+ buff.append(f"\tchar *event_names[event_max{self.enum_suffix}];")
+ if self.is_hybrid_automata():
+ buff.append(f"\tchar *env_names[env_max{self.enum_suffix}];")
buff.append(f"\t{min_type} function[state_max{self.enum_suffix}][event_max{self.enum_suffix}];")
buff.append(f"\t{min_type} initial_state;")
buff.append(f"\tbool final_states[state_max{self.enum_suffix}];")
diff --git a/tools/verification/rvgen/rvgen/dot2k.py b/tools/verification/rvgen/rvgen/dot2k.py
index fd3254ea5b4d..6600bfa0f20f 100644
--- a/tools/verification/rvgen/rvgen/dot2k.py
+++ b/tools/verification/rvgen/rvgen/dot2k.py
@@ -30,14 +30,19 @@ class dot2k(Monitor, Dot2c):
buff = [ self.monitor_type.upper() ]
buff += self._fill_timer_type()
if self.monitor_type == "per_obj":
- buff.append("typedef /* XXX: define the target type */ *monitor_target;")
+ pad = "_bpf" if self.bpf else ""
+ buff.append(f"typedef /* XXX: define the target type */ *monitor_target{pad};")
return "\n".join(buff)
def fill_tracepoint_handlers_skel(self) -> str:
buff = []
buff += self._fill_hybrid_definitions()
for event in self.events:
- buff.append(f"static void handle_{event}(void *data, /* XXX: fill header */)")
+ if self.bpf:
+ buff.append("SEC(/* XXX: tracepoint or other probe */)")
+ buff.append(f"int BPF_PROG(handle_{event}, /* XXX: fill header */)")
+ else:
+ buff.append(f"static void handle_{event}(void *data, /* XXX: fill header */)")
buff.append("{")
handle = "handle_event"
if self.is_start_event(event):
@@ -50,19 +55,28 @@ class dot2k(Monitor, Dot2c):
buff.append("\tstruct task_struct *p = /* XXX: how do I get p? */;")
buff.append(f"\tda_{handle}(p, {event}{self.enum_suffix});")
elif self.monitor_type == "per_obj":
+ pad = "_bpf" if self.bpf else ""
buff.append("\tint id = /* XXX: how do I get the id? */;")
- buff.append("\tmonitor_target t = /* XXX: how do I get t? */;")
+ buff.append(f"\tmonitor_target{pad} t = /* XXX: how do I get t? */;")
buff.append(f"\tda_{handle}(id, t, {event}{self.enum_suffix});")
else:
buff.append(f"\tda_{handle}({event}{self.enum_suffix});")
+ if self.bpf:
+ buff.append("\treturn 0;")
buff.append("}")
buff.append("")
if self.monitor_type == "per_obj":
buff.append("/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */")
- buff.append(f"static void handle_{self.cleanup_marker}(void *data, /* XXX: fill header */)")
+ if self.bpf:
+ buff.append("SEC(/* XXX: tracepoint or other probe */)")
+ buff.append(f"int BPF_PROG(handle_{self.cleanup_marker}, /* XXX: fill header */)")
+ else:
+ buff.append(f"static void handle_{self.cleanup_marker}(void *data, /* XXX: fill header */)")
buff.append("{")
buff.append("\tint id = /* XXX: how do I get the id? */;")
buff.append("\tda_destroy_storage(id);")
+ if self.bpf:
+ buff.append("\treturn 0;")
buff.append("}")
buff.append("")
return '\n'.join(buff)
diff --git a/tools/verification/rvgen/rvgen/generator.py b/tools/verification/rvgen/rvgen/generator.py
index 45e2bab26cb5..b7985ff84521 100644
--- a/tools/verification/rvgen/rvgen/generator.py
+++ b/tools/verification/rvgen/rvgen/generator.py
@@ -11,18 +11,22 @@ from pathlib import Path
class RVGenerator:
rv_dir = "kernel/trace/rv"
+ rv_tool_dir = "../../../tools/verification/rv/bpf_monitors/"
def __init__(self, extra_params={}):
self.name = extra_params.get("model_name")
self.parent = extra_params.get("parent")
+ self.bpf = extra_params.get("bpf")
self.abs_template_dir = \
Path(__file__).resolve().parent / "templates" / self.template_dir
- self.main_c = self._read_template_file("main.c")
+ self.main_c = self._read_template_file("main.c" if not self.bpf
+ else "main_bpf.c")
self.kconfig = self._read_template_file("Kconfig")
self.description = extra_params.get("description", self.name) or "auto-generated"
self.auto_patch = extra_params.get("auto_patch")
if self.auto_patch:
self._fill_rv_kernel_dir()
+ self.rv_tool_dir = (Path(self.rv_dir) / self.rv_tool_dir).resolve()
def _fill_rv_kernel_dir(self):
# find the kernel tree root relative to this file's location
@@ -191,6 +195,9 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
def __create_directory(self):
path = Path(self.name)
if self.auto_patch:
+ if self.bpf:
+ # no directory for BPF monitors
+ return
path = Path(self.rv_dir) / "monitors" / path
path.mkdir(exist_ok=True)
@@ -202,6 +209,8 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
path = Path(self.name) / file_name
if self.auto_patch:
path = Path(self.rv_dir) / "monitors" / self.name / file_name
+ if self.bpf:
+ path = Path(self.rv_tool_dir) / file_name
self.__write_file(path, content)
def print_files(self):
@@ -216,8 +225,9 @@ obj-$(CONFIG_RV_MON_{name_up}) += monitors/{name}/{name}.o
path = f"{self.name}.h"
self._create_file(path, model_h)
- kconfig = self.fill_kconfig()
- self._create_file("Kconfig", kconfig)
+ if not self.bpf:
+ kconfig = self.fill_kconfig()
+ self._create_file("Kconfig", kconfig)
class Monitor(RVGenerator):
@@ -225,7 +235,8 @@ class Monitor(RVGenerator):
def __init__(self, extra_params={}):
super().__init__(extra_params)
- self.trace_h = self._read_template_file("trace.h")
+ if not self.bpf:
+ self.trace_h = self._read_template_file("trace.h")
def fill_trace_h(self):
trace_h = self.trace_h
@@ -245,6 +256,7 @@ class Monitor(RVGenerator):
def print_files(self):
super().print_files()
- trace_h = self.fill_trace_h()
- path = f"{self.name}_trace.h"
- self._create_file(path, trace_h)
+ if not self.bpf:
+ trace_h = self.fill_trace_h()
+ path = f"{self.name}_trace.h"
+ self._create_file(path, trace_h)
diff --git a/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c b/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
new file mode 100644
index 000000000000..1a83f3a2159a
--- /dev/null
+++ b/tools/verification/rvgen/rvgen/templates/dot2k/main_bpf.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_%%MONITOR_TYPE%%
+#include "%%MODEL_NAME%%.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+%%TRACEPOINT_HANDLERS_SKEL%%
+static struct rv_monitor rv_this = {
+ .enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "%%DESCRIPTION%%";
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 13/15] verification/rvgen: Add support for BPF monitors
2026-10-01 15:20 ` [PATCH v2 13/15] verification/rvgen: Add support for " Gabriele Monaco
@ 2026-10-02 0:25 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:25 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2352 bytes --]
> verification/rvgen: Add support for BPF monitors
TL;DR: In rvgen, with -b and -a, the printed messages and checklist point to a
nonexistent {monitor.name}/ directory and never say the sources went to
bpf_monitors/. Should they print that location?
> diff --git a/tools/verification/rvgen/__main__.py b/tools/verification/rvgen/__main__.py
> --- a/tools/verification/rvgen/__main__.py
> +++ b/tools/verification/rvgen/__main__.py
[ ... ]
> @@ -80,7 +85,9 @@ if __name__ == '__main__':
> print("Almost done, checklist")
> if params.subcmd == "monitor":
> print(f" - Edit the {monitor.name}/{monitor.name}.c to add the instrumentation")
> - print(monitor.fill_tracepoint_tooltip())
> - print(monitor.fill_makefile_tooltip())
> - print(monitor.fill_kconfig_tooltip())
> - print(monitor.fill_monitor_tooltip())
> + if not params.bpf:
> + print(monitor.fill_tracepoint_tooltip())
> + if not params.subcmd == "monitor" or not params.bpf:
> + print(monitor.fill_makefile_tooltip())
> + print(monitor.fill_kconfig_tooltip())
> + print(monitor.fill_monitor_tooltip())
With -b and -a, does this checklist point to a path that is never created?
RVGenerator.__create_directory() returns early for this case:
if self.auto_patch:
if self.bpf:
# no directory for BPF monitors
return
and RVGenerator._create_file() writes the files to self.rv_tool_dir, which
resolves to tools/verification/rv/bpf_monitors/<name>.c in the kernel tree:
if self.bpf:
path = Path(self.rv_tool_dir) / file_name
But __main__ still prints "Writing the monitor into the directory
{monitor.name}" and "Edit the {monitor.name}/{monitor.name}.c to add the
instrumentation", and no {monitor.name}/ directory exists anywhere.
Since fill_monitor_tooltip() is now skipped for BPF monitors, which on the
in-kernel -a path prints "Monitor created in {rv_dir}/monitors/{name}",
nothing in the output tells the user where the generated sources were
written. Should the BPF -a case print the bpf_monitors/ location instead?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (12 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 13/15] verification/rvgen: Add support for " Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
2026-10-02 0:43 ` bot+bpf-ci
2026-10-01 15:20 ` [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b Gabriele Monaco
14 siblings, 1 reply; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Add selftest cases for the rv userspace tool BPF monitors, test BPF
monitors listing and functionality, including traces and reactors.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
tools/verification/rv/tests/rv_list.t | 6 ++-
tools/verification/rv/tests/rv_mon.t | 61 ++++++++++++++++++++++++++-
2 files changed, 65 insertions(+), 2 deletions(-)
diff --git a/tools/verification/rv/tests/rv_list.t b/tools/verification/rv/tests/rv_list.t
index 201af33a52cc..3c94f2af2109 100644
--- a/tools/verification/rv/tests/rv_list.t
+++ b/tools/verification/rv/tests/rv_list.t
@@ -6,6 +6,8 @@ test_begin
set_timeout 30s
RVDIR=/sys/kernel/tracing/rv/
+# only works when running in the kernel tree
+RVTOOL=$(dirname "$RV")
# Help and basic tests
check "verify help page" \
@@ -17,13 +19,15 @@ check "verify list subcommand help" \
all_nested=$(grep : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
all_non_nested=$(grep -v : $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
sched_monitors=$(grep sched: $RVDIR/available_monitors | cut -d: -f2 | paste -s | sed 's/\t/\\|/g')
+all_bpf=$(find "$RVTOOL/bpf_monitors" -name "*.o" -exec basename \{} .o \; | paste -s | sed 's/\t/\\|/g')
description_state="[[:space:]]\+[[:print:]]\+\[\(OFF\|ON\)\]"
line_nested=" - \($all_nested\)${description_state}"
line_non_nested="\($all_non_nested\)${description_state}"
+line_bpf="\($all_bpf\)${description_state}"
# List monitors and containers
check "list all monitors" \
- "$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\)$"
+ "$RV list" 0 "" "" "^\($line_nested\|$line_non_nested\|$line_bpf\)$"
check_if_exists "list container" \
"$RV list sched" "$RVDIR/monitors/sched" \
diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t
index cbc346c74c71..8e14471661c7 100644
--- a/tools/verification/rv/tests/rv_mon.t
+++ b/tools/verification/rv/tests/rv_mon.t
@@ -6,6 +6,7 @@ test_begin
set_timeout 30s
RVDIR=/sys/kernel/tracing/rv/
+RVTOOL=$(dirname "$RV")
# Help and basic tests
check "verify mon subcommand help" \
@@ -23,13 +24,34 @@ if [ -d $RVDIR/monitors/wwnr ]; then
check "invalid reactor name" \
"$RV mon wwnr -r invalid" 1 "failed to set invalid reactor, is it available?"
+check "invalid BPF reactor name" \
+ "$RV mon nohz -r invalid" 1 "failed to set invalid reactor, is it available?"
+
+check "invalid BPF reactor name check available" \
+ "$RV mon nohz -r invalid" 1 "available BPF reactors: nop [a-z]\+" \
+ "available reactors:"
+
check "monitor name is substring of another monitor" \
"$RV mon nr" 1 "monitor nr does not exist"
check "already enabled monitor returns error" \
"echo 1 > $RVDIR/monitors/wwnr/enable; $RV mon wwnr" 1 \
"monitor wwnr (in-kernel) is already enabled"
-echo 0 > $RVDIR/monitors/wwnr/enable
+[ -n "$TEST_COUNT" ] && echo 0 > $RVDIR/monitors/wwnr/enable
+
+fi
+
+if [ -f "$RVTOOL/bpf_monitors/tqueue.o" ]; then
+
+[ -n "$TEST_COUNT" ] && { $RV mon tqueue & disown ; tmp=$! ; sleep 1 ; }
+check "already enabled BPF monitor returns error" \
+ "$RV mon tqueue" 1 "monitor tqueue (BPF) is already enabled"
+
+[ -n "$TEST_COUNT" ] && { kill -9 "$tmp" && sleep 1 ; }
+set_expected_timeout 1s
+
+check "crashed BPF monitor does not leak resources" \
+ "$RV mon tqueue" 0 "" "monitor tqueue (BPF) is already enabled"
fi
@@ -40,6 +62,12 @@ set_expected_timeout 2s
check_if_exists "run the monitor without parameters" \
"$RV mon wwnr" "$RVDIR/monitors/wwnr" "" "."
+check_if_exists "run a BPF monitor without parameters" \
+ "$RV mon nohz" "$RVTOOL/bpf_monitors/nohz.o" "" "."
+
+check_if_exists "run a per-task BPF monitor without parameters" \
+ "$RV mon tqueue" "$RVTOOL/bpf_monitors/tqueue.o" "" "."
+
check_if_exists "run the monitor as verbose" \
"$RV mon wwnr -v" "$RVDIR/monitors/wwnr" \
"my pid is \$pid" "\(event\|error\)"
@@ -60,6 +88,20 @@ check_if_exists "run an explicitly nested monitor with a reactor" \
"$RV mon sched:sssw -r printk & sleep .5 && cat $RVDIR/monitors/sched/sssw/reactors && wait" \
"$RVDIR/monitors/sched/sssw/reactors" "\[printk\]"
+TRACE=/sys/kernel/tracing/trace
+
+[ -n "$TEST_COUNT" ] && echo -n > $TRACE
+check_if_exists "run a BPF monitor with a reactor" \
+ "$RV mon nohz -r printk && cat $TRACE" "$RVTOOL/bpf_monitors/nohz.o" \
+ "rv: monitor nohz does not allow event [a-z_]\+ on state [a-z_]\+"
+
+# Give some time for maps from previous run to be cleaned up
+[ -n "$TEST_COUNT" ] && sleep 1
+[ -n "$TEST_COUNT" ] && echo -n > $TRACE
+check_if_exists "run BPF monitors with nop reactor" \
+ "$RV mon nohz -r nop && cat $TRACE" "$RVTOOL/bpf_monitors/nohz.o" \
+ "" "rv: monitor nohz does not allow event"
+
check_if_exists "run container monitor" \
"$RV mon sched & sleep .5 && cat $RVDIR/monitors/sched/{sssw,sco}/enable && wait" \
"$RVDIR/monitors/sched" "1" "0" "^1$"
@@ -92,4 +134,21 @@ check_if_exists "run per-cpu monitor tracing also self" \
"$RV mon sco -t -s" "$RVDIR/monitors/sched/sco" \
"$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+check_if_exists "run per-task BPF monitor with tracing" \
+ "$RV mon tqueue -t" "$RVTOOL/bpf_monitors/tqueue.o" \
+ "$header" "$trace_task_self" "\($header\|$trace_task\)"
+
+check_if_exists "run per-task BPF monitor tracing also self" \
+ "$RV mon tqueue -t -s" "$RVTOOL/bpf_monitors/tqueue.o" \
+ "$trace_task_self" "" "\($header\|$trace_task\)"
+
+check_if_exists "run per-cpu BPF monitor with tracing" \
+ "$RV mon nohz -t" "$RVTOOL/bpf_monitors/nohz.o" \
+ "$header" "$trace_cpu_self" "\($header\|$trace_cpu\)"
+
+# This is unstable, we may never see events from self
+#check_if_exists "run per-cpu BPF monitor tracing also self" \
+# "$RV mon nohz -t -s" "$RVTOOL/bpf_monitors/nohz.o" \
+# "$trace_cpu_self" "" "\($header\|$trace_cpu\)"
+
test_end
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors
2026-10-01 15:20 ` [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors Gabriele Monaco
@ 2026-10-02 0:43 ` bot+bpf-ci
0 siblings, 0 replies; 27+ messages in thread
From: bot+bpf-ci @ 2026-10-02 0:43 UTC (permalink / raw)
To: gmonaco, linux-kernel, linux-trace-kernel, bpf, rostedt, gmonaco
Cc: alexei.starovoitov, namcao, wen.yang, tobias.schaffner, vmalik,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2733 bytes --]
> tools/rv: Add selftest for rv bpf monitors
TL;DR: Selftest issue: the two back-to-back tqueue runs in rv_mon.t may fail
with "already enabled" because the previous run's BPF maps aren't freed yet; a
sleep like the nohz tests use may be needed.
> diff --git a/tools/verification/rv/tests/rv_mon.t b/tools/verification/rv/tests/rv_mon.t
> --- a/tools/verification/rv/tests/rv_mon.t
> +++ b/tools/verification/rv/tests/rv_mon.t
[ ... ]
> @@ -92,4 +134,21 @@ check_if_exists "run per-cpu monitor tracing also self" \
> "$RV mon sco -t -s" "$RVDIR/monitors/sched/sco" \
> "$trace_cpu_self" "" "\($header\|$trace_cpu\)"
>
> +check_if_exists "run per-task BPF monitor with tracing" \
> + "$RV mon tqueue -t" "$RVTOOL/bpf_monitors/tqueue.o" \
> + "$header" "$trace_task_self" "\($header\|$trace_task\)"
> +
> +check_if_exists "run per-task BPF monitor tracing also self" \
> + "$RV mon tqueue -t -s" "$RVTOOL/bpf_monitors/tqueue.o" \
> + "$trace_task_self" "" "\($header\|$trace_task\)"
> +
Can this second tqueue run fail with "monitor tqueue (BPF) is already
enabled"?
Earlier in this patch the two consecutive nohz runs are separated by a
delay, because the previous run's maps are not necessarily cleaned up yet:
> +# Give some time for maps from previous run to be cleaned up
> +[ -n "$TEST_COUNT" ] && sleep 1
The two tqueue runs here are also back to back, with no delay between
"$RV mon tqueue -t" and "$RV mon tqueue -t -s".
bpf_read_enable() decides a monitor is already enabled by walking the map
IDs looking for an rv_mon_tqueue map. After the first rv exits, that map
stays visible until the deferred frees finish:
bpf_link_free()
bpf_link_is_tracepoint()
call_tracepoint_unregister_atomic() <- SRCU grace period
bpf_prog_put()
bpf_prog_put_deferred()
__bpf_prog_put_noref(prog, true)
call_rcu()
bpf_prog_free()
schedule_work(bpf_prog_free_deferred)
bpf_free_used_maps()
bpf_map_put()
bpf_map_free_id()
Until bpf_map_free_id() runs, bpf_map_get_fd_by_id() still succeeds. If
the second rv reaches bpf_read_enable() in that window, it prints "bpf:
monitor tqueue (BPF) is already enabled" and exits with 1, while
check_if_exists expects 0.
tqueue is a tp_btf monitor, so this chain includes a tracepoint SRCU grace
period on top of what the fentry based nohz case has to wait for. Would a
similar sleep between these two tqueue runs be needed?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36944413541
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 15/15] verification/rvgen: Add selftest for rvgen -b
2026-10-01 15:20 [PATCH v2 00/15] rv: Add support for BPF monitors Gabriele Monaco
` (13 preceding siblings ...)
2026-10-01 15:20 ` [PATCH v2 14/15] tools/rv: Add selftest for rv bpf monitors Gabriele Monaco
@ 2026-10-01 15:20 ` Gabriele Monaco
14 siblings, 0 replies; 27+ messages in thread
From: Gabriele Monaco @ 2026-10-01 15:20 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, bpf, Steven Rostedt, Gabriele Monaco
Cc: Alexei Starovoitov, Nam Cao, Wen Yang, Tobias Schaffner, Viktor Malik
Add selftest cases for BPF monitors generation.
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
---
.../tests/golden/da_bpf_cpu/da_bpf_cpu.c | 35 +++++++++++++
.../tests/golden/da_bpf_cpu/da_bpf_cpu.h | 47 ++++++++++++++++++
.../tests/golden/da_bpf_obj/da_bpf_obj.c | 49 +++++++++++++++++++
.../tests/golden/da_bpf_obj/da_bpf_obj.h | 47 ++++++++++++++++++
.../verification/rvgen/tests/rvgen_monitor.t | 11 +++++
5 files changed, 189 insertions(+)
create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
create mode 100644 tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
new file mode 100644
index 000000000000..5a652ce9b461
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_CPU
+#include "da_bpf_cpu.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_1, /* XXX: fill header */)
+{
+ da_handle_event(event_1_da_bpf_cpu);
+ return 0;
+}
+
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_2, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ da_handle_start_event(event_2_da_bpf_cpu);
+ return 0;
+}
+
+static struct rv_monitor rv_this = {
+ .enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "auto-generated";
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
new file mode 100644
index 000000000000..fd8125118d81
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_cpu/da_bpf_cpu.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_bpf_cpu automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_bpf_cpu
+
+enum states_da_bpf_cpu {
+ state_a_da_bpf_cpu,
+ state_b_da_bpf_cpu,
+ state_max_da_bpf_cpu,
+};
+
+#define INVALID_STATE state_max_da_bpf_cpu
+
+enum events_da_bpf_cpu {
+ event_1_da_bpf_cpu,
+ event_2_da_bpf_cpu,
+ event_max_da_bpf_cpu,
+};
+
+struct automaton_da_bpf_cpu {
+ char state_names[state_max_da_bpf_cpu][32];
+ char event_names[event_max_da_bpf_cpu][32];
+ unsigned char function[state_max_da_bpf_cpu][event_max_da_bpf_cpu];
+ unsigned char initial_state;
+ bool final_states[state_max_da_bpf_cpu];
+};
+
+static const struct automaton_da_bpf_cpu automaton_da_bpf_cpu = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_da_bpf_cpu, state_a_da_bpf_cpu },
+ { INVALID_STATE, state_a_da_bpf_cpu },
+ },
+ .initial_state = state_a_da_bpf_cpu,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
new file mode 100644
index 000000000000..a7d465f84f05
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#define RV_MON_TYPE RV_MON_PER_OBJ
+typedef /* XXX: define the target type */ *monitor_target_bpf;
+#include "da_bpf_obj.h"
+#include <rv/da_monitor.h>
+
+/*
+ * This is the instrumentation part of the monitor.
+ *
+ * This is the section where manual work is required. Here the kernel events
+ * are translated into model's event.
+ */
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_1, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ monitor_target_bpf t = /* XXX: how do I get t? */;
+ da_handle_event(id, t, event_1_da_bpf_obj);
+ return 0;
+}
+
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_event_2, /* XXX: fill header */)
+{
+ /* XXX: validate that this event always leads to the initial state */
+ int id = /* XXX: how do I get the id? */;
+ monitor_target_bpf t = /* XXX: how do I get t? */;
+ da_handle_start_event(id, t, event_2_da_bpf_obj);
+ return 0;
+}
+
+/* XXX: obj is being destroyed, remove if not required (e.g. obj is static) */
+SEC(/* XXX: tracepoint or other probe */)
+int BPF_PROG(handle_obj_cleanup, /* XXX: fill header */)
+{
+ int id = /* XXX: how do I get the id? */;
+ da_destroy_storage(id);
+ return 0;
+}
+
+static struct rv_monitor rv_this = {
+ .enabled = 0,
+};
+
+char LICENSE[] SEC("license") = "GPL";
+char DESCRIPTION[] SEC(".rodata.description") = "auto-generated";
diff --git a/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h
new file mode 100644
index 000000000000..385006098049
--- /dev/null
+++ b/tools/verification/rvgen/tests/golden/da_bpf_obj/da_bpf_obj.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Automatically generated C representation of da_bpf_obj automaton
+ * For further information about this format, see kernel documentation:
+ * Documentation/trace/rv/deterministic_automata.rst
+ */
+
+#define MONITOR_NAME da_bpf_obj
+
+enum states_da_bpf_obj {
+ state_a_da_bpf_obj,
+ state_b_da_bpf_obj,
+ state_max_da_bpf_obj,
+};
+
+#define INVALID_STATE state_max_da_bpf_obj
+
+enum events_da_bpf_obj {
+ event_1_da_bpf_obj,
+ event_2_da_bpf_obj,
+ event_max_da_bpf_obj,
+};
+
+struct automaton_da_bpf_obj {
+ char state_names[state_max_da_bpf_obj][32];
+ char event_names[event_max_da_bpf_obj][32];
+ unsigned char function[state_max_da_bpf_obj][event_max_da_bpf_obj];
+ unsigned char initial_state;
+ bool final_states[state_max_da_bpf_obj];
+};
+
+static const struct automaton_da_bpf_obj automaton_da_bpf_obj = {
+ .state_names = {
+ "state_a",
+ "state_b",
+ },
+ .event_names = {
+ "event_1",
+ "event_2",
+ },
+ .function = {
+ { state_b_da_bpf_obj, state_a_da_bpf_obj },
+ { INVALID_STATE, state_a_da_bpf_obj },
+ },
+ .initial_state = state_a_da_bpf_obj,
+ .final_states = { 1, 0 },
+};
diff --git a/tools/verification/rvgen/tests/rvgen_monitor.t b/tools/verification/rvgen/tests/rvgen_monitor.t
index 5f2562600bad..3d71685a7ad5 100644
--- a/tools/verification/rvgen/tests/rvgen_monitor.t
+++ b/tools/verification/rvgen/tests/rvgen_monitor.t
@@ -47,6 +47,17 @@ check_and_compare_folder "LTL per_task with parent and description (default name
"$RVGEN monitor -c ltl -s tests/specs/test_ltl.ltl -t per_task -p ltl_parent -D 'Simple description'" \
"test_ltl" "LTL_MON_EVENTS_ID"
+# BPF monitor test
+check_and_compare_folder "DA BPF per_cpu" \
+ "$RVGEN monitor -b -c da -s tests/specs/test_da.dot -t per_cpu -n da_bpf_cpu" \
+ "da_bpf_cpu" "Edit the da_bpf_cpu/da_bpf_cpu.c to add the instrumentation" \
+ "Edit kernel/trace/rv/Makefile"
+
+check_and_compare_folder "DA BPF per_obj" \
+ "$RVGEN monitor -b -c da -s tests/specs/test_da.dot -t per_obj -n da_bpf_obj" \
+ "da_bpf_obj" "Edit the da_bpf_obj/da_bpf_obj.c to add the instrumentation" \
+ "Edit kernel/trace/rv/Kconfig"
+
# Error handling tests
check "missing required spec argument" \
"$RVGEN monitor -c da -t per_cpu" 2 \
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread