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 <[email protected]>
---
 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 <[email protected]>
+ */
+
+#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


Reply via email to