All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH bpf-next v3 0/2] libbpf: Improve BPF load performance by selectively loading kmod BTFs
@ 2026-08-19  9:04 Fuyu Zhao
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
  0 siblings, 2 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-19  9:04 UTC (permalink / raw)
  To: bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, shuah, yatsenko,
	linux-kernel, linux-kselftest, Fuyu Zhao

Currently, during BPF object loading, load_module_btfs() unconditionally
iterates over all kernel module BTFs and loads each one. This introduces
unnecessary overhead when a BPF program only needs BTFs from a specific,
small subset of modules. In environments with hundreds of modules,
loading all module BTFs can measurably increase the loading time.

In our Android testing, BPF programs are loaded on demand rather than
preloaded to avoid unnecessary memory usage from unused programs. With
93 module BTFs present, the total BPF loading time exceeds 300 ms, with
module BTF loading accounting for around 69% of the total loading time.

The existing module qualification in SEC(), such as
SEC("fentry/mymod:foo"), does not address this issue. It only affects
BTF ID lookup after module BTFs have already been loaded by
load_module_btfs(), and does not reduce the number of module BTFs loaded.

This series adds kmod_btf_names and kmod_btf_names_cnt fields to
bpf_object_open_opts, allowing users to specify a list of kernel modules
whose BTFs should be loaded. libbpf uses this information to skip
unrelated module BTFs during iteration and stops iterating once all
requested module BTFs have been loaded. Without these options, the
existing behavior remains unchanged.

Performance impact (BPF skeleton open and load time):

  Modules loaded | Default (load all) | With kmod_btf_names | Speedup
  ---------------|--------------------|---------------------|--------
  1              | 35.6 ms            | 35.6 ms             | Baseline
  10             | 37.1 ms            | 35.8 ms             | +3.5%
  100            | 46.7 ms            | 36.7 ms             | +21.4%
  300            | 65.1 ms            | 38.7 ms             | +40.6%

Changelog:
v3:
- Use bpf_object_open_opts to specify kernel module BTFs to load rather
  than introducing a new .kmod_btfs ELF section, as suggested by Andrii.

v2:
- Link: https://lore.kernel.org/bpf/20260813032613.2755-1-zhaofuyu@vivo.com/
- Addressed issues with allocation error handling, multiple .kmod_btfs
  sections, the transient stack pointer in is_kmod_btf_needed(), and
  selftest dependency and comment formatting. (sashiko-bot)
- Removed KMODS_BTF_LOADED and KMODS_BTF_UNLOADED and simplified the
  related logic.

v1:
- Link: https://lore.kernel.org/bpf/20260806042042.3239428-1-zhaofuyu@vivo.com/

Fuyu Zhao (2):
  libbpf: support selective kernel module BTF loading via
    bpf_object_open_opts
  selftests/bpf: add tests for selective kmod BTF loading

 tools/lib/bpf/libbpf.c                        | 133 ++++++++++++
 tools/lib/bpf/libbpf.h                        |  16 +-
 .../selftests/bpf/prog_tests/kmod_btfs.c      | 196 ++++++++++++++++++
 tools/testing/selftests/bpf/progs/kmod_btfs.c |  13 ++
 4 files changed, 357 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs.c

-- 
2.34.1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-19  9:04 [RFC PATCH bpf-next v3 0/2] libbpf: Improve BPF load performance by selectively loading kmod BTFs Fuyu Zhao
@ 2026-08-19  9:04 ` Fuyu Zhao
  2026-08-19  9:12   ` sashiko-bot
                     ` (2 more replies)
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
  1 sibling, 3 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-19  9:04 UTC (permalink / raw)
  To: bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, shuah, yatsenko,
	linux-kernel, linux-kselftest, Fuyu Zhao

Add kmod_btf_names and kmod_btf_names_cnt fields to
bpf_object_open_opts to support selective kernel module BTF loading.

When kmod_btf_names is provided, libbpf only loads BTFs for the
specified kernel modules and skips other module BTFs during iteration,
speeding up BPF object loading by avoiding unnecessary module BTF
loading.

Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com>
Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
---
 tools/lib/bpf/libbpf.c | 133 +++++++++++++++++++++++++++++++++++++++++
 tools/lib/bpf/libbpf.h |  16 ++++-
 2 files changed, 148 insertions(+), 1 deletion(-)

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 514e4e9daa82..37934ca49dd7 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -779,6 +779,13 @@ struct bpf_object {
 	char *token_path;
 	int token_fd;
 
+	/* kernel module BTFs to load, as specified via bpf_object_open_opts */
+	struct {
+		char **names;
+		size_t nr_names;
+		struct hashmap *names_map;
+	} *kmod_btfs;
+
 	char path[];
 };
 
@@ -5803,6 +5810,121 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
 	return 0;
 }
 
+static size_t mod_name_hash_fn(long key, void *ctx)
+{
+	return str_hash((char *)key);
+}
+
+static bool mod_name_equal_fn(long key1, long key2, void *ctx)
+{
+	return strcmp((char *)key1, (char *)key2) == 0;
+}
+
+static void bpf_object__free_kmod_btfs(struct bpf_object *obj)
+{
+	size_t i;
+
+	if (!obj->kmod_btfs)
+		return;
+
+	if (obj->kmod_btfs->names) {
+		for (i = 0; i < obj->kmod_btfs->nr_names; i++)
+			zfree(&obj->kmod_btfs->names[i]);
+		zfree(&obj->kmod_btfs->names);
+	}
+	hashmap__free(obj->kmod_btfs->names_map);
+	zfree(&obj->kmod_btfs);
+}
+
+static int bpf_object__init_kmod_btfs(struct bpf_object *obj,
+				      const struct bpf_object_open_opts *opts)
+{
+	const char **kmod_btf_names;
+	size_t i, kmod_btf_names_cnt;
+	int err;
+
+	kmod_btf_names = OPTS_GET(opts, kmod_btf_names, NULL);
+	if (!kmod_btf_names)
+		return 0;
+
+	kmod_btf_names_cnt = OPTS_GET(opts, kmod_btf_names_cnt, 0);
+	if (!kmod_btf_names_cnt) {
+		pr_warn("kmod_btf_names_cnt must be set when kmod_btf_names is provided\n");
+		return -EINVAL;
+	}
+
+	obj->kmod_btfs = calloc(1, sizeof(*obj->kmod_btfs));
+	if (!obj->kmod_btfs)
+		return -ENOMEM;
+
+	obj->kmod_btfs->names = calloc(kmod_btf_names_cnt, sizeof(char *));
+	if (!obj->kmod_btfs->names) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+
+	obj->kmod_btfs->names_map = hashmap__new(mod_name_hash_fn,
+						 mod_name_equal_fn, NULL);
+	if (IS_ERR(obj->kmod_btfs->names_map)) {
+		err = PTR_ERR(obj->kmod_btfs->names_map);
+		obj->kmod_btfs->names_map = NULL;
+		goto err_out;
+	}
+
+	for (i = 0; i < kmod_btf_names_cnt; i++) {
+		size_t idx = obj->kmod_btfs->nr_names;
+
+		if (!kmod_btf_names[i] || !kmod_btf_names[i][0]) {
+			pr_warn("invalid kernel module BTF name at index %zu\n", i);
+			err = -EINVAL;
+			goto err_out;
+		}
+
+		obj->kmod_btfs->names[idx] = strdup(kmod_btf_names[i]);
+		if (!obj->kmod_btfs->names[idx]) {
+			err = -ENOMEM;
+			goto err_out;
+		}
+
+		err = hashmap__add(obj->kmod_btfs->names_map,
+				   obj->kmod_btfs->names[idx], 0);
+		if (err) {
+			zfree(&obj->kmod_btfs->names[idx]);
+			if (err == -EEXIST) {
+				pr_warn("duplicate kmod BTF name '%s' ignored\n",
+					kmod_btf_names[i]);
+				continue;
+			}
+			goto err_out;
+		}
+		obj->kmod_btfs->nr_names++;
+	}
+	return 0;
+
+err_out:
+	bpf_object__free_kmod_btfs(obj);
+	return err;
+}
+
+static bool is_kmod_btf_needed(struct bpf_object *obj, const char *name)
+{
+	if (!obj->kmod_btfs || !obj->kmod_btfs->names_map)
+		return true;
+
+	if (hashmap__find(obj->kmod_btfs->names_map, name, NULL))
+		return true;
+
+	pr_debug("skipping module BTF '%s', not in kmod_btf_names\n", name);
+	return false;
+}
+
+static bool all_needed_kmod_btfs_loaded(const struct bpf_object *obj)
+{
+	return obj->kmod_btfs &&
+	       obj->kmod_btfs->nr_names > 0 &&
+	       obj->kmod_btfs->nr_names == obj->btf_module_cnt;
+}
+
 static int load_module_btfs(struct bpf_object *obj)
 {
 	struct bpf_btf_info info;
@@ -5867,6 +5989,11 @@ static int load_module_btfs(struct bpf_object *obj)
 			continue;
 		}
 
+		if (!is_kmod_btf_needed(obj, name)) {
+			close(fd);
+			continue;
+		}
+
 		btf = btf_get_from_fd(fd, obj->btf_vmlinux);
 		err = libbpf_get_error(btf);
 		if (err) {
@@ -5891,6 +6018,9 @@ static int load_module_btfs(struct bpf_object *obj)
 			break;
 		}
 		obj->btf_module_cnt++;
+
+		if (all_needed_kmod_btfs_loaded(obj))
+			break;
 	}
 
 	if (err) {
@@ -8515,6 +8645,7 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 	err = err ? : bpf_object__init_maps(obj, opts);
 	err = err ? : bpf_object_init_progs(obj, opts);
 	err = err ? : bpf_object__collect_relos(obj);
+	err = err ? : bpf_object__init_kmod_btfs(obj, opts);
 	if (err)
 		goto out;
 
@@ -9629,6 +9760,8 @@ void bpf_object__close(struct bpf_object *obj)
 		close(obj->jumptable_maps[i].fd);
 	zfree(&obj->jumptable_maps);
 
+	bpf_object__free_kmod_btfs(obj);
+
 	free(obj);
 }
 
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index b965ad571540..2f8ff6d2d3df 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -224,10 +224,24 @@ struct bpf_object_open_opts {
 	 * point (/sys/fs/bpf), in case this default behavior is undesirable.
 	 */
 	const char *bpf_token_path;
+	/*
+	 * Optional list of kernel module names whose BTFs should be loaded.
+	 * kmod_btf_names_cnt specifies the number of entries in
+	 * kmod_btf_names.
+	 *
+	 * If kmod_btf_names is NULL, all module BTFs are loaded, preserving
+	 * the default behavior. Otherwise, only the specified module BTFs
+	 * are loaded.
+	 *
+	 * kmod_btf_names_cnt must be non-zero when kmod_btf_names is
+	 * non-NULL; otherwise -EINVAL is returned.
+	 */
+	const char **kmod_btf_names;
+	size_t kmod_btf_names_cnt;
 
 	size_t :0;
 };
-#define bpf_object_open_opts__last_field bpf_token_path
+#define bpf_object_open_opts__last_field kmod_btf_names_cnt
 
 /**
  * @brief **bpf_object__open()** creates a bpf_object by opening
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-19  9:04 [RFC PATCH bpf-next v3 0/2] libbpf: Improve BPF load performance by selectively loading kmod BTFs Fuyu Zhao
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
@ 2026-08-19  9:04 ` Fuyu Zhao
  2026-08-19  9:12   ` sashiko-bot
                     ` (2 more replies)
  1 sibling, 3 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-19  9:04 UTC (permalink / raw)
  To: bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, shuah, yatsenko,
	linux-kernel, linux-kselftest, Fuyu Zhao

The added test cases cover the following scenarios:

  - Valid module: ensure loading succeeds when the required module BTF is
    specified.
  - Non-existent module: ensure loading fails when only a non-existent
    module is specified and the required module BTF is unavailable.
  - Duplicate module names: ensure duplicate entries are ignored.
  - Unneeded module: ensure a loaded module whose BTF is not requested is
    skipped.
  - Missing count: ensure opening fails when kmod_btf_names is provided
    without kmod_btf_names_cnt.

Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
---
 .../selftests/bpf/prog_tests/kmod_btfs.c      | 196 ++++++++++++++++++
 tools/testing/selftests/bpf/progs/kmod_btfs.c |  13 ++
 2 files changed, 209 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs.c

diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
new file mode 100644
index 000000000000..61d2845a731f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "kmod_btfs.skel.h"
+
+static bool btf_skipped;
+static bool dup_ignored;
+
+static const char btf_skip_fmt[] =
+	"libbpf: skipping module BTF '%s', not in kmod_btf_names\n";
+static const char dup_ignore_fmt[] =
+	"libbpf: duplicate kmod BTF name '%s' ignored\n";
+
+static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
+			   va_list args)
+{
+	if (!strcmp(fmt, btf_skip_fmt)) {
+		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
+			btf_skipped = true;
+	} else if (!strcmp(fmt, dup_ignore_fmt)) {
+		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
+			dup_ignored = true;
+	}
+
+	return 0;
+}
+
+static void kmod_btfs_pass(void)
+{
+	struct kmod_btfs *skel = NULL;
+	int ret;
+	static const char *kmods[] = { "bpf_testmod" };
+
+	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.kmod_btf_names = kmods,
+		.kmod_btf_names_cnt = 1,
+	);
+
+	skel = kmod_btfs__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+		goto out;
+
+	ret = kmod_btfs__load(skel);
+	ASSERT_OK(ret, "kmod_btfs__load");
+out:
+	kmod_btfs__destroy(skel);
+}
+
+static void kmod_btfs_nonexist(void)
+{
+	struct kmod_btfs *skel = NULL;
+	int ret;
+	static const char *nonexist_kmods[] = { "module_nonexist" };
+
+	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.kmod_btf_names = nonexist_kmods,
+		.kmod_btf_names_cnt = 1,
+	);
+
+	/*
+	 * Only "module_nonexist" is requested; since it does not exist in
+	 * the kernel, no module BTF gets loaded. The BPF program then
+	 * fails to load because the real needed module's BTF is unavailable.
+	 */
+	skel = kmod_btfs__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+		goto out;
+
+	ret = kmod_btfs__load(skel);
+	ASSERT_ERR(ret, "kmod_btfs__load");
+
+out:
+	kmod_btfs__destroy(skel);
+}
+
+static void kmod_btfs_dup(void)
+{
+	struct kmod_btfs *skel = NULL;
+	libbpf_print_fn_t old_print_cb;
+	int ret;
+	static const char *dup_kmods[] = { "bpf_testmod", "bpf_testmod" };
+
+	/* duplicate entry on purpose: libbpf should dedupe it with a warning */
+	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.kmod_btf_names = dup_kmods,
+		.kmod_btf_names_cnt = 2,
+	);
+
+	dup_ignored = false;
+	old_print_cb = libbpf_set_print(libbpf_print_cb);
+
+	skel = kmod_btfs__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+		goto out;
+
+	ASSERT_TRUE(dup_ignored, "dup_ignored");
+
+	ret = kmod_btfs__load(skel);
+	ASSERT_OK(ret, "kmod_btfs__load");
+
+	kmod_btfs__destroy(skel);
+out:
+	libbpf_set_print(old_print_cb);
+}
+
+static void kmod_btfs_skip(void)
+{
+	struct kmod_btfs *skel = NULL;
+	libbpf_print_fn_t old_print_cb;
+	bool testmod_unloaded = false;
+	int ret;
+	static const char *kmods[] = { "bpf_testmod" };
+
+	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.kmod_btf_names = kmods,
+		.kmod_btf_names_cnt = 1,
+	);
+
+	btf_skipped = false;
+	old_print_cb = libbpf_set_print(libbpf_print_cb);
+
+	/*
+	 * Reload bpf_testmod after bpf_test_no_cfi to ensure the unneeded
+	 * module's BTF is enumerated first and skipped.
+	 */
+	if (!ASSERT_OK(unload_bpf_testmod(false), "unload bpf_testmod"))
+		goto cleanup_print;
+	testmod_unloaded = true;
+
+	if (!ASSERT_OK(load_module("bpf_test_no_cfi.ko", false),
+		       "load unneeded bpf_test_no_cfi module"))
+		goto cleanup_print;
+	if (!ASSERT_OK(load_bpf_testmod(false), "restore bpf_testmod"))
+		goto cleanup_module;
+	testmod_unloaded = false;
+
+	skel = kmod_btfs__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "kmod_btfs__open_opts"))
+		goto cleanup_module;
+
+	ret = kmod_btfs__load(skel);
+	if (!ASSERT_OK(ret, "kmod_btfs__load"))
+		goto cleanup_skel;
+
+	ASSERT_TRUE(btf_skipped, "btf_skipped");
+
+cleanup_skel:
+	kmod_btfs__destroy(skel);
+cleanup_module:
+	ASSERT_OK(unload_module("bpf_test_no_cfi", false),
+		  "unload unneeded bpf_test_no_cfi module");
+cleanup_print:
+	if (testmod_unloaded)
+		ASSERT_OK(load_bpf_testmod(false),
+			  "restore bpf_testmod on error path");
+	libbpf_set_print(old_print_cb);
+}
+
+static void kmod_btfs_no_cnt(void)
+{
+	struct kmod_btfs *skel = NULL;
+	static const char *kmods[] = { "bpf_testmod" };
+
+	/*
+	 * kmod_btf_names set but kmod_btf_names_cnt not set:
+	 * open must fail with -EINVAL.
+	 */
+	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.kmod_btf_names = kmods,
+	);
+
+	skel = kmod_btfs__open_opts(&opts);
+	ASSERT_NULL(skel, "kmod_btfs__open_opts without kmod_btf_names_cnt");
+}
+
+void serial_test_kmod_btfs(void)
+{
+	if (!env.has_testmod) {
+		test__skip();
+		return;
+	}
+
+	if (test__start_subtest("kmod_btfs_pass"))
+		kmod_btfs_pass();
+
+	if (test__start_subtest("kmod_btfs_nonexist"))
+		kmod_btfs_nonexist();
+
+	if (test__start_subtest("kmod_btfs_dup"))
+		kmod_btfs_dup();
+
+	if (test__start_subtest("kmod_btfs_skip"))
+		kmod_btfs_skip();
+
+	if (test__start_subtest("kmod_btfs_no_cnt"))
+		kmod_btfs_no_cnt();
+}
diff --git a/tools/testing/selftests/bpf/progs/kmod_btfs.c b/tools/testing/selftests/bpf/progs/kmod_btfs.c
new file mode 100644
index 000000000000..b96860d60e0d
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kmod_btfs.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+SEC("fentry/bpf_testmod_loop_test")
+int BPF_PROG(test_kmod_btfs)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
@ 2026-08-19  9:12   ` sashiko-bot
  2026-08-20  3:20     ` Fuyu Zhao
  2026-08-19  9:50   ` bot+bpf-ci
  2026-08-21  0:10   ` Eduard Zingerman
  2 siblings, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-08-19  9:12 UTC (permalink / raw)
  To: Fuyu Zhao; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The custom libbpf print callback silently drops all non-matching libbpf logs, hiding crucial diagnostic information if the test fails.
--

--- Patch [2]: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading ---
commit 17f3a2340dd7be0947e3baabf30a8c7722ec91fe
Author: Fuyu Zhao <zhaofuyu@vivo.com>

selftests/bpf: add tests for selective kmod BTF loading

This commit adds tests for the selective kmod BTF loading feature. It
covers scenarios such as valid modules, non-existent modules, duplicate
names, unneeded modules, and missing count properties.

> diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> new file mode 100644
> index 0000000000000..61d2845a731ff
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> @@ -0,0 +1,196 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <test_progs.h>
> +#include "kmod_btfs.skel.h"

[ ... ]

> +static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
> +			   va_list args)
> +{
> +	if (!strcmp(fmt, btf_skip_fmt)) {
> +		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
> +			btf_skipped = true;
> +	} else if (!strcmp(fmt, dup_ignore_fmt)) {
> +		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
> +			dup_ignored = true;
> +	}
> +
> +	return 0;
> +}

[Severity: Medium]
Does this unconditionally swallow all other libbpf debug, info, and error logs?

Since old_print_cb is stored as a local variable in the subtests (like
kmod_btfs_dup and kmod_btfs_skip) rather than globally, this callback has no
way to forward unmatched logs to the original handler. If a BPF program fails
to load during these tests, will the underlying verifier or libbpf errors be
completely hidden, making CI failures harder to debug?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260819090426.267-1-zhaofuyu@vivo.com?part=2

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
@ 2026-08-19  9:12   ` sashiko-bot
  2026-08-20  3:19     ` Fuyu Zhao
  2026-08-19  9:35   ` bot+bpf-ci
  2026-08-20 23:58   ` Eduard Zingerman
  2 siblings, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-08-19  9:12 UTC (permalink / raw)
  To: Fuyu Zhao; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Premature loop termination in load_module_btfs when kernel contains multiple BTF objects for the same module.
- [Low] API design prevents users from explicitly skipping all kernel module BTFs without providing a dummy name.
--

--- Patch [1]: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts ---
commit 627a7f9dbb55055d6c4d1331859084b6d2f56792
Author: Fuyu Zhao <zhaofuyu@vivo.com>

libbpf: support selective kernel module BTF loading via bpf_object_open_opts

Add kmod_btf_names and kmod_btf_names_cnt fields to bpf_object_open_opts
to support selective kernel module BTF loading. This allows libbpf to
load only specified module BTFs and skip others.

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index b749c01742ee0..957415341caf1 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c

[ ... ]

> @@ -5851,6 +5858,121 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>  	return 0;
>  }

[ ... ]

> +static int bpf_object__init_kmod_btfs(struct bpf_object *obj,
> +				      const struct bpf_object_open_opts *opts)
> +{
> +	const char **kmod_btf_names;
> +	size_t i, kmod_btf_names_cnt;
> +	int err;
> +
> +	kmod_btf_names = OPTS_GET(opts, kmod_btf_names, NULL);
> +	if (!kmod_btf_names)
> +		return 0;
> +
> +	kmod_btf_names_cnt = OPTS_GET(opts, kmod_btf_names_cnt, 0);
> +	if (!kmod_btf_names_cnt) {
> +		pr_warn("kmod_btf_names_cnt must be set when kmod_btf_names is provided\n");
> +		return -EINVAL;
> +	}

[Severity: Low]
If a user wants to optimize BPF object loading by skipping all module BTFs
(for instance, when only vmlinux BTF is needed), setting kmod_btf_names to NULL
falls back to the default behavior of loading all modules, but providing a
valid pointer with kmod_btf_names_cnt = 0 returns -EINVAL.

Does this API design force users to allocate and pass a dummy module name just
to skip loading module BTFs entirely?

> +
> +	obj->kmod_btfs = calloc(1, sizeof(*obj->kmod_btfs));

[ ... ]

> +static bool all_needed_kmod_btfs_loaded(const struct bpf_object *obj)
> +{
> +	return obj->kmod_btfs &&
> +	       obj->kmod_btfs->nr_names > 0 &&
> +	       obj->kmod_btfs->nr_names == obj->btf_module_cnt;
> +}

[Severity: Medium]
If a kernel module is unloaded but its BTF object is kept alive by an open file
descriptor, and the module is then reloaded, the kernel will contain multiple
BTF objects with the same module name.

Because btf_module_cnt increments for every loaded BTF object, but nr_names
tracks the number of unique requested module names, can duplicate BTF objects
inflate btf_module_cnt?

If a user requests BTFs for multiple modules via kmod_btf_names, would this
condition evaluate to true prematurely and skip the remaining requested modules?

> +
>  static int load_module_btfs(struct bpf_object *obj)
>  {

[ ... ]

> @@ -5939,6 +6066,9 @@ static int load_module_btfs(struct bpf_object *obj)
>  			break;
>  		}
>  		obj->btf_module_cnt++;
> +
> +		if (all_needed_kmod_btfs_loaded(obj))
> +			break;
>  	}
>  
>  	if (err) {

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260819090426.267-1-zhaofuyu@vivo.com?part=1

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
  2026-08-19  9:12   ` sashiko-bot
@ 2026-08-19  9:35   ` bot+bpf-ci
  2026-08-20  3:27     ` Fuyu Zhao
  2026-08-20 23:58   ` Eduard Zingerman
  2 siblings, 1 reply; 15+ messages in thread
From: bot+bpf-ci @ 2026-08-19  9:35 UTC (permalink / raw)
  To: zhaofuyu, bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, shuah, yatsenko,
	linux-kernel, linux-kselftest, zhaofuyu, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1402 bytes --]

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index b749c01742ee..957415341caf 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -782,6 +782,13 @@ struct bpf_object {
>  	char *token_path;
>  	int token_fd;
>
> +	/* kernel module BTFs to load, as specified via bpf_object_open_opts */
> +	struct {
> +		char **names;
> +		size_t nr_names;
> +		struct hashmap *names_map;
> +	} *kmod_btfs;
> +

This isn't a bug, but since this holds module names rather than BTFs,
would a name like kmod_btf_filter read more clearly next to the existing
btf_modules field?

>  	char path[];
>  };

[ ... ]

> @@ -5851,6 +5858,121 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>  	return 0;
>  }
>
> +static size_t mod_name_hash_fn(long key, void *ctx)
> +{
> +	return str_hash((char *)key);
> +}
> +
> +static bool mod_name_equal_fn(long key1, long key2, void *ctx)
> +{
> +	return strcmp((char *)key1, (char *)key2) == 0;
> +}

This isn't a bug, but these look identical to str_hash_fn()/str_equal_fn()
in btf_dump.c - would it be worth hoisting one copy into libbpf_internal.h
instead of adding a second pair?

[ ... ]


---
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/32236360082

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
  2026-08-19  9:12   ` sashiko-bot
@ 2026-08-19  9:50   ` bot+bpf-ci
  2026-08-20  3:28     ` Fuyu Zhao
  2026-08-21  0:10   ` Eduard Zingerman
  2 siblings, 1 reply; 15+ messages in thread
From: bot+bpf-ci @ 2026-08-19  9:50 UTC (permalink / raw)
  To: zhaofuyu, bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, ihor.solodrai, shuah, yatsenko,
	linux-kernel, linux-kselftest, zhaofuyu, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1898 bytes --]

> diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> new file mode 100644
> index 000000000000..61d2845a731f
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> @@ -0,0 +1,196 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <test_progs.h>
> +#include "kmod_btfs.skel.h"
> +
> +static bool btf_skipped;
> +static bool dup_ignored;
> +
> +static const char btf_skip_fmt[] =
> +	"libbpf: skipping module BTF '%s', not in kmod_btf_names\n";
> +static const char dup_ignore_fmt[] =
> +	"libbpf: duplicate kmod BTF name '%s' ignored\n";
> +
> +static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
> +			   va_list args)
> +{
> +	if (!strcmp(fmt, btf_skip_fmt)) {
> +		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
> +			btf_skipped = true;
> +	} else if (!strcmp(fmt, dup_ignore_fmt)) {
> +		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
> +			dup_ignored = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static void kmod_btfs_pass(void)
> +{
> +	struct kmod_btfs *skel = NULL;
> +	int ret;
> +	static const char *kmods[] = { "bpf_testmod" };
> +
> +	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
> +		.kmod_btf_names = kmods,
> +		.kmod_btf_names_cnt = 1,
> +	);

This isn't a bug, but would LIBBPF_OPTS() read better here than
DECLARE_LIBBPF_OPTS()? The same pattern appears in all five subtests
(kmod_btfs_pass, kmod_btfs_nonexist, kmod_btfs_dup, kmod_btfs_skip,
kmod_btfs_no_cnt). DECLARE_LIBBPF_OPTS is defined in libbpf_legacy.h as a
backwards-compatibility alias, and most of prog_tests/ uses LIBBPF_OPTS()
directly.

[ ... ]


---
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/32236360082

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-19  9:12   ` sashiko-bot
@ 2026-08-20  3:19     ` Fuyu Zhao
  0 siblings, 0 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-20  3:19 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: bpf



On 8/19/2026 5:12 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Premature loop termination in load_module_btfs when kernel contains multiple BTF objects for the same module.
> - [Low] API design prevents users from explicitly skipping all kernel module BTFs without providing a dummy name.
> --
> 
> --- Patch [1]: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts ---
> commit 627a7f9dbb55055d6c4d1331859084b6d2f56792
> Author: Fuyu Zhao <zhaofuyu@vivo.com>
> 
> libbpf: support selective kernel module BTF loading via bpf_object_open_opts
> 
> Add kmod_btf_names and kmod_btf_names_cnt fields to bpf_object_open_opts
> to support selective kernel module BTF loading. This allows libbpf to
> load only specified module BTFs and skip others.
> 
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index b749c01742ee0..957415341caf1 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
> 
> [ ... ]
> 
>> @@ -5851,6 +5858,121 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>>  	return 0;
>>  }
> 
> [ ... ]
> 
>> +static int bpf_object__init_kmod_btfs(struct bpf_object *obj,
>> +				      const struct bpf_object_open_opts *opts)
>> +{
>> +	const char **kmod_btf_names;
>> +	size_t i, kmod_btf_names_cnt;
>> +	int err;
>> +
>> +	kmod_btf_names = OPTS_GET(opts, kmod_btf_names, NULL);
>> +	if (!kmod_btf_names)
>> +		return 0;
>> +
>> +	kmod_btf_names_cnt = OPTS_GET(opts, kmod_btf_names_cnt, 0);
>> +	if (!kmod_btf_names_cnt) {
>> +		pr_warn("kmod_btf_names_cnt must be set when kmod_btf_names is provided\n");
>> +		return -EINVAL;
>> +	}
> 
> [Severity: Low]
> If a user wants to optimize BPF object loading by skipping all module BTFs
> (for instance, when only vmlinux BTF is needed), setting kmod_btf_names to NULL
> falls back to the default behavior of loading all modules, but providing a
> valid pointer with kmod_btf_names_cnt = 0 returns -EINVAL.
> 
> Does this API design force users to allocate and pass a dummy module name just
> to skip loading module BTFs entirely?
> 

The API is not intended to explicitly skip all module BTF loading.

A NULL kmod_btf_names means no module filter is specified, so libbpf
preserves the existing behavior.

When kmod_btf_names is provided, kmod_btf_names_cnt must specify the
number of entries. A non-NULL list with a zero count is therefore treated
as invalid API usage, rather than a request to skip all module BTFs.

>> +
>> +	obj->kmod_btfs = calloc(1, sizeof(*obj->kmod_btfs));
> 
> [ ... ]
> 
>> +static bool all_needed_kmod_btfs_loaded(const struct bpf_object *obj)
>> +{
>> +	return obj->kmod_btfs &&
>> +	       obj->kmod_btfs->nr_names > 0 &&
>> +	       obj->kmod_btfs->nr_names == obj->btf_module_cnt;
>> +}
> 
> [Severity: Medium]
> If a kernel module is unloaded but its BTF object is kept alive by an open file
> descriptor, and the module is then reloaded, the kernel will contain multiple
> BTF objects with the same module name.
> 
> Because btf_module_cnt increments for every loaded BTF object, but nr_names
> tracks the number of unique requested module names, can duplicate BTF objects
> inflate btf_module_cnt?
> 
> If a user requests BTFs for multiple modules via kmod_btf_names, would this
> condition evaluate to true prematurely and skip the remaining requested modules?
> 

I don't think this can cause premature termination. `load_module_btfs()`
iterates BTFs in ascending ID order, so a reloaded module gets a higher
BTF ID and is encountered later. We stop as soon as all requested module
BTFs are loaded, before reaching such duplicate BTFs.

>> +
>>  static int load_module_btfs(struct bpf_object *obj)
>>  {
> 
> [ ... ]
> 
>> @@ -5939,6 +6066,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>  			break;
>>  		}
>>  		obj->btf_module_cnt++;
>> +
>> +		if (all_needed_kmod_btfs_loaded(obj))
>> +			break;
>>  	}
>>  
>>  	if (err) {
> 


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-19  9:12   ` sashiko-bot
@ 2026-08-20  3:20     ` Fuyu Zhao
  0 siblings, 0 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-20  3:20 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: bpf



On 8/19/2026 5:12 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The custom libbpf print callback silently drops all non-matching libbpf logs, hiding crucial diagnostic information if the test fails.
> --
> 
> --- Patch [2]: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading ---
> commit 17f3a2340dd7be0947e3baabf30a8c7722ec91fe
> Author: Fuyu Zhao <zhaofuyu@vivo.com>
> 
> selftests/bpf: add tests for selective kmod BTF loading
> 
> This commit adds tests for the selective kmod BTF loading feature. It
> covers scenarios such as valid modules, non-existent modules, duplicate
> names, unneeded modules, and missing count properties.
> 
>> diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
>> new file mode 100644
>> index 0000000000000..61d2845a731ff
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
>> @@ -0,0 +1,196 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +#include <test_progs.h>
>> +#include "kmod_btfs.skel.h"
> 
> [ ... ]
> 
>> +static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
>> +			   va_list args)
>> +{
>> +	if (!strcmp(fmt, btf_skip_fmt)) {
>> +		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
>> +			btf_skipped = true;
>> +	} else if (!strcmp(fmt, dup_ignore_fmt)) {
>> +		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
>> +			dup_ignored = true;
>> +	}
>> +
>> +	return 0;
>> +}
> 
> [Severity: Medium]
> Does this unconditionally swallow all other libbpf debug, info, and error logs?
> 
> Since old_print_cb is stored as a local variable in the subtests (like
> kmod_btfs_dup and kmod_btfs_skip) rather than globally, this callback has no
> way to forward unmatched logs to the original handler. If a BPF program fails
> to load during these tests, will the underlying verifier or libbpf errors be
> completely hidden, making CI failures harder to debug?
> 

I don't consider this a problem since the test runs serially and the callback
only affects the kmod_btfs test.

However, I can forward unmatched logs to the original callback through
old_print_cb to preserve the original logging behavior.



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-19  9:35   ` bot+bpf-ci
@ 2026-08-20  3:27     ` Fuyu Zhao
  0 siblings, 0 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-20  3:27 UTC (permalink / raw)
  To: bot+bpf-ci; +Cc: bpf



On 8/19/2026 5:35 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index b749c01742ee..957415341caf 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
>> @@ -782,6 +782,13 @@ struct bpf_object {
>>  	char *token_path;
>>  	int token_fd;
>>
>> +	/* kernel module BTFs to load, as specified via bpf_object_open_opts */
>> +	struct {
>> +		char **names;
>> +		size_t nr_names;
>> +		struct hashmap *names_map;
>> +	} *kmod_btfs;
>> +
> 
> This isn't a bug, but since this holds module names rather than BTFs,
> would a name like kmod_btf_filter read more clearly next to the existing
> btf_modules field?
> 

I think kmod_btfs is sufficiently clear here. It represents the kernel
module names used to select which module BTFs should be loaded. Using
kmod_btf_filter could instead imply filtering BTFs, which would be
ambiguous with the fact that the filter is based on module names.

>>  	char path[];
>>  };
> 
> [ ... ]
> 
>> @@ -5851,6 +5858,121 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>>  	return 0;
>>  }
>>
>> +static size_t mod_name_hash_fn(long key, void *ctx)
>> +{
>> +	return str_hash((char *)key);
>> +}
>> +
>> +static bool mod_name_equal_fn(long key1, long key2, void *ctx)
>> +{
>> +	return strcmp((char *)key1, (char *)key2) == 0;
>> +}
> 
> This isn't a bug, but these look identical to str_hash_fn()/str_equal_fn()
> in btf_dump.c - would it be worth hoisting one copy into libbpf_internal.h
> instead of adding a second pair?
> 

I think it is better to keep these helpers separate. Although they are
currently identical, the two use cases are unrelated and may evolve
independently, so sharing them would unnecessarily couple the implementations.

> [ ... ]
> 
> 
> ---
> 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/32236360082


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-19  9:50   ` bot+bpf-ci
@ 2026-08-20  3:28     ` Fuyu Zhao
  0 siblings, 0 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-20  3:28 UTC (permalink / raw)
  To: bot+bpf-ci; +Cc: bpf



On 8/19/2026 5:50 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
>> new file mode 100644
>> index 000000000000..61d2845a731f
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
>> @@ -0,0 +1,196 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +#include <test_progs.h>
>> +#include "kmod_btfs.skel.h"
>> +
>> +static bool btf_skipped;
>> +static bool dup_ignored;
>> +
>> +static const char btf_skip_fmt[] =
>> +	"libbpf: skipping module BTF '%s', not in kmod_btf_names\n";
>> +static const char dup_ignore_fmt[] =
>> +	"libbpf: duplicate kmod BTF name '%s' ignored\n";
>> +
>> +static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
>> +			   va_list args)
>> +{
>> +	if (!strcmp(fmt, btf_skip_fmt)) {
>> +		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
>> +			btf_skipped = true;
>> +	} else if (!strcmp(fmt, dup_ignore_fmt)) {
>> +		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
>> +			dup_ignored = true;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void kmod_btfs_pass(void)
>> +{
>> +	struct kmod_btfs *skel = NULL;
>> +	int ret;
>> +	static const char *kmods[] = { "bpf_testmod" };
>> +
>> +	DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +		.kmod_btf_names = kmods,
>> +		.kmod_btf_names_cnt = 1,
>> +	);
> 
> This isn't a bug, but would LIBBPF_OPTS() read better here than
> DECLARE_LIBBPF_OPTS()? The same pattern appears in all five subtests
> (kmod_btfs_pass, kmod_btfs_nonexist, kmod_btfs_dup, kmod_btfs_skip,
> kmod_btfs_no_cnt). DECLARE_LIBBPF_OPTS is defined in libbpf_legacy.h as a
> backwards-compatibility alias, and most of prog_tests/ uses LIBBPF_OPTS()
> directly.
> 

Agreed, I'll use LIBBPF_OPTS() 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/32236360082


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
  2026-08-19  9:12   ` sashiko-bot
  2026-08-19  9:35   ` bot+bpf-ci
@ 2026-08-20 23:58   ` Eduard Zingerman
  2026-08-21  3:51     ` Fuyu Zhao
  2 siblings, 1 reply; 15+ messages in thread
From: Eduard Zingerman @ 2026-08-20 23:58 UTC (permalink / raw)
  To: Fuyu Zhao, bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, memxor, martin.lau, song, yonghong.song,
	jolsa, emil, ihor.solodrai, shuah, yatsenko, linux-kernel,
	linux-kselftest

On Wed, 2026-08-19 at 17:04 +0800, Fuyu Zhao wrote:

Overall the logic seem to be fine for me, please find a few comments below.

...

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 514e4e9daa82..37934ca49dd7 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -779,6 +779,13 @@ struct bpf_object {
>  	char *token_path;
>  	int token_fd;
>  
> +	/* kernel module BTFs to load, as specified via bpf_object_open_opts */
> +	struct {
> +		char **names;
> +		size_t nr_names;
> +		struct hashmap *names_map;

We already have a strset type, please use it instead of a direct
hashmap reference. This would remove the need for `names` field
and simplify the bpf_object__init_kmod_btfs() function.

Also, do you expect `nr_names` to be high?
If not, wouldn't plain array search be simpler/faster here?

> +	} *kmod_btfs;

Why indirection?

Also, I agree with the bot here, prior fields use "module" in the name,
so something like "btf_module_names" or similar is a better fit.
Same for publicly visible 'opts' name.

...

> +static int bpf_object__init_kmod_btfs(struct bpf_object *obj,
> +				      const struct bpf_object_open_opts *opts)
> +{
> +	const char **kmod_btf_names;
> +	size_t i, kmod_btf_names_cnt;
> +	int err;
> +
> +	kmod_btf_names = OPTS_GET(opts, kmod_btf_names, NULL);
> +	if (!kmod_btf_names)
> +		return 0;
> +
> +	kmod_btf_names_cnt = OPTS_GET(opts, kmod_btf_names_cnt, 0);
> +	if (!kmod_btf_names_cnt) {
> +		pr_warn("kmod_btf_names_cnt must be set when kmod_btf_names is provided\n");
> +		return -EINVAL;

I kinda agree with the bot here, why disallow an empty filter here?

> +	}
> +
> +	obj->kmod_btfs = calloc(1, sizeof(*obj->kmod_btfs));
> +	if (!obj->kmod_btfs)
> +		return -ENOMEM;
> +
> +	obj->kmod_btfs->names = calloc(kmod_btf_names_cnt, sizeof(char *));
> +	if (!obj->kmod_btfs->names) {
> +		err = -ENOMEM;
> +		goto err_out;
> +	}
> +
> +	obj->kmod_btfs->names_map = hashmap__new(mod_name_hash_fn,
> +						 mod_name_equal_fn, NULL);
> +	if (IS_ERR(obj->kmod_btfs->names_map)) {
> +		err = PTR_ERR(obj->kmod_btfs->names_map);
> +		obj->kmod_btfs->names_map = NULL;
> +		goto err_out;
> +	}
> +
> +	for (i = 0; i < kmod_btf_names_cnt; i++) {
> +		size_t idx = obj->kmod_btfs->nr_names;
> +
> +		if (!kmod_btf_names[i] || !kmod_btf_names[i][0]) {
> +			pr_warn("invalid kernel module BTF name at index %zu\n", i);
> +			err = -EINVAL;
> +			goto err_out;
> +		}
> +
> +		obj->kmod_btfs->names[idx] = strdup(kmod_btf_names[i]);
> +		if (!obj->kmod_btfs->names[idx]) {
> +			err = -ENOMEM;
> +			goto err_out;
> +		}
> +
> +		err = hashmap__add(obj->kmod_btfs->names_map,
> +				   obj->kmod_btfs->names[idx], 0);
> +		if (err) {
> +			zfree(&obj->kmod_btfs->names[idx]);
> +			if (err == -EEXIST) {
> +				pr_warn("duplicate kmod BTF name '%s' ignored\n",
> +					kmod_btf_names[i]);

Nit: I'd downgrade this to debug level, if at all.

> +				continue;
> +			}
> +			goto err_out;
> +		}
> +		obj->kmod_btfs->nr_names++;
> +	}
> +	return 0;
> +
> +err_out:
> +	bpf_object__free_kmod_btfs(obj);
> +	return err;
> +}

...

> @@ -8515,6 +8645,7 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>  	err = err ? : bpf_object__init_maps(obj, opts);
>  	err = err ? : bpf_object_init_progs(obj, opts);
>  	err = err ? : bpf_object__collect_relos(obj);
> +	err = err ? : bpf_object__init_kmod_btfs(obj, opts);

Nit: all other options are collected before bpf_object__elf_init()
     call just above.

>  	if (err)
>  		goto out;
>  

...

> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index b965ad571540..2f8ff6d2d3df 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -224,10 +224,24 @@ struct bpf_object_open_opts {
>  	 * point (/sys/fs/bpf), in case this default behavior is undesirable.
>  	 */
>  	const char *bpf_token_path;
> +	/*
> +	 * Optional list of kernel module names whose BTFs should be loaded.
> +	 * kmod_btf_names_cnt specifies the number of entries in
> +	 * kmod_btf_names.
> +	 *
> +	 * If kmod_btf_names is NULL, all module BTFs are loaded, preserving
> +	 * the default behavior. Otherwise, only the specified module BTFs
> +	 * are loaded.
> +	 *
> +	 * kmod_btf_names_cnt must be non-zero when kmod_btf_names is
> +	 * non-NULL; otherwise -EINVAL is returned.

Nit: please add a complete list of behaviors this affects, e.g. CO-RE,
     fentry/fexit resolution, etc.

> +	 */
> +	const char **kmod_btf_names;
> +	size_t kmod_btf_names_cnt;
>  
>  	size_t :0;
>  };
> -#define bpf_object_open_opts__last_field bpf_token_path
> +#define bpf_object_open_opts__last_field kmod_btf_names_cnt
>  
>  /**
>   * @brief **bpf_object__open()** creates a bpf_object by opening

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-19  9:04 ` [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
  2026-08-19  9:12   ` sashiko-bot
  2026-08-19  9:50   ` bot+bpf-ci
@ 2026-08-21  0:10   ` Eduard Zingerman
  2026-08-21  3:51     ` Fuyu Zhao
  2 siblings, 1 reply; 15+ messages in thread
From: Eduard Zingerman @ 2026-08-21  0:10 UTC (permalink / raw)
  To: Fuyu Zhao, bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, memxor, martin.lau, song, yonghong.song,
	jolsa, emil, ihor.solodrai, shuah, yatsenko, linux-kernel,
	linux-kselftest

On Wed, 2026-08-19 at 17:04 +0800, Fuyu Zhao wrote:

...

> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> @@ -0,0 +1,196 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <test_progs.h>
> +#include "kmod_btfs.skel.h"
> +
> +static bool btf_skipped;
> +static bool dup_ignored;
> +
> +static const char btf_skip_fmt[] =
> +	"libbpf: skipping module BTF '%s', not in kmod_btf_names\n";
> +static const char dup_ignore_fmt[] =
> +	"libbpf: duplicate kmod BTF name '%s' ignored\n";
> +
> +static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
> +			   va_list args)
> +{
> +	if (!strcmp(fmt, btf_skip_fmt)) {
> +		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
> +			btf_skipped = true;
> +	} else if (!strcmp(fmt, dup_ignore_fmt)) {
> +		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
> +			dup_ignored = true;
> +	}
> +
> +	return 0;
> +}

Idk what Andrii would say about this, but I'd rather add an internal
function akin to:

  size_t bpf_object_btf_module_cnt(const struct bpf_object *obj);

in libbpf.c and expose it via libbpf_internal.h (*not* via API),
instead of intercepting log messages.

...

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-20 23:58   ` Eduard Zingerman
@ 2026-08-21  3:51     ` Fuyu Zhao
  0 siblings, 0 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-21  3:51 UTC (permalink / raw)
  To: Eduard Zingerman, bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, memxor, martin.lau, song, yonghong.song,
	jolsa, emil, ihor.solodrai, shuah, yatsenko, linux-kernel,
	linux-kselftest

Hi Eduard,

Thanks for the suggestions. Please see my responses inline below.

On 8/21/2026 7:58 AM, Eduard Zingerman wrote:
> On Wed, 2026-08-19 at 17:04 +0800, Fuyu Zhao wrote:
> 
> Overall the logic seem to be fine for me, please find a few comments below.
> 
> ...
> 
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index 514e4e9daa82..37934ca49dd7 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
>> @@ -779,6 +779,13 @@ struct bpf_object {
>>  	char *token_path;
>>  	int token_fd;
>>  
>> +	/* kernel module BTFs to load, as specified via bpf_object_open_opts */
>> +	struct {
>> +		char **names;
>> +		size_t nr_names;
>> +		struct hashmap *names_map;
> 
> We already have a strset type, please use it instead of a direct
> hashmap reference. This would remove the need for `names` field
> and simplify the bpf_object__init_kmod_btfs() function.
> 
> Also, do you expect `nr_names` to be high?
> If not, wouldn't plain array search be simpler/faster here?
> 

I initially used a hashmap to avoid repeatedly walking the requested
module name list. But I don't expect nr_names to be high in practice, so
a plain array search should be sufficient and simpler. I'll change this
in the next version.

>> +	} *kmod_btfs;
> 
> Why indirection?
> 

I followed the existing pattern in bpf_object and used an anonymous
structure with indirection here. I don't have a strong reason to keep
the indirection, though. I'll simplify it.

> Also, I agree with the bot here, prior fields use "module" in the name,
> so something like "btf_module_names" or similar is a better fit.
> Same for publicly visible 'opts' name.
> 

I think `btf_module_names` is a much better name than my original choice.
I'll change that in `opts`, too.

> ...
> 
>> +static int bpf_object__init_kmod_btfs(struct bpf_object *obj,
>> +				      const struct bpf_object_open_opts *opts)
>> +{
>> +	const char **kmod_btf_names;
>> +	size_t i, kmod_btf_names_cnt;
>> +	int err;
>> +
>> +	kmod_btf_names = OPTS_GET(opts, kmod_btf_names, NULL);
>> +	if (!kmod_btf_names)
>> +		return 0;
>> +
>> +	kmod_btf_names_cnt = OPTS_GET(opts, kmod_btf_names_cnt, 0);
>> +	if (!kmod_btf_names_cnt) {
>> +		pr_warn("kmod_btf_names_cnt must be set when kmod_btf_names is provided\n");
>> +		return -EINVAL;
> 
> I kinda agree with the bot here, why disallow an empty filter here?
> 

I understand the point now. I hadn't considered the use case of
explicitly skipping all module BTFs. A non-NULL `kmod_btf_names` with a
zero count is a way to represent this case, so I'll support it in the
next version.

>> +	}
>> +
>> +	obj->kmod_btfs = calloc(1, sizeof(*obj->kmod_btfs));
>> +	if (!obj->kmod_btfs)
>> +		return -ENOMEM;
>> +
>> +	obj->kmod_btfs->names = calloc(kmod_btf_names_cnt, sizeof(char *));
>> +	if (!obj->kmod_btfs->names) {
>> +		err = -ENOMEM;
>> +		goto err_out;
>> +	}
>> +
>> +	obj->kmod_btfs->names_map = hashmap__new(mod_name_hash_fn,
>> +						 mod_name_equal_fn, NULL);
>> +	if (IS_ERR(obj->kmod_btfs->names_map)) {
>> +		err = PTR_ERR(obj->kmod_btfs->names_map);
>> +		obj->kmod_btfs->names_map = NULL;
>> +		goto err_out;
>> +	}
>> +
>> +	for (i = 0; i < kmod_btf_names_cnt; i++) {
>> +		size_t idx = obj->kmod_btfs->nr_names;
>> +
>> +		if (!kmod_btf_names[i] || !kmod_btf_names[i][0]) {
>> +			pr_warn("invalid kernel module BTF name at index %zu\n", i);
>> +			err = -EINVAL;
>> +			goto err_out;
>> +		}
>> +
>> +		obj->kmod_btfs->names[idx] = strdup(kmod_btf_names[i]);
>> +		if (!obj->kmod_btfs->names[idx]) {
>> +			err = -ENOMEM;
>> +			goto err_out;
>> +		}
>> +
>> +		err = hashmap__add(obj->kmod_btfs->names_map,
>> +				   obj->kmod_btfs->names[idx], 0);
>> +		if (err) {
>> +			zfree(&obj->kmod_btfs->names[idx]);
>> +			if (err == -EEXIST) {
>> +				pr_warn("duplicate kmod BTF name '%s' ignored\n",
>> +					kmod_btf_names[i]);
> 
> Nit: I'd downgrade this to debug level, if at all.
> 

Agreed. I'll downgrade this to debug level in the next version.

>> +				continue;
>> +			}
>> +			goto err_out;
>> +		}
>> +		obj->kmod_btfs->nr_names++;
>> +	}
>> +	return 0;
>> +
>> +err_out:
>> +	bpf_object__free_kmod_btfs(obj);
>> +	return err;
>> +}
> 
> ...
> 
>> @@ -8515,6 +8645,7 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>>  	err = err ? : bpf_object__init_maps(obj, opts);
>>  	err = err ? : bpf_object_init_progs(obj, opts);
>>  	err = err ? : bpf_object__collect_relos(obj);
>> +	err = err ? : bpf_object__init_kmod_btfs(obj, opts);
> 
> Nit: all other options are collected before bpf_object__elf_init()
>      call just above.
> 

Agreed. I'll change it in the next version.

>>  	if (err)
>>  		goto out;
>>  
> 
> ...
> 
>> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
>> index b965ad571540..2f8ff6d2d3df 100644
>> --- a/tools/lib/bpf/libbpf.h
>> +++ b/tools/lib/bpf/libbpf.h
>> @@ -224,10 +224,24 @@ struct bpf_object_open_opts {
>>  	 * point (/sys/fs/bpf), in case this default behavior is undesirable.
>>  	 */
>>  	const char *bpf_token_path;
>> +	/*
>> +	 * Optional list of kernel module names whose BTFs should be loaded.
>> +	 * kmod_btf_names_cnt specifies the number of entries in
>> +	 * kmod_btf_names.
>> +	 *
>> +	 * If kmod_btf_names is NULL, all module BTFs are loaded, preserving
>> +	 * the default behavior. Otherwise, only the specified module BTFs
>> +	 * are loaded.
>> +	 *
>> +	 * kmod_btf_names_cnt must be non-zero when kmod_btf_names is
>> +	 * non-NULL; otherwise -EINVAL is returned.
> 
> Nit: please add a complete list of behaviors this affects, e.g. CO-RE,
>      fentry/fexit resolution, etc.
> 

I'll expand the comment to cover the affected behaviors in the next version.

>> +	 */
>> +	const char **kmod_btf_names;
>> +	size_t kmod_btf_names_cnt;
>>  
>>  	size_t :0;
>>  };
>> -#define bpf_object_open_opts__last_field bpf_token_path
>> +#define bpf_object_open_opts__last_field kmod_btf_names_cnt
>>  
>>  /**
>>   * @brief **bpf_object__open()** creates a bpf_object by opening


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-21  0:10   ` Eduard Zingerman
@ 2026-08-21  3:51     ` Fuyu Zhao
  0 siblings, 0 replies; 15+ messages in thread
From: Fuyu Zhao @ 2026-08-21  3:51 UTC (permalink / raw)
  To: Eduard Zingerman, bpf, andrii.nakryiko, alan.maguire
  Cc: andrii, ast, daniel, memxor, martin.lau, song, yonghong.song,
	jolsa, emil, ihor.solodrai, shuah, yatsenko, linux-kernel,
	linux-kselftest



On 8/21/2026 8:10 AM, Eduard Zingerman wrote:
> On Wed, 2026-08-19 at 17:04 +0800, Fuyu Zhao wrote:
> 
> ...
> 
>> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
>> @@ -0,0 +1,196 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +#include <test_progs.h>
>> +#include "kmod_btfs.skel.h"
>> +
>> +static bool btf_skipped;
>> +static bool dup_ignored;
>> +
>> +static const char btf_skip_fmt[] =
>> +	"libbpf: skipping module BTF '%s', not in kmod_btf_names\n";
>> +static const char dup_ignore_fmt[] =
>> +	"libbpf: duplicate kmod BTF name '%s' ignored\n";
>> +
>> +static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt,
>> +			   va_list args)
>> +{
>> +	if (!strcmp(fmt, btf_skip_fmt)) {
>> +		if (!strcmp(va_arg(args, char *), "bpf_test_no_cfi"))
>> +			btf_skipped = true;
>> +	} else if (!strcmp(fmt, dup_ignore_fmt)) {
>> +		if (!strcmp(va_arg(args, char *), "bpf_testmod"))
>> +			dup_ignored = true;
>> +	}
>> +
>> +	return 0;
>> +}
> 
> Idk what Andrii would say about this, but I'd rather add an internal
> function akin to:
> 
>   size_t bpf_object_btf_module_cnt(const struct bpf_object *obj);
> 
> in libbpf.c and expose it via libbpf_internal.h (*not* via API),
> instead of intercepting log messages.
> 
> ...

I think your suggestion is better. It also addresses the issue raised by
the bot about potentially hiding other libbpf logs. I'll wait for Andrii's
feedback on this.

Thanks,
Fuyu


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-08-21  3:51 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-19  9:04 [RFC PATCH bpf-next v3 0/2] libbpf: Improve BPF load performance by selectively loading kmod BTFs Fuyu Zhao
2026-08-19  9:04 ` [RFC PATCH bpf-next v3 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
2026-08-19  9:12   ` sashiko-bot
2026-08-20  3:19     ` Fuyu Zhao
2026-08-19  9:35   ` bot+bpf-ci
2026-08-20  3:27     ` Fuyu Zhao
2026-08-20 23:58   ` Eduard Zingerman
2026-08-21  3:51     ` Fuyu Zhao
2026-08-19  9:04 ` [RFC PATCH bpf-next v3 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
2026-08-19  9:12   ` sashiko-bot
2026-08-20  3:20     ` Fuyu Zhao
2026-08-19  9:50   ` bot+bpf-ci
2026-08-20  3:28     ` Fuyu Zhao
2026-08-21  0:10   ` Eduard Zingerman
2026-08-21  3:51     ` Fuyu Zhao

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.