BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v6 0/2] libbpf: Improve BPF load performance by selectively loading module BTFs
@ 2026-08-31 13:38 Fuyu Zhao
  2026-08-31 13:38 ` [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
  2026-08-31 13:38 ` [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
  0 siblings, 2 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-08-31 13:38 UTC (permalink / raw)
  To: bpf; +Cc: eddyz87, andrii.nakryiko, alan.maguire, 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 btf_module_names and nr_btf_module_names fields to
bpf_object_open_opts to support selective kernel module BTF loading.
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):

  Module BTFs | Default | Selective loading | Time reduction
  ------------|---------|-------------------|---------------
  1           | 35.6 ms | 35.6 ms           | Baseline
  10          | 37.2 ms | 35.7 ms           | 4.0%
  100         | 46.7 ms | 36.5 ms           | 21.8%
  300         | 65.2 ms | 38.5 ms           | 40.9%

Changelog:
v6:
- Document module-qualified tracing multi-attach targets and invalid
  module name handling. (bot+bpf-ci)
- Remove unnecessary `static` qualifiers from test module name arrays.
  (bot+bpf-ci)
- Check that bpf_testmod BTF is available before running the tests.
  (bot+bpf-ci)

v5:	
-Link: https://lore.kernel.org/bpf/20260826131428.302-1-zhaofuyu@vivo.com/
- Add no_module_btfs_needed() to skip loading module BTFs early when the
  module BTF name list is empty. (sashiko-bot)
- Document struct_ops as an affected use case for btf_module_names.
  (bot+bpf-ci)
- Add invalid input tests and assert expected error codes for negative
  cases. (bot+bpf-ci)

v4:
- Link: https://lore.kernel.org/bpf/20260824125310.1384-1-zhaofuyu@vivo.com/
- Simplify and rename newly added struct members and their documentation.
  (Eduard)
- Use array search instead of a hashmap for module name matching to
  simplify the code. (Eduard)
- Allow an empty module name list to skip loading all module BTFs.
  (Eduard, sashiko-bot)
- Move btf_module_names option handling before bpf_object__elf_init().
  (Eduard)
- Rename internal helpers to follow libbpf naming conventions. (Andrii)
- Reject duplicate module names and document the rejection rule. (Andrii)
- Replace log interception with functional tests and simplify selftests.
  (Andrii)

v3:
- Link: https://lore.kernel.org/bpf/20260819090426.267-1-zhaofuyu@vivo.com/
- 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 module BTF loading

 tools/lib/bpf/libbpf.c                        | 117 +++++++++++++
 tools/lib/bpf/libbpf.h                        |  24 ++-
 .../bpf/prog_tests/btf_module_names.c         | 162 ++++++++++++++++++
 .../selftests/bpf/progs/btf_module_names.c    |  13 ++
 4 files changed, 315 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_module_names.c
 create mode 100644 tools/testing/selftests/bpf/progs/btf_module_names.c

-- 
2.34.1


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

* [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-31 13:38 [PATCH bpf-next v6 0/2] libbpf: Improve BPF load performance by selectively loading module BTFs Fuyu Zhao
@ 2026-08-31 13:38 ` Fuyu Zhao
  2026-08-31 14:46   ` bot+bpf-ci
                     ` (2 more replies)
  2026-08-31 13:38 ` [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
  1 sibling, 3 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-08-31 13:38 UTC (permalink / raw)
  To: bpf; +Cc: eddyz87, andrii.nakryiko, alan.maguire, Fuyu Zhao

Add btf_module_names and nr_btf_module_names fields to
bpf_object_open_opts to support selective kernel module BTF loading.

With btf_module_names:
- when provided, only the specified kernel module BTFs are loaded;
- when an empty list is provided, no module BTFs are loaded;
- when NULL, all module BTFs are loaded as before.

This avoids unnecessary module BTF loading and reduces BPF object
loading time when only a subset of kernel module BTFs is needed.

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

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 514e4e9daa82..e19d0afd2592 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -779,6 +779,9 @@ struct bpf_object {
 	char *token_path;
 	int token_fd;
 
+	char **btf_module_names;
+	size_t nr_btf_module_names;
+
 	char path[];
 };
 
@@ -5803,6 +5806,103 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
 	return 0;
 }
 
+static void bpf_object_free_btf_module_names(struct bpf_object *obj)
+{
+	size_t i;
+
+	if (!obj->btf_module_names)
+		return;
+
+	for (i = 0; i < obj->nr_btf_module_names; i++)
+		zfree(&obj->btf_module_names[i]);
+	zfree(&obj->btf_module_names);
+	obj->nr_btf_module_names = 0;
+}
+
+static int bpf_object_init_btf_module_names(struct bpf_object *obj,
+					    const struct bpf_object_open_opts *opts)
+{
+	const char **names;
+	size_t i, j, cnt;
+	int err;
+
+	names = OPTS_GET(opts, btf_module_names, NULL);
+	if (!names)
+		return 0;
+
+	cnt = OPTS_GET(opts, nr_btf_module_names, 0);
+
+	/*
+	 * Allocate one entry for an empty list to distinguish it from the
+	 * default behavior.
+	 */
+	obj->btf_module_names = calloc(cnt ?: 1,
+				       sizeof(*obj->btf_module_names));
+	if (!obj->btf_module_names)
+		return -ENOMEM;
+
+	for (i = 0; i < cnt; i++) {
+		if (!names[i] || !names[i][0]) {
+			pr_warn("invalid kernel module BTF name at index %zu\n", i);
+			err = -EINVAL;
+			goto err_out;
+		}
+
+		/*
+		 * The list is expected to be small, so a simple nested scan is
+		 * sufficient for duplicate detection.
+		 */
+		for (j = 0; j < i; j++) {
+			if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
+				pr_warn("duplicate kernel module BTF name '%s'\n",
+					names[i]);
+				err = -EINVAL;
+				goto err_out;
+			}
+		}
+
+		obj->btf_module_names[i] = strdup(names[i]);
+		if (!obj->btf_module_names[i]) {
+			err = -ENOMEM;
+			goto err_out;
+		}
+
+		obj->nr_btf_module_names++;
+	}
+	return 0;
+
+err_out:
+	bpf_object_free_btf_module_names(obj);
+	return err;
+}
+
+static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)
+{
+	size_t i;
+
+	if (!obj->btf_module_names)
+		return true;
+
+	for (i = 0; i < obj->nr_btf_module_names; i++) {
+		if (strcmp(obj->btf_module_names[i], name) == 0)
+			return true;
+	}
+
+	pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);
+	return false;
+}
+
+static bool no_module_btfs_needed(const struct bpf_object *obj)
+{
+	return obj->btf_module_names && !obj->nr_btf_module_names;
+}
+
+static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
+{
+	return obj->btf_module_names &&
+	       obj->nr_btf_module_names == obj->btf_module_cnt;
+}
+
 static int load_module_btfs(struct bpf_object *obj)
 {
 	struct bpf_btf_info info;
@@ -5825,6 +5925,9 @@ static int load_module_btfs(struct bpf_object *obj)
 	if (!kernel_supports(obj, FEAT_MODULE_BTF))
 		return 0;
 
+	if (no_module_btfs_needed(obj))
+		return 0;
+
 	while (true) {
 		err = bpf_btf_get_next_id(id, &id);
 		if (err && errno == ENOENT)
@@ -5867,6 +5970,11 @@ static int load_module_btfs(struct bpf_object *obj)
 			continue;
 		}
 
+		if (!is_module_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 +5999,9 @@ static int load_module_btfs(struct bpf_object *obj)
 			break;
 		}
 		obj->btf_module_cnt++;
+
+		if (all_needed_module_btfs_loaded(obj))
+			break;
 	}
 
 	if (err) {
@@ -8508,6 +8619,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 		}
 	}
 
+	err = bpf_object_init_btf_module_names(obj, opts);
+	if (err)
+		goto out;
+
 	err = bpf_object__elf_init(obj);
 	err = err ? : bpf_object__elf_collect(obj);
 	err = err ? : bpf_object__collect_externs(obj);
@@ -9629,6 +9744,8 @@ void bpf_object__close(struct bpf_object *obj)
 		close(obj->jumptable_maps[i].fd);
 	zfree(&obj->jumptable_maps);
 
+	bpf_object_free_btf_module_names(obj);
+
 	free(obj);
 }
 
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index b965ad571540..1cabf7e46554 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -224,10 +224,32 @@ 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.
+	 * nr_btf_module_names specifies the number of entries in
+	 * btf_module_names.
+	 *
+	 * With btf_module_names:
+	 * - when provided, only the BTFs of the specified modules are loaded;
+	 * - when an empty list is provided, no module BTFs are loaded;
+	 * - when NULL, all module BTFs are loaded as before.
+	 *
+	 * The list must contain valid, non-empty module names and must not
+	 * contain duplicate entries; otherwise -EINVAL is returned.
+	 *
+	 * This affects:
+	 * - BPF CO-RE relocations against types defined in modules;
+	 * - BTF-based resolution of attach targets;
+	 * - module-qualified tracing multi-attach targets;
+	 * - struct_ops kernel type resolution;
+	 * - extern (ksym) resolution for kernel symbols defined in modules.
+	 */
+	const char **btf_module_names;
+	size_t nr_btf_module_names;
 
 	size_t :0;
 };
-#define bpf_object_open_opts__last_field bpf_token_path
+#define bpf_object_open_opts__last_field nr_btf_module_names
 
 /**
  * @brief **bpf_object__open()** creates a bpf_object by opening
-- 
2.34.1


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

* [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading
  2026-08-31 13:38 [PATCH bpf-next v6 0/2] libbpf: Improve BPF load performance by selectively loading module BTFs Fuyu Zhao
  2026-08-31 13:38 ` [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
@ 2026-08-31 13:38 ` Fuyu Zhao
  2026-08-31 14:46   ` bot+bpf-ci
  2026-09-03  0:12   ` Andrii Nakryiko
  1 sibling, 2 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-08-31 13:38 UTC (permalink / raw)
  To: bpf; +Cc: eddyz87, andrii.nakryiko, alan.maguire, Fuyu Zhao

Add selftests covering selective kernel module BTF loading through
bpf_object_open_opts.

The tests verify that:
- the existing behavior is preserved when btf_module_names is not
  specified, and loading succeeds when the required module BTF is
  specified;
- a required module BTF is skipped when the module is not specified in
  btf_module_names;
- an empty btf_module_names list skips loading all module BTFs;
- invalid module BTF name lists are rejected.

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

diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
new file mode 100644
index 000000000000..68f4a8d73560
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <bpf/btf.h>
+#include "btf_module_names.skel.h"
+
+static void btf_module_names_load(void)
+{
+	struct btf_module_names *skel = NULL;
+	int ret;
+	const char *mod_names[] = { "bpf_testmod" };
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = mod_names,
+		.nr_btf_module_names = 1,
+	);
+
+	/* Verify backward compatibility. */
+	skel = btf_module_names__open_opts(NULL);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts default"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_OK(ret, "btf_module_names__load default");
+
+	btf_module_names__destroy(skel);
+	skel = NULL;
+
+	skel = btf_module_names__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_OK(ret, "btf_module_names__load");
+out:
+	btf_module_names__destroy(skel);
+}
+
+/*
+ * Verify that an unrequested module BTF is skipped. The BPF program
+ * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
+ * btf_module_names, so its BTF is skipped and the BPF program fails to load.
+ */
+static void btf_module_names_skip(void)
+{
+	struct btf_module_names *skel = NULL;
+	int ret;
+	const char *mod_names[] = { "module_nonexist" };
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = mod_names,
+		.nr_btf_module_names = 1,
+	);
+
+	skel = btf_module_names__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
+
+out:
+	btf_module_names__destroy(skel);
+}
+
+/*
+ * Verify that an empty filter skips loading all module BTFs. The BPF
+ * program requires bpf_testmod BTF, so it fails to load.
+ */
+static void btf_module_names_empty(void)
+{
+	struct btf_module_names *skel = NULL;
+	int ret;
+	const char *mod_names[] = { NULL };
+
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		/* Non-NULL pointer with zero entries represents an empty list. */
+		.btf_module_names = mod_names,
+		.nr_btf_module_names = 0,
+	);
+
+	skel = btf_module_names__open_opts(&opts);
+	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
+		goto out;
+
+	ret = btf_module_names__load(skel);
+	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
+
+out:
+	btf_module_names__destroy(skel);
+}
+
+static void btf_module_names_invalid(void)
+{
+	struct btf_module_names *skel = NULL;
+	const char *names[] = { NULL };
+	const char *empty_names[] = { "" };
+	const char *duplicate_names[] = {
+		"bpf_testmod", "bpf_testmod",
+	};
+	LIBBPF_OPTS(bpf_object_open_opts, opts,
+		.btf_module_names = names,
+		.nr_btf_module_names = 1,
+	);
+
+	skel = btf_module_names__open_opts(&opts);
+	ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+		  "btf_module_names__open_opts null");
+	btf_module_names__destroy(skel);
+
+	opts.btf_module_names = empty_names;
+	skel = btf_module_names__open_opts(&opts);
+	ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+		  "btf_module_names__open_opts empty");
+	btf_module_names__destroy(skel);
+
+	opts.btf_module_names = duplicate_names;
+	opts.nr_btf_module_names = 2;
+	skel = btf_module_names__open_opts(&opts);
+	ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
+		  "btf_module_names__open_opts duplicate name");
+	btf_module_names__destroy(skel);
+}
+
+void test_btf_module_names(void)
+{
+	struct btf *vmlinux_btf = NULL;
+	struct btf *module_btf = NULL;
+
+	if (!env.has_testmod) {
+		test__skip();
+		return;
+	}
+
+	vmlinux_btf = btf__load_vmlinux_btf();
+	if (libbpf_get_error(vmlinux_btf)) {
+		test__skip();
+		return;
+	}
+
+	/* Ensure bpf_testmod BTF is available. */
+	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
+	if (libbpf_get_error(module_btf)) {
+		btf__free(vmlinux_btf);
+		test__skip();
+		return;
+	}
+
+	btf__free(module_btf);
+	btf__free(vmlinux_btf);
+
+	if (test__start_subtest("btf_module_names_load"))
+		btf_module_names_load();
+
+	if (test__start_subtest("btf_module_names_skip"))
+		btf_module_names_skip();
+
+	if (test__start_subtest("btf_module_names_empty"))
+		btf_module_names_empty();
+
+	if (test__start_subtest("btf_module_names_invalid"))
+		btf_module_names_invalid();
+}
diff --git a/tools/testing/selftests/bpf/progs/btf_module_names.c b/tools/testing/selftests/bpf/progs/btf_module_names.c
new file mode 100644
index 000000000000..232adc0f33f6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/btf_module_names.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_btf_module_names)
+{
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.34.1


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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-31 13:38 ` [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
@ 2026-08-31 14:46   ` bot+bpf-ci
  2026-09-01  3:55     ` Fuyu Zhao
  2026-08-31 14:46   ` Alan Maguire
  2026-09-03  0:12   ` Andrii Nakryiko
  2 siblings, 1 reply; 14+ messages in thread
From: bot+bpf-ci @ 2026-08-31 14:46 UTC (permalink / raw)
  To: zhaofuyu, bpf
  Cc: eddyz87, andrii.nakryiko, alan.maguire, zhaofuyu, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

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

> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index b965ad5715403..1cabf7e465546 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -224,10 +224,32 @@ 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.
> +	 * nr_btf_module_names specifies the number of entries in
> +	 * btf_module_names.
> +	 *
> +	 * With btf_module_names:
> +	 * - when provided, only the BTFs of the specified modules are loaded;
> +	 * - when an empty list is provided, no module BTFs are loaded;
> +	 * - when NULL, all module BTFs are loaded as before.
> +	 *
> +	 * The list must contain valid, non-empty module names and must not
> +	 * contain duplicate entries; otherwise -EINVAL is returned.
> +	 *
> +	 * This affects:
> +	 * - BPF CO-RE relocations against types defined in modules;
> +	 * - BTF-based resolution of attach targets;
> +	 * - module-qualified tracing multi-attach targets;
> +	 * - struct_ops kernel type resolution;
> +	 * - extern (ksym) resolution for kernel symbols defined in modules.
> +	 */
> +	const char **btf_module_names;
> +	size_t nr_btf_module_names;
                      ^^
This isn't a bug, but would btf_module_cnt (or just cnt-style naming) fit
better here? The other opts structs in libbpf.h pair their arrays with a
cnt field: bpf_kprobe_multi_opts uses cnt, bpf_uprobe_multi_opts uses
cnt, and bpf_tracing_multi_opts uses cnt. This looks like the only nr_*
field in the public header, and the field name becomes part of the stable
public API once it ships.

>
>  	size_t :0;
>  };
> -#define bpf_object_open_opts__last_field bpf_token_path
> +#define bpf_object_open_opts__last_field nr_btf_module_names


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

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

* Re: [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading
  2026-08-31 13:38 ` [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
@ 2026-08-31 14:46   ` bot+bpf-ci
  2026-09-01  3:56     ` Fuyu Zhao
  2026-09-03  0:12   ` Andrii Nakryiko
  1 sibling, 1 reply; 14+ messages in thread
From: bot+bpf-ci @ 2026-08-31 14:46 UTC (permalink / raw)
  To: zhaofuyu, bpf
  Cc: eddyz87, andrii.nakryiko, alan.maguire, zhaofuyu, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

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

> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
> new file mode 100644
> index 0000000000000..68f4a8d735608
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
> @@ -0,0 +1,162 @@

[ ... ]

> +/*
> + * Verify that an unrequested module BTF is skipped. The BPF program
> + * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
> + * btf_module_names, so its BTF is skipped and the BPF program fails to load.
> + */
> +static void btf_module_names_skip(void)
> +{
> +	struct btf_module_names *skel = NULL;
> +	int ret;
> +	const char *mod_names[] = { "module_nonexist" };
> +
> +	LIBBPF_OPTS(bpf_object_open_opts, opts,
> +		.btf_module_names = mod_names,
> +		.nr_btf_module_names = 1,
> +	);
> +
> +	skel = btf_module_names__open_opts(&opts);
> +	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
> +		goto out;
> +
> +	ret = btf_module_names__load(skel);
> +	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
> +
> +out:
> +	btf_module_names__destroy(skel);
> +}
> +
> +/*
> + * Verify that an empty filter skips loading all module BTFs. The BPF
> + * program requires bpf_testmod BTF, so it fails to load.
> + */
> +static void btf_module_names_empty(void)
> +{
> +	struct btf_module_names *skel = NULL;
> +	int ret;
> +	const char *mod_names[] = { NULL };
> +
> +	LIBBPF_OPTS(bpf_object_open_opts, opts,
> +		/* Non-NULL pointer with zero entries represents an empty list. */
> +		.btf_module_names = mod_names,
> +		.nr_btf_module_names = 0,
> +	);
> +
> +	skel = btf_module_names__open_opts(&opts);
> +	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
> +		goto out;
> +
> +	ret = btf_module_names__load(skel);
> +	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
> +
> +out:
> +	btf_module_names__destroy(skel);
> +}

This isn't a bug, but btf_module_names_skip() and btf_module_names_empty()
look like the same body with two initializer values changed. Would a small
helper taking (names, nr, expected_ret) read better and keep the subtests
to a line apiece?

[ ... ]

> +void test_btf_module_names(void)
> +{
> +	struct btf *vmlinux_btf = NULL;
> +	struct btf *module_btf = NULL;
> +
> +	if (!env.has_testmod) {
> +		test__skip();
> +		return;
> +	}
> +
> +	vmlinux_btf = btf__load_vmlinux_btf();
> +	if (libbpf_get_error(vmlinux_btf)) {
> +		test__skip();
> +		return;
> +	}
> +
> +	/* Ensure bpf_testmod BTF is available. */
> +	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
> +	if (libbpf_get_error(module_btf)) {
> +		btf__free(vmlinux_btf);
> +		test__skip();
> +		return;
> +	}

A subsystem pattern flags this as potentially concerning: test_btf_module_names()
has three distinct prerequisite failures and all three collapse into a bare
test__skip() with no reason emitted.

The resulting log line is identical in all three cases, so a reader of a CI
result cannot tell which prerequisite was missing, and cannot distinguish
"module BTF support is not built in" from "bpf_testmod failed to load".

test__skip() takes no arguments in the BPF test framework, so the idiom for
conveying a skip reason is an adjacent printf("%s:SKIP:...", __func__) or a
trailing comment. The `if (!env.has_testmod) { test__skip(); return; }` form
is universally idiomatic in prog_tests/ and would not normally be raised.

The escalation is really about the two BTF-availability skips:

	vmlinux_btf = btf__load_vmlinux_btf();
	if (libbpf_get_error(vmlinux_btf)) {
		test__skip();
		return;
	}

	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
	if (libbpf_get_error(module_btf)) {
		btf__free(vmlinux_btf);
		test__skip();
		return;
	}

Neighbouring tests like prog_tests/btf_tag.c annotate these conditions:

	printf("%s:SKIP: btf_type_tag attribute not in vmlinux btf", __func__);
	printf("%s:SKIP: btf_type_tag attribute not in %s", __func__, module_name);

This matters more here than in a typical test because two of the four
subtests assert a *failure* code (-ESRCH), so silently skipping on a machine
without module BTF is the same observable outcome as the feature working.

Would adding skip reason strings for the BTF-availability checks help
distinguish which prerequisite was missing?

[ ... ]


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

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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-31 13:38 ` [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
  2026-08-31 14:46   ` bot+bpf-ci
@ 2026-08-31 14:46   ` Alan Maguire
  2026-09-01  3:15     ` Fuyu Zhao
  2026-09-03  0:12   ` Andrii Nakryiko
  2 siblings, 1 reply; 14+ messages in thread
From: Alan Maguire @ 2026-08-31 14:46 UTC (permalink / raw)
  To: Fuyu Zhao, bpf; +Cc: eddyz87, andrii.nakryiko



On 31/08/2026 14:38, Fuyu Zhao wrote:
> 
> Add btf_module_names and nr_btf_module_names fields to
> bpf_object_open_opts to support selective kernel module BTF loading.
> 
> With btf_module_names:
> - when provided, only the specified kernel module BTFs are loaded;
> - when an empty list is provided, no module BTFs are loaded;
> - when NULL, all module BTFs are loaded as before.
> 
> This avoids unnecessary module BTF loading and reduces BPF object
> loading time when only a subset of kernel module BTFs is needed.
> 
> Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com>
> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
> ---
>  tools/lib/bpf/libbpf.c | 117 +++++++++++++++++++++++++++++++++++++++++
>  tools/lib/bpf/libbpf.h |  24 ++++++++-
>  2 files changed, 140 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 514e4e9daa82..e19d0afd2592 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -779,6 +779,9 @@ struct bpf_object {
>  	char *token_path;
>  	int token_fd;
>  
> +	char **btf_module_names;
> +	size_t nr_btf_module_names;
> +
>  	char path[];
>  };
>  
> @@ -5803,6 +5806,103 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>  	return 0;
>  }
>  
> +static void bpf_object_free_btf_module_names(struct bpf_object *obj)
> +{
> +	size_t i;
> +
> +	if (!obj->btf_module_names)
> +		return;
> +
> +	for (i = 0; i < obj->nr_btf_module_names; i++)
> +		zfree(&obj->btf_module_names[i]);
> +	zfree(&obj->btf_module_names);
> +	obj->nr_btf_module_names = 0;
> +}
> +
> +static int bpf_object_init_btf_module_names(struct bpf_object *obj,
> +					    const struct bpf_object_open_opts *opts)
> +{
> +	const char **names;
> +	size_t i, j, cnt;
> +	int err;
> +
> +	names = OPTS_GET(opts, btf_module_names, NULL);
> +	if (!names)
> +		return 0;
> +
> +	cnt = OPTS_GET(opts, nr_btf_module_names, 0);
> +
> +	/*
> +	 * Allocate one entry for an empty list to distinguish it from the
> +	 * default behavior.
> +	 */
> +	obj->btf_module_names = calloc(cnt ?: 1,
> +				       sizeof(*obj->btf_module_names));
> +	if (!obj->btf_module_names)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < cnt; i++) {
> +		if (!names[i] || !names[i][0]) {
> +			pr_warn("invalid kernel module BTF name at index %zu\n", i);
> +			err = -EINVAL;
> +			goto err_out;
> +		}
> +
> +		/*
> +		 * The list is expected to be small, so a simple nested scan is
> +		 * sufficient for duplicate detection.
> +		 */
> +		for (j = 0; j < i; j++) {
> +			if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
> +				pr_warn("duplicate kernel module BTF name '%s'\n",
> +					names[i]);
> +				err = -EINVAL;
> +				goto err_out;
> +			}
> +		}
> +
> +		obj->btf_module_names[i] = strdup(names[i]);
> +		if (!obj->btf_module_names[i]) {
> +			err = -ENOMEM;
> +			goto err_out;
> +		}
> +
> +		obj->nr_btf_module_names++;
> +	}
> +	return 0;
> +
> +err_out:
> +	bpf_object_free_btf_module_names(obj);
> +	return err;
> +}
> +
> +static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)
> +{
> +	size_t i;
> +
> +	if (!obj->btf_module_names)
> +		return true;
> +
> +	for (i = 0; i < obj->nr_btf_module_names; i++) {
> +		if (strcmp(obj->btf_module_names[i], name) == 0)
> +			return true;
> +	}
> +
> +	pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);
> +	return false;
> +}
> +
> +static bool no_module_btfs_needed(const struct bpf_object *obj)
> +{
> +	return obj->btf_module_names && !obj->nr_btf_module_names;
> +}
> +
> +static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
> +{
> +	return obj->btf_module_names &&
> +	       obj->nr_btf_module_names == obj->btf_module_cnt;
> +}
> +
>  static int load_module_btfs(struct bpf_object *obj)
>  {
>  	struct bpf_btf_info info;
> @@ -5825,6 +5925,9 @@ static int load_module_btfs(struct bpf_object *obj)
>  	if (!kernel_supports(obj, FEAT_MODULE_BTF))
>  		return 0;
>  
> +	if (no_module_btfs_needed(obj))
> +		return 0;
> +
>  	while (true) {
>  		err = bpf_btf_get_next_id(id, &id);
>  		if (err && errno == ENOENT)
> @@ -5867,6 +5970,11 @@ static int load_module_btfs(struct bpf_object *obj)
>  			continue;
>  		}
>  
> +		if (!is_module_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 +5999,9 @@ static int load_module_btfs(struct bpf_object *obj)
>  			break;
>  		}
>  		obj->btf_module_cnt++;
> +
> +		if (all_needed_module_btfs_loaded(obj))
> +			break;
>  	}
>  

I was looking at the selftest in patch 2 and was a bit confused as to why
specifying just "nonexistent_module" in the module list was a way to test 
the scenario where we fail to specify a module that is needed ("bpf_testmod"
in that case). I think I see the answer here - we don't look at the module
count and check it against the number we asked for after the loop completes.
In the case of "nonexistent_module" our obj->btf_module_cnt is 0. Since 
all_needed_module_btfs_loaded() will evaluate to false in that case,
we keep iterating over BTF ids.

On loop exit shouldn't we check obj->btf_module_cnt in the case we asked
for specific modules, and make sure it matches the requested number?
  
>  	if (err) {
> @@ -8508,6 +8619,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>  		}
>  	}
>  
> +	err = bpf_object_init_btf_module_names(obj, opts);
> +	if (err)
> +		goto out;
> +
>  	err = bpf_object__elf_init(obj);
>  	err = err ? : bpf_object__elf_collect(obj);
>  	err = err ? : bpf_object__collect_externs(obj);
> @@ -9629,6 +9744,8 @@ void bpf_object__close(struct bpf_object *obj)
>  		close(obj->jumptable_maps[i].fd);
>  	zfree(&obj->jumptable_maps);
>  
> +	bpf_object_free_btf_module_names(obj);
> +
>  	free(obj);
>  }
>  
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index b965ad571540..1cabf7e46554 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -224,10 +224,32 @@ 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.
> +	 * nr_btf_module_names specifies the number of entries in
> +	 * btf_module_names.
> +	 *
> +	 * With btf_module_names:
> +	 * - when provided, only the BTFs of the specified modules are loaded;
> +	 * - when an empty list is provided, no module BTFs are loaded;
> +	 * - when NULL, all module BTFs are loaded as before.
> +	 *
> +	 * The list must contain valid, non-empty module names and must not
> +	 * contain duplicate entries; otherwise -EINVAL is returned.
> +	 *
> +	 * This affects:
> +	 * - BPF CO-RE relocations against types defined in modules;
> +	 * - BTF-based resolution of attach targets;
> +	 * - module-qualified tracing multi-attach targets;
> +	 * - struct_ops kernel type resolution;
> +	 * - extern (ksym) resolution for kernel symbols defined in modules.
> +	 */
> +	const char **btf_module_names;
> +	size_t nr_btf_module_names;
>  
>  	size_t :0;
>  };
> -#define bpf_object_open_opts__last_field bpf_token_path
> +#define bpf_object_open_opts__last_field nr_btf_module_names
>  
>  /**
>   * @brief **bpf_object__open()** creates a bpf_object by opening
> -- 
> 2.34.1
> 


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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-31 14:46   ` Alan Maguire
@ 2026-09-01  3:15     ` Fuyu Zhao
  2026-09-01  7:33       ` Alan Maguire
  0 siblings, 1 reply; 14+ messages in thread
From: Fuyu Zhao @ 2026-09-01  3:15 UTC (permalink / raw)
  To: Alan Maguire, bpf; +Cc: eddyz87, andrii.nakryiko



在 2026/8/31 22:46, Alan Maguire 写道:
> 
> 
> On 31/08/2026 14:38, Fuyu Zhao wrote:
>>
>> Add btf_module_names and nr_btf_module_names fields to
>> bpf_object_open_opts to support selective kernel module BTF loading.
>>
>> With btf_module_names:
>> - when provided, only the specified kernel module BTFs are loaded;
>> - when an empty list is provided, no module BTFs are loaded;
>> - when NULL, all module BTFs are loaded as before.
>>
>> This avoids unnecessary module BTF loading and reduces BPF object
>> loading time when only a subset of kernel module BTFs is needed.
>>
>> Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com>
>> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
>> ---
>>  tools/lib/bpf/libbpf.c | 117 +++++++++++++++++++++++++++++++++++++++++
>>  tools/lib/bpf/libbpf.h |  24 ++++++++-
>>  2 files changed, 140 insertions(+), 1 deletion(-)
>>
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index 514e4e9daa82..e19d0afd2592 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
>> @@ -779,6 +779,9 @@ struct bpf_object {
>>  	char *token_path;
>>  	int token_fd;
>>  
>> +	char **btf_module_names;
>> +	size_t nr_btf_module_names;
>> +
>>  	char path[];
>>  };
>>  
>> @@ -5803,6 +5806,103 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>>  	return 0;
>>  }
>>  
>> +static void bpf_object_free_btf_module_names(struct bpf_object *obj)
>> +{
>> +	size_t i;
>> +
>> +	if (!obj->btf_module_names)
>> +		return;
>> +
>> +	for (i = 0; i < obj->nr_btf_module_names; i++)
>> +		zfree(&obj->btf_module_names[i]);
>> +	zfree(&obj->btf_module_names);
>> +	obj->nr_btf_module_names = 0;
>> +}
>> +
>> +static int bpf_object_init_btf_module_names(struct bpf_object *obj,
>> +					    const struct bpf_object_open_opts *opts)
>> +{
>> +	const char **names;
>> +	size_t i, j, cnt;
>> +	int err;
>> +
>> +	names = OPTS_GET(opts, btf_module_names, NULL);
>> +	if (!names)
>> +		return 0;
>> +
>> +	cnt = OPTS_GET(opts, nr_btf_module_names, 0);
>> +
>> +	/*
>> +	 * Allocate one entry for an empty list to distinguish it from the
>> +	 * default behavior.
>> +	 */
>> +	obj->btf_module_names = calloc(cnt ?: 1,
>> +				       sizeof(*obj->btf_module_names));
>> +	if (!obj->btf_module_names)
>> +		return -ENOMEM;
>> +
>> +	for (i = 0; i < cnt; i++) {
>> +		if (!names[i] || !names[i][0]) {
>> +			pr_warn("invalid kernel module BTF name at index %zu\n", i);
>> +			err = -EINVAL;
>> +			goto err_out;
>> +		}
>> +
>> +		/*
>> +		 * The list is expected to be small, so a simple nested scan is
>> +		 * sufficient for duplicate detection.
>> +		 */
>> +		for (j = 0; j < i; j++) {
>> +			if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
>> +				pr_warn("duplicate kernel module BTF name '%s'\n",
>> +					names[i]);
>> +				err = -EINVAL;
>> +				goto err_out;
>> +			}
>> +		}
>> +
>> +		obj->btf_module_names[i] = strdup(names[i]);
>> +		if (!obj->btf_module_names[i]) {
>> +			err = -ENOMEM;
>> +			goto err_out;
>> +		}
>> +
>> +		obj->nr_btf_module_names++;
>> +	}
>> +	return 0;
>> +
>> +err_out:
>> +	bpf_object_free_btf_module_names(obj);
>> +	return err;
>> +}
>> +
>> +static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)
>> +{
>> +	size_t i;
>> +
>> +	if (!obj->btf_module_names)
>> +		return true;
>> +
>> +	for (i = 0; i < obj->nr_btf_module_names; i++) {
>> +		if (strcmp(obj->btf_module_names[i], name) == 0)
>> +			return true;
>> +	}
>> +
>> +	pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);
>> +	return false;
>> +}
>> +
>> +static bool no_module_btfs_needed(const struct bpf_object *obj)
>> +{
>> +	return obj->btf_module_names && !obj->nr_btf_module_names;
>> +}
>> +
>> +static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
>> +{
>> +	return obj->btf_module_names &&
>> +	       obj->nr_btf_module_names == obj->btf_module_cnt;
>> +}
>> +
>>  static int load_module_btfs(struct bpf_object *obj)
>>  {
>>  	struct bpf_btf_info info;
>> @@ -5825,6 +5925,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>  	if (!kernel_supports(obj, FEAT_MODULE_BTF))
>>  		return 0;
>>  
>> +	if (no_module_btfs_needed(obj))
>> +		return 0;
>> +
>>  	while (true) {
>>  		err = bpf_btf_get_next_id(id, &id);
>>  		if (err && errno == ENOENT)
>> @@ -5867,6 +5970,11 @@ static int load_module_btfs(struct bpf_object *obj)
>>  			continue;
>>  		}
>>  
>> +		if (!is_module_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 +5999,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>  			break;
>>  		}
>>  		obj->btf_module_cnt++;
>> +
>> +		if (all_needed_module_btfs_loaded(obj))
>> +			break;
>>  	}
>>  
> 
> I was looking at the selftest in patch 2 and was a bit confused as to why
> specifying just "nonexistent_module" in the module list was a way to test 
> the scenario where we fail to specify a module that is needed ("bpf_testmod"
> in that case). I think I see the answer here - we don't look at the module
> count and check it against the number we asked for after the loop completes.
> In the case of "nonexistent_module" our obj->btf_module_cnt is 0. Since 
> all_needed_module_btfs_loaded() will evaluate to false in that case,
> we keep iterating over BTF ids.
> 
> On loop exit shouldn't we check obj->btf_module_cnt in the case we asked
> for specific modules, and make sure it matches the requested number?
>   

Sorry for causing some confusion here. My original design was to
treat `btf_module_names` as a filter rather than a list of required
modules. Even if the number of requested and loaded module BTFs does
not match after the loop, BPF program loading will still fail after
`load_module_btfs()` if a required module BTF is missing.

However, your question made me realize that this behavior could be
made more explicit. It seems better to detect the mismatch and fail
here, rather than defer the failure to subsequent processing in
libbpf. I'll add this check and update the corresponding selftests in
the next version.

Thanks,
Fuyu

>>  	if (err) {
>> @@ -8508,6 +8619,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>>  		}
>>  	}
>>  
>> +	err = bpf_object_init_btf_module_names(obj, opts);
>> +	if (err)
>> +		goto out;
>> +
>>  	err = bpf_object__elf_init(obj);
>>  	err = err ? : bpf_object__elf_collect(obj);
>>  	err = err ? : bpf_object__collect_externs(obj);
>> @@ -9629,6 +9744,8 @@ void bpf_object__close(struct bpf_object *obj)
>>  		close(obj->jumptable_maps[i].fd);
>>  	zfree(&obj->jumptable_maps);
>>  
>> +	bpf_object_free_btf_module_names(obj);
>> +
>>  	free(obj);
>>  }
>>  
>> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
>> index b965ad571540..1cabf7e46554 100644
>> --- a/tools/lib/bpf/libbpf.h
>> +++ b/tools/lib/bpf/libbpf.h
>> @@ -224,10 +224,32 @@ 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.
>> +	 * nr_btf_module_names specifies the number of entries in
>> +	 * btf_module_names.
>> +	 *
>> +	 * With btf_module_names:
>> +	 * - when provided, only the BTFs of the specified modules are loaded;
>> +	 * - when an empty list is provided, no module BTFs are loaded;
>> +	 * - when NULL, all module BTFs are loaded as before.
>> +	 *
>> +	 * The list must contain valid, non-empty module names and must not
>> +	 * contain duplicate entries; otherwise -EINVAL is returned.
>> +	 *
>> +	 * This affects:
>> +	 * - BPF CO-RE relocations against types defined in modules;
>> +	 * - BTF-based resolution of attach targets;
>> +	 * - module-qualified tracing multi-attach targets;
>> +	 * - struct_ops kernel type resolution;
>> +	 * - extern (ksym) resolution for kernel symbols defined in modules.
>> +	 */
>> +	const char **btf_module_names;
>> +	size_t nr_btf_module_names;
>>  
>>  	size_t :0;
>>  };
>> -#define bpf_object_open_opts__last_field bpf_token_path
>> +#define bpf_object_open_opts__last_field nr_btf_module_names
>>  
>>  /**
>>   * @brief **bpf_object__open()** creates a bpf_object by opening
>> -- 
>> 2.34.1
>>
> 


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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-31 14:46   ` bot+bpf-ci
@ 2026-09-01  3:55     ` Fuyu Zhao
  0 siblings, 0 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-09-01  3:55 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: eddyz87, andrii.nakryiko, alan.maguire, ast, andrii, daniel,
	martin.lau, yonghong.song, mason, ihor.solodrai



On 8/31/2026 10:46 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
>> index b965ad5715403..1cabf7e465546 100644
>> --- a/tools/lib/bpf/libbpf.h
>> +++ b/tools/lib/bpf/libbpf.h
>> @@ -224,10 +224,32 @@ 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.
>> +	 * nr_btf_module_names specifies the number of entries in
>> +	 * btf_module_names.
>> +	 *
>> +	 * With btf_module_names:
>> +	 * - when provided, only the BTFs of the specified modules are loaded;
>> +	 * - when an empty list is provided, no module BTFs are loaded;
>> +	 * - when NULL, all module BTFs are loaded as before.
>> +	 *
>> +	 * The list must contain valid, non-empty module names and must not
>> +	 * contain duplicate entries; otherwise -EINVAL is returned.
>> +	 *
>> +	 * This affects:
>> +	 * - BPF CO-RE relocations against types defined in modules;
>> +	 * - BTF-based resolution of attach targets;
>> +	 * - module-qualified tracing multi-attach targets;
>> +	 * - struct_ops kernel type resolution;
>> +	 * - extern (ksym) resolution for kernel symbols defined in modules.
>> +	 */
>> +	const char **btf_module_names;
>> +	size_t nr_btf_module_names;
>                       ^^
> This isn't a bug, but would btf_module_cnt (or just cnt-style naming) fit
> better here? The other opts structs in libbpf.h pair their arrays with a
> cnt field: bpf_kprobe_multi_opts uses cnt, bpf_uprobe_multi_opts uses
> cnt, and bpf_tracing_multi_opts uses cnt. This looks like the only nr_*
> field in the public header, and the field name becomes part of the stable
> public API once it ships.
> 

I agree that a cnt-style name is more consistent here. I won't use
`btf_module_cnt`, though, as it could be confused with
`obj->btf_module_cnt`. I'll use `btf_module_names_cnt` instead to keep
the distinction clear.

>>
>>  	size_t :0;
>>  };
>> -#define bpf_object_open_opts__last_field bpf_token_path
>> +#define bpf_object_open_opts__last_field nr_btf_module_names
> 
> 
> ---
> 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/33399468751


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

* Re: [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading
  2026-08-31 14:46   ` bot+bpf-ci
@ 2026-09-01  3:56     ` Fuyu Zhao
  0 siblings, 0 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-09-01  3:56 UTC (permalink / raw)
  To: bot+bpf-ci, bpf
  Cc: eddyz87, andrii.nakryiko, alan.maguire, ast, andrii, daniel,
	martin.lau, yonghong.song, mason, ihor.solodrai



On 8/31/2026 10:46 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
>> new file mode 100644
>> index 0000000000000..68f4a8d735608
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
>> @@ -0,0 +1,162 @@
> 
> [ ... ]
> 
>> +/*
>> + * Verify that an unrequested module BTF is skipped. The BPF program
>> + * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
>> + * btf_module_names, so its BTF is skipped and the BPF program fails to load.
>> + */
>> +static void btf_module_names_skip(void)
>> +{
>> +	struct btf_module_names *skel = NULL;
>> +	int ret;
>> +	const char *mod_names[] = { "module_nonexist" };
>> +
>> +	LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +		.btf_module_names = mod_names,
>> +		.nr_btf_module_names = 1,
>> +	);
>> +
>> +	skel = btf_module_names__open_opts(&opts);
>> +	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
>> +		goto out;
>> +
>> +	ret = btf_module_names__load(skel);
>> +	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
>> +
>> +out:
>> +	btf_module_names__destroy(skel);
>> +}
>> +
>> +/*
>> + * Verify that an empty filter skips loading all module BTFs. The BPF
>> + * program requires bpf_testmod BTF, so it fails to load.
>> + */
>> +static void btf_module_names_empty(void)
>> +{
>> +	struct btf_module_names *skel = NULL;
>> +	int ret;
>> +	const char *mod_names[] = { NULL };
>> +
>> +	LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +		/* Non-NULL pointer with zero entries represents an empty list. */
>> +		.btf_module_names = mod_names,
>> +		.nr_btf_module_names = 0,
>> +	);
>> +
>> +	skel = btf_module_names__open_opts(&opts);
>> +	if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
>> +		goto out;
>> +
>> +	ret = btf_module_names__load(skel);
>> +	ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
>> +
>> +out:
>> +	btf_module_names__destroy(skel);
>> +}
> 
> This isn't a bug, but btf_module_names_skip() and btf_module_names_empty()
> look like the same body with two initializer values changed. Would a small
> helper taking (names, nr, expected_ret) read better and keep the subtests
> to a line apiece?
> 

This makes sense. I'll add a small helper to share the common test
logic between these subtests.

> [ ... ]
> 
>> +void test_btf_module_names(void)
>> +{
>> +	struct btf *vmlinux_btf = NULL;
>> +	struct btf *module_btf = NULL;
>> +
>> +	if (!env.has_testmod) {
>> +		test__skip();
>> +		return;
>> +	}
>> +
>> +	vmlinux_btf = btf__load_vmlinux_btf();
>> +	if (libbpf_get_error(vmlinux_btf)) {
>> +		test__skip();
>> +		return;
>> +	}
>> +
>> +	/* Ensure bpf_testmod BTF is available. */
>> +	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
>> +	if (libbpf_get_error(module_btf)) {
>> +		btf__free(vmlinux_btf);
>> +		test__skip();
>> +		return;
>> +	}
> 
> A subsystem pattern flags this as potentially concerning: test_btf_module_names()
> has three distinct prerequisite failures and all three collapse into a bare
> test__skip() with no reason emitted.
> 
> The resulting log line is identical in all three cases, so a reader of a CI
> result cannot tell which prerequisite was missing, and cannot distinguish
> "module BTF support is not built in" from "bpf_testmod failed to load".
> 
> test__skip() takes no arguments in the BPF test framework, so the idiom for
> conveying a skip reason is an adjacent printf("%s:SKIP:...", __func__) or a
> trailing comment. The `if (!env.has_testmod) { test__skip(); return; }` form
> is universally idiomatic in prog_tests/ and would not normally be raised.
> 
> The escalation is really about the two BTF-availability skips:
> 
> 	vmlinux_btf = btf__load_vmlinux_btf();
> 	if (libbpf_get_error(vmlinux_btf)) {
> 		test__skip();
> 		return;
> 	}
> 
> 	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
> 	if (libbpf_get_error(module_btf)) {
> 		btf__free(vmlinux_btf);
> 		test__skip();
> 		return;
> 	}
> 
> Neighbouring tests like prog_tests/btf_tag.c annotate these conditions:
> 
> 	printf("%s:SKIP: btf_type_tag attribute not in vmlinux btf", __func__);
> 	printf("%s:SKIP: btf_type_tag attribute not in %s", __func__, module_name);
> 
> This matters more here than in a typical test because two of the four
> subtests assert a *failure* code (-ESRCH), so silently skipping on a machine
> without module BTF is the same observable outcome as the feature working.
> 
> Would adding skip reason strings for the BTF-availability checks help
> distinguish which prerequisite was missing?
> 

Good point. I'll add skip reasons for the vmlinux and bpf_testmod BTF
availability checks.

Thanks,
Fuyu

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


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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-09-01  3:15     ` Fuyu Zhao
@ 2026-09-01  7:33       ` Alan Maguire
  0 siblings, 0 replies; 14+ messages in thread
From: Alan Maguire @ 2026-09-01  7:33 UTC (permalink / raw)
  To: Fuyu Zhao, bpf; +Cc: eddyz87, andrii.nakryiko

On 01/09/2026 04:15, Fuyu Zhao wrote:
> 在 2026/8/31 22:46, Alan Maguire 写道:
>> 
>> 
>> On 31/08/2026 14:38, Fuyu Zhao wrote:
>>>
>>> Add btf_module_names and nr_btf_module_names fields to
>>> bpf_object_open_opts to support selective kernel module BTF loading.
>>>
>>> With btf_module_names:
>>> - when provided, only the specified kernel module BTFs are loaded;
>>> - when an empty list is provided, no module BTFs are loaded;
>>> - when NULL, all module BTFs are loaded as before.
>>>
>>> This avoids unnecessary module BTF loading and reduces BPF object
>>> loading time when only a subset of kernel module BTFs is needed.
>>>
>>> Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com>
>>> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
>>> ---
>>>  tools/lib/bpf/libbpf.c | 117 +++++++++++++++++++++++++++++++++++++++++
>>>  tools/lib/bpf/libbpf.h |  24 ++++++++-
>>>  2 files changed, 140 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>>> index 514e4e9daa82..e19d0afd2592 100644
>>> --- a/tools/lib/bpf/libbpf.c
>>> +++ b/tools/lib/bpf/libbpf.c
>>> @@ -779,6 +779,9 @@ struct bpf_object {
>>>  	char *token_path;
>>>  	int token_fd;
>>>  
>>> +	char **btf_module_names;
>>> +	size_t nr_btf_module_names;
>>> +
>>>  	char path[];
>>>  };
>>>  
>>> @@ -5803,6 +5806,103 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>>>  	return 0;
>>>  }
>>>  
>>> +static void bpf_object_free_btf_module_names(struct bpf_object *obj)
>>> +{
>>> +	size_t i;
>>> +
>>> +	if (!obj->btf_module_names)
>>> +		return;
>>> +
>>> +	for (i = 0; i < obj->nr_btf_module_names; i++)
>>> +		zfree(&obj->btf_module_names[i]);
>>> +	zfree(&obj->btf_module_names);
>>> +	obj->nr_btf_module_names = 0;
>>> +}
>>> +
>>> +static int bpf_object_init_btf_module_names(struct bpf_object *obj,
>>> +					    const struct bpf_object_open_opts *opts)
>>> +{
>>> +	const char **names;
>>> +	size_t i, j, cnt;
>>> +	int err;
>>> +
>>> +	names = OPTS_GET(opts, btf_module_names, NULL);
>>> +	if (!names)
>>> +		return 0;
>>> +
>>> +	cnt = OPTS_GET(opts, nr_btf_module_names, 0);
>>> +
>>> +	/*
>>> +	 * Allocate one entry for an empty list to distinguish it from the
>>> +	 * default behavior.
>>> +	 */
>>> +	obj->btf_module_names = calloc(cnt ?: 1,
>>> +				       sizeof(*obj->btf_module_names));
>>> +	if (!obj->btf_module_names)
>>> +		return -ENOMEM;
>>> +
>>> +	for (i = 0; i < cnt; i++) {
>>> +		if (!names[i] || !names[i][0]) {
>>> +			pr_warn("invalid kernel module BTF name at index %zu\n", i);
>>> +			err = -EINVAL;
>>> +			goto err_out;
>>> +		}
>>> +
>>> +		/*
>>> +		 * The list is expected to be small, so a simple nested scan is
>>> +		 * sufficient for duplicate detection.
>>> +		 */
>>> +		for (j = 0; j < i; j++) {
>>> +			if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
>>> +				pr_warn("duplicate kernel module BTF name '%s'\n",
>>> +					names[i]);
>>> +				err = -EINVAL;
>>> +				goto err_out;
>>> +			}
>>> +		}
>>> +
>>> +		obj->btf_module_names[i] = strdup(names[i]);
>>> +		if (!obj->btf_module_names[i]) {
>>> +			err = -ENOMEM;
>>> +			goto err_out;
>>> +		}
>>> +
>>> +		obj->nr_btf_module_names++;
>>> +	}
>>> +	return 0;
>>> +
>>> +err_out:
>>> +	bpf_object_free_btf_module_names(obj);
>>> +	return err;
>>> +}
>>> +
>>> +static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)
>>> +{
>>> +	size_t i;
>>> +
>>> +	if (!obj->btf_module_names)
>>> +		return true;
>>> +
>>> +	for (i = 0; i < obj->nr_btf_module_names; i++) {
>>> +		if (strcmp(obj->btf_module_names[i], name) == 0)
>>> +			return true;
>>> +	}
>>> +
>>> +	pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);
>>> +	return false;
>>> +}
>>> +
>>> +static bool no_module_btfs_needed(const struct bpf_object *obj)
>>> +{
>>> +	return obj->btf_module_names && !obj->nr_btf_module_names;
>>> +}
>>> +
>>> +static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
>>> +{
>>> +	return obj->btf_module_names &&
>>> +	       obj->nr_btf_module_names == obj->btf_module_cnt;
>>> +}
>>> +
>>>  static int load_module_btfs(struct bpf_object *obj)
>>>  {
>>>  	struct bpf_btf_info info;
>>> @@ -5825,6 +5925,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>>  	if (!kernel_supports(obj, FEAT_MODULE_BTF))
>>>  		return 0;
>>>  
>>> +	if (no_module_btfs_needed(obj))
>>> +		return 0;
>>> +
>>>  	while (true) {
>>>  		err = bpf_btf_get_next_id(id, &id);
>>>  		if (err && errno == ENOENT)
>>> @@ -5867,6 +5970,11 @@ static int load_module_btfs(struct bpf_object *obj)
>>>  			continue;
>>>  		}
>>>  
>>> +		if (!is_module_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 +5999,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>>  			break;
>>>  		}
>>>  		obj->btf_module_cnt++;
>>> +
>>> +		if (all_needed_module_btfs_loaded(obj))
>>> +			break;
>>>  	}
>>>  
>> 
>> I was looking at the selftest in patch 2 and was a bit confused as to why
>> specifying just "nonexistent_module" in the module list was a way to test 
>> the scenario where we fail to specify a module that is needed ("bpf_testmod"
>> in that case). I think I see the answer here - we don't look at the module
>> count and check it against the number we asked for after the loop completes.
>> In the case of "nonexistent_module" our obj->btf_module_cnt is 0. Since 
>> all_needed_module_btfs_loaded() will evaluate to false in that case,
>> we keep iterating over BTF ids.
>> 
>> On loop exit shouldn't we check obj->btf_module_cnt in the case we asked
>> for specific modules, and make sure it matches the requested number?
>>   
> 
> Sorry for causing some confusion here. My original design was to
> treat `btf_module_names` as a filter rather than a list of required
> modules. Even if the number of requested and loaded module BTFs does
> not match after the loop, BPF program loading will still fail after
> `load_module_btfs()` if a required module BTF is missing.
> 
> However, your question made me realize that this behavior could be
> made more explicit. It seems better to detect the mismatch and fail
> here, rather than defer the failure to subsequent processing in
> libbpf. I'll add this check and update the corresponding selftests in
> the next version.
>

Sounds good. Thinking about it, it might be good to retain the non-existent
module tolerance optionally somehow (a required_module_cnt perhaps?) since it's
often the case that a module is built-in on one kernel and a module on
another. If a module is builtin ("y" instead of "m"), having a way to say 
"load this if it is a module but don't fail if it is not present" would be handy,
since the needed BTF would then be in vmlinux BTF rather than module BTF.
So in such a case the nonexistent_module test would fail if 
required_cnt == module_cnt == 1, but not if required_cnt = 0.

Others may disagree so let's wait to see if anyone else has opinions on this,
but in general it may be useful to have flexibility around module BTF presence
as it varies greatly across configs.

Thanks!

Alan

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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-08-31 13:38 ` [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
  2026-08-31 14:46   ` bot+bpf-ci
  2026-08-31 14:46   ` Alan Maguire
@ 2026-09-03  0:12   ` Andrii Nakryiko
  2026-09-03 12:52     ` Fuyu Zhao
  2 siblings, 1 reply; 14+ messages in thread
From: Andrii Nakryiko @ 2026-09-03  0:12 UTC (permalink / raw)
  To: Fuyu Zhao; +Cc: bpf, eddyz87, alan.maguire

On Mon, Aug 31, 2026 at 6:38 AM Fuyu Zhao <zhaofuyu@vivo.com> wrote:
>
> Add btf_module_names and nr_btf_module_names fields to
> bpf_object_open_opts to support selective kernel module BTF loading.
>
> With btf_module_names:
> - when provided, only the specified kernel module BTFs are loaded;
> - when an empty list is provided, no module BTFs are loaded;
> - when NULL, all module BTFs are loaded as before.
>
> This avoids unnecessary module BTF loading and reduces BPF object
> loading time when only a subset of kernel module BTFs is needed.
>
> Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com>

nit: use andrii@kernel.org as my email, thanks

> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
> ---
>  tools/lib/bpf/libbpf.c | 117 +++++++++++++++++++++++++++++++++++++++++
>  tools/lib/bpf/libbpf.h |  24 ++++++++-
>  2 files changed, 140 insertions(+), 1 deletion(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 514e4e9daa82..e19d0afd2592 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -779,6 +779,9 @@ struct bpf_object {
>         char *token_path;
>         int token_fd;
>
> +       char **btf_module_names;
> +       size_t nr_btf_module_names;

there is entire module related set of fields higher up, put these new
fields close to them

but also naming-wise:
  - use blah_cnt instead of nr_blah naming
  - we should call this "btf_module_allowlist" and
btf_module_allowlist_cnt, a bit verbose but explains what it is and
leaves doors open for denylist, if we ever need it. Please use these
names internally in bpf_object and in bpf_object_open_opts

overall, the code as is super verbose and doesn't feel like libbpf
code, please try to preserve naming/styling/succinct approach as much
as possible

> +
>         char path[];
>  };
>
> @@ -5803,6 +5806,103 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>         return 0;
>  }
>
> +static void bpf_object_free_btf_module_names(struct bpf_object *obj)
> +{
> +       size_t i;
> +
> +       if (!obj->btf_module_names)
> +               return;
> +

no need for this check, count should be zero and everything works as
is without extra guard

> +       for (i = 0; i < obj->nr_btf_module_names; i++)
> +               zfree(&obj->btf_module_names[i]);
> +       zfree(&obj->btf_module_names);
> +       obj->nr_btf_module_names = 0;

just inline this logic in bpf_object__close()

> +}
> +
> +static int bpf_object_init_btf_module_names(struct bpf_object *obj,
> +                                           const struct bpf_object_open_opts *opts)

if you look at bpf_object_open(), all the opts processing is done
inline , let's do the same here. we can do validation and duplicate
detection before we even get to bpf_object__new. and alloc and strdup
loop after obj is created

pw-bot: cr

> +{
> +       const char **names;
> +       size_t i, j, cnt;
> +       int err;
> +
> +       names = OPTS_GET(opts, btf_module_names, NULL);
> +       if (!names)
> +               return 0;

don't exit early, we need to validate that if allowlist is not
specified, corresponding count is zero and vice versa

> +
> +       cnt = OPTS_GET(opts, nr_btf_module_names, 0);
> +
> +       /*
> +        * Allocate one entry for an empty list to distinguish it from the
> +        * default behavior.
> +        */
> +       obj->btf_module_names = calloc(cnt ?: 1,

this count trick is kind of ugly... should we just set count to <0
instead? we'll still be able to do for loop without any extra
handling, but -1 vs 0 for count will be a sign of having no or empty
allowlist

> +                                      sizeof(*obj->btf_module_names));
> +       if (!obj->btf_module_names)
> +               return -ENOMEM;
> +
> +       for (i = 0; i < cnt; i++) {
> +               if (!names[i] || !names[i][0]) {
> +                       pr_warn("invalid kernel module BTF name at index %zu\n", i);

I don't think this will be some common mistake, let's drop all this
verbosity with pt_warn and so on, just error out

> +                       err = -EINVAL;
> +                       goto err_out;
> +               }
> +
> +               /*
> +                * The list is expected to be small, so a simple nested scan is
> +                * sufficient for duplicate detection.
> +                */

unnecessary comment, drop it

> +               for (j = 0; j < i; j++) {
> +                       if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
> +                               pr_warn("duplicate kernel module BTF name '%s'\n",
> +                                       names[i]);

ditto, just error out, no pr_warn()

> +                               err = -EINVAL;
> +                               goto err_out;
> +                       }
> +               }
> +
> +               obj->btf_module_names[i] = strdup(names[i]);
> +               if (!obj->btf_module_names[i]) {
> +                       err = -ENOMEM;
> +                       goto err_out;
> +               }
> +
> +               obj->nr_btf_module_names++;
> +       }
> +       return 0;
> +
> +err_out:
> +       bpf_object_free_btf_module_names(obj);

I don't think we need to clean up here, bpf_object__close() will be
closed and will take care of this cleanup, keep the logic simple

> +       return err;
> +}
> +
> +static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)

let's call this is_btf_mod_allowed()

> +{
> +       size_t i;
> +
> +       if (!obj->btf_module_names)
> +               return true;
> +
> +       for (i = 0; i < obj->nr_btf_module_names; i++) {
> +               if (strcmp(obj->btf_module_names[i], name) == 0)
> +                       return true;
> +       }
> +
> +       pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);

drop this pr_debug(), do you think it will be very useful? this whole
feature feels super niche, whoever is going to use it should know what
they are doing, so I find it unlikely that they might be surprised by
ignoring kernel module they explicitly excluded

> +       return false;
> +}
> +
> +static bool no_module_btfs_needed(const struct bpf_object *obj)
> +{
> +       return obj->btf_module_names && !obj->nr_btf_module_names;
> +}
> +
> +static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
> +{
> +       return obj->btf_module_names &&
> +              obj->nr_btf_module_names == obj->btf_module_cnt;
> +}
> +

these two helpers are just verbosity, let's get rid of them and do
corresponding checks inline

>  static int load_module_btfs(struct bpf_object *obj)
>  {
>         struct bpf_btf_info info;
> @@ -5825,6 +5925,9 @@ static int load_module_btfs(struct bpf_object *obj)
>         if (!kernel_supports(obj, FEAT_MODULE_BTF))
>                 return 0;
>
> +       if (no_module_btfs_needed(obj))
> +               return 0;

under new convention, it will be `if (btf_mod_allowlist_cnt == 0) return 0;`

> +
>         while (true) {
>                 err = bpf_btf_get_next_id(id, &id);
>                 if (err && errno == ENOENT)
> @@ -5867,6 +5970,11 @@ static int load_module_btfs(struct bpf_object *obj)
>                         continue;
>                 }
>
> +               if (!is_module_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 +5999,9 @@ static int load_module_btfs(struct bpf_object *obj)
>                         break;
>                 }
>                 obj->btf_module_cnt++;
> +
> +               if (all_needed_module_btfs_loaded(obj))
> +                       break;
>         }
>
>         if (err) {
> @@ -8508,6 +8619,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>                 }
>         }
>
> +       err = bpf_object_init_btf_module_names(obj, opts);
> +       if (err)
> +               goto out;
> +
>         err = bpf_object__elf_init(obj);
>         err = err ? : bpf_object__elf_collect(obj);
>         err = err ? : bpf_object__collect_externs(obj);
> @@ -9629,6 +9744,8 @@ void bpf_object__close(struct bpf_object *obj)
>                 close(obj->jumptable_maps[i].fd);
>         zfree(&obj->jumptable_maps);
>
> +       bpf_object_free_btf_module_names(obj);
> +

just inline that simple clean up logic here without helper function

>         free(obj);
>  }
>
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index b965ad571540..1cabf7e46554 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -224,10 +224,32 @@ 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.
> +        * nr_btf_module_names specifies the number of entries in
> +        * btf_module_names.

not really, it's not "what should be loaded" but what is allowed to be
loaded. quite a difference, because with "should be loaded" we should
be failing if we don't find all those modules, but I don't think that
was the intent, was it?

> +        *
> +        * With btf_module_names:
> +        * - when provided, only the BTFs of the specified modules are loaded;
> +        * - when an empty list is provided, no module BTFs are loaded;
> +        * - when NULL, all module BTFs are loaded as before.
> +        *

also mention that count should be zero for empty or NULL list

> +        * The list must contain valid, non-empty module names and must not
> +        * contain duplicate entries; otherwise -EINVAL is returned.
> +        *
> +        * This affects:

just say that this allowlist affects any place where kernel module BTF
might be needed by limiting which kernel module BTFs libbpf will
consult. no need to enumerate all the different places where we use
BTF, it will get out of sync too easily.

> +        * - BPF CO-RE relocations against types defined in modules;
> +        * - BTF-based resolution of attach targets;
> +        * - module-qualified tracing multi-attach targets;
> +        * - struct_ops kernel type resolution;
> +        * - extern (ksym) resolution for kernel symbols defined in modules.
> +        */
> +       const char **btf_module_names;
> +       size_t nr_btf_module_names;
>
>         size_t :0;
>  };
> -#define bpf_object_open_opts__last_field bpf_token_path
> +#define bpf_object_open_opts__last_field nr_btf_module_names
>
>  /**
>   * @brief **bpf_object__open()** creates a bpf_object by opening
> --
> 2.34.1
>

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

* Re: [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading
  2026-08-31 13:38 ` [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
  2026-08-31 14:46   ` bot+bpf-ci
@ 2026-09-03  0:12   ` Andrii Nakryiko
  2026-09-03 12:52     ` Fuyu Zhao
  1 sibling, 1 reply; 14+ messages in thread
From: Andrii Nakryiko @ 2026-09-03  0:12 UTC (permalink / raw)
  To: Fuyu Zhao; +Cc: bpf, eddyz87, alan.maguire

On Mon, Aug 31, 2026 at 6:38 AM Fuyu Zhao <zhaofuyu@vivo.com> wrote:
>
> Add selftests covering selective kernel module BTF loading through
> bpf_object_open_opts.
>
> The tests verify that:
> - the existing behavior is preserved when btf_module_names is not
>   specified, and loading succeeds when the required module BTF is
>   specified;
> - a required module BTF is skipped when the module is not specified in
>   btf_module_names;
> - an empty btf_module_names list skips loading all module BTFs;
> - invalid module BTF name lists are rejected.
>
> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
> ---
>  .../bpf/prog_tests/btf_module_names.c         | 162 ++++++++++++++++++
>  .../selftests/bpf/progs/btf_module_names.c    |  13 ++
>  2 files changed, 175 insertions(+)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_module_names.c
>  create mode 100644 tools/testing/selftests/bpf/progs/btf_module_names.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
> new file mode 100644
> index 000000000000..68f4a8d73560
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
> @@ -0,0 +1,162 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <test_progs.h>
> +#include <bpf/btf.h>
> +#include "btf_module_names.skel.h"
> +
> +static void btf_module_names_load(void)
> +{
> +       struct btf_module_names *skel = NULL;
> +       int ret;
> +       const char *mod_names[] = { "bpf_testmod" };
> +
> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
> +               .btf_module_names = mod_names,
> +               .nr_btf_module_names = 1,
> +       );
> +
> +       /* Verify backward compatibility. */
> +       skel = btf_module_names__open_opts(NULL);
> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts default"))
> +               goto out;
> +
> +       ret = btf_module_names__load(skel);
> +       ASSERT_OK(ret, "btf_module_names__load default");
> +
> +       btf_module_names__destroy(skel);
> +       skel = NULL;
> +
> +       skel = btf_module_names__open_opts(&opts);
> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
> +               goto out;
> +
> +       ret = btf_module_names__load(skel);
> +       ASSERT_OK(ret, "btf_module_names__load");
> +out:
> +       btf_module_names__destroy(skel);
> +}
> +
> +/*
> + * Verify that an unrequested module BTF is skipped. The BPF program
> + * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
> + * btf_module_names, so its BTF is skipped and the BPF program fails to load.
> + */
> +static void btf_module_names_skip(void)
> +{
> +       struct btf_module_names *skel = NULL;
> +       int ret;
> +       const char *mod_names[] = { "module_nonexist" };
> +
> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
> +               .btf_module_names = mod_names,
> +               .nr_btf_module_names = 1,
> +       );
> +
> +       skel = btf_module_names__open_opts(&opts);
> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
> +               goto out;
> +
> +       ret = btf_module_names__load(skel);
> +       ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
> +
> +out:
> +       btf_module_names__destroy(skel);
> +}
> +
> +/*
> + * Verify that an empty filter skips loading all module BTFs. The BPF
> + * program requires bpf_testmod BTF, so it fails to load.
> + */
> +static void btf_module_names_empty(void)
> +{
> +       struct btf_module_names *skel = NULL;
> +       int ret;
> +       const char *mod_names[] = { NULL };
> +
> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
> +               /* Non-NULL pointer with zero entries represents an empty list. */
> +               .btf_module_names = mod_names,
> +               .nr_btf_module_names = 0,
> +       );
> +
> +       skel = btf_module_names__open_opts(&opts);
> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
> +               goto out;
> +
> +       ret = btf_module_names__load(skel);
> +       ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
> +
> +out:
> +       btf_module_names__destroy(skel);
> +}
> +
> +static void btf_module_names_invalid(void)
> +{
> +       struct btf_module_names *skel = NULL;
> +       const char *names[] = { NULL };
> +       const char *empty_names[] = { "" };
> +       const char *duplicate_names[] = {
> +               "bpf_testmod", "bpf_testmod",
> +       };
> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
> +               .btf_module_names = names,
> +               .nr_btf_module_names = 1,
> +       );
> +
> +       skel = btf_module_names__open_opts(&opts);
> +       ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
> +                 "btf_module_names__open_opts null");
> +       btf_module_names__destroy(skel);
> +
> +       opts.btf_module_names = empty_names;
> +       skel = btf_module_names__open_opts(&opts);
> +       ASSERT_EQ(libbpf_get_error(skel), -EINVAL,

please don't use libbpf_get_error(), this is legacy and is
discouraged. check that skell is null and then look at errno


> +                 "btf_module_names__open_opts empty");
> +       btf_module_names__destroy(skel);
> +
> +       opts.btf_module_names = duplicate_names;
> +       opts.nr_btf_module_names = 2;
> +       skel = btf_module_names__open_opts(&opts);
> +       ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
> +                 "btf_module_names__open_opts duplicate name");
> +       btf_module_names__destroy(skel);
> +}
> +
> +void test_btf_module_names(void)
> +{
> +       struct btf *vmlinux_btf = NULL;
> +       struct btf *module_btf = NULL;
> +
> +       if (!env.has_testmod) {
> +               test__skip();
> +               return;
> +       }
> +
> +       vmlinux_btf = btf__load_vmlinux_btf();
> +       if (libbpf_get_error(vmlinux_btf)) {
> +               test__skip();
> +               return;
> +       }
> +
> +       /* Ensure bpf_testmod BTF is available. */
> +       module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
> +       if (libbpf_get_error(module_btf)) {
> +               btf__free(vmlinux_btf);
> +               test__skip();
> +               return;
> +       }
> +
> +       btf__free(module_btf);
> +       btf__free(vmlinux_btf);
> +
> +       if (test__start_subtest("btf_module_names_load"))
> +               btf_module_names_load();
> +
> +       if (test__start_subtest("btf_module_names_skip"))
> +               btf_module_names_skip();
> +
> +       if (test__start_subtest("btf_module_names_empty"))
> +               btf_module_names_empty();
> +
> +       if (test__start_subtest("btf_module_names_invalid"))
> +               btf_module_names_invalid();
> +}
> diff --git a/tools/testing/selftests/bpf/progs/btf_module_names.c b/tools/testing/selftests/bpf/progs/btf_module_names.c
> new file mode 100644
> index 000000000000..232adc0f33f6
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/btf_module_names.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_btf_module_names)
> +{
> +       return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";
> --
> 2.34.1
>

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

* Re: [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts
  2026-09-03  0:12   ` Andrii Nakryiko
@ 2026-09-03 12:52     ` Fuyu Zhao
  0 siblings, 0 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-09-03 12:52 UTC (permalink / raw)
  To: Andrii Nakryiko; +Cc: bpf, eddyz87, alan.maguire



On 9/3/2026 8:12 AM, Andrii Nakryiko wrote:
> On Mon, Aug 31, 2026 at 6:38 AM Fuyu Zhao <zhaofuyu@vivo.com> wrote:
>>
>> Add btf_module_names and nr_btf_module_names fields to
>> bpf_object_open_opts to support selective kernel module BTF loading.
>>
>> With btf_module_names:
>> - when provided, only the specified kernel module BTFs are loaded;
>> - when an empty list is provided, no module BTFs are loaded;
>> - when NULL, all module BTFs are loaded as before.
>>
>> This avoids unnecessary module BTF loading and reduces BPF object
>> loading time when only a subset of kernel module BTFs is needed.
>>
>> Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com>
> 
> nit: use andrii@kernel.org as my email, thanks
> 

Will do.

>> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
>> ---
>>  tools/lib/bpf/libbpf.c | 117 +++++++++++++++++++++++++++++++++++++++++
>>  tools/lib/bpf/libbpf.h |  24 ++++++++-
>>  2 files changed, 140 insertions(+), 1 deletion(-)
>>
>> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
>> index 514e4e9daa82..e19d0afd2592 100644
>> --- a/tools/lib/bpf/libbpf.c
>> +++ b/tools/lib/bpf/libbpf.c
>> @@ -779,6 +779,9 @@ struct bpf_object {
>>         char *token_path;
>>         int token_fd;
>>
>> +       char **btf_module_names;
>> +       size_t nr_btf_module_names;
> 
> there is entire module related set of fields higher up, put these new
> fields close to them
> 

Sure, I'll move them next to the existing module-related fields.

> but also naming-wise:
>   - use blah_cnt instead of nr_blah naming
>   - we should call this "btf_module_allowlist" and
> btf_module_allowlist_cnt, a bit verbose but explains what it is and
> leaves doors open for denylist, if we ever need it. Please use these
> names internally in bpf_object and in bpf_object_open_opts
> 

Makes sense. I'll rename them to btf_module_allowlist and
btf_module_allowlist_cnt.

> overall, the code as is super verbose and doesn't feel like libbpf
> code, please try to preserve naming/styling/succinct approach as much
> as possible
> 

Understood. I'll simplify the implementation and keep it closer to the
existing libbpf style.

>> +
>>         char path[];
>>  };
>>
>> @@ -5803,6 +5806,103 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>>         return 0;
>>  }
>>
>> +static void bpf_object_free_btf_module_names(struct bpf_object *obj)
>> +{
>> +       size_t i;
>> +
>> +       if (!obj->btf_module_names)
>> +               return;
>> +
> 
> no need for this check, count should be zero and everything works as
> is without extra guard
> 

Right, I'll drop it.

>> +       for (i = 0; i < obj->nr_btf_module_names; i++)
>> +               zfree(&obj->btf_module_names[i]);
>> +       zfree(&obj->btf_module_names);
>> +       obj->nr_btf_module_names = 0;
> 
> just inline this logic in bpf_object__close()
> 

Sure.

>> +}
>> +
>> +static int bpf_object_init_btf_module_names(struct bpf_object *obj,
>> +                                           const struct bpf_object_open_opts *opts)
> 
> if you look at bpf_object_open(), all the opts processing is done
> inline , let's do the same here. we can do validation and duplicate
> detection before we even get to bpf_object__new. and alloc and strdup
> loop after obj is created
> 

Agreed.

> pw-bot: cr
> 
>> +{
>> +       const char **names;
>> +       size_t i, j, cnt;
>> +       int err;
>> +
>> +       names = OPTS_GET(opts, btf_module_names, NULL);
>> +       if (!names)
>> +               return 0;
> 
> don't exit early, we need to validate that if allowlist is not
> specified, corresponding count is zero and vice versa
> 

Understood.

>> +
>> +       cnt = OPTS_GET(opts, nr_btf_module_names, 0);
>> +
>> +       /*
>> +        * Allocate one entry for an empty list to distinguish it from the
>> +        * default behavior.
>> +        */
>> +       obj->btf_module_names = calloc(cnt ?: 1,
> 
> this count trick is kind of ugly... should we just set count to <0
> instead? we'll still be able to do for loop without any extra
> handling, but -1 vs 0 for count will be a sign of having no or empty
> allowlist
> 

Yes, setting count to -1 can better address this issue.

>> +                                      sizeof(*obj->btf_module_names));
>> +       if (!obj->btf_module_names)
>> +               return -ENOMEM;
>> +
>> +       for (i = 0; i < cnt; i++) {
>> +               if (!names[i] || !names[i][0]) {
>> +                       pr_warn("invalid kernel module BTF name at index %zu\n", i);
> 
> I don't think this will be some common mistake, let's drop all this
> verbosity with pt_warn and so on, just error out
> 

Sure, I'll remove the warning and return the error directly.

>> +                       err = -EINVAL;
>> +                       goto err_out;
>> +               }
>> +
>> +               /*
>> +                * The list is expected to be small, so a simple nested scan is
>> +                * sufficient for duplicate detection.
>> +                */
> 
> unnecessary comment, drop it
> 

Got it.

>> +               for (j = 0; j < i; j++) {
>> +                       if (strcmp(obj->btf_module_names[j], names[i]) == 0) {
>> +                               pr_warn("duplicate kernel module BTF name '%s'\n",
>> +                                       names[i]);
> 
> ditto, just error out, no pr_warn()
> 

Will do.

>> +                               err = -EINVAL;
>> +                               goto err_out;
>> +                       }
>> +               }
>> +
>> +               obj->btf_module_names[i] = strdup(names[i]);
>> +               if (!obj->btf_module_names[i]) {
>> +                       err = -ENOMEM;
>> +                       goto err_out;
>> +               }
>> +
>> +               obj->nr_btf_module_names++;
>> +       }
>> +       return 0;
>> +
>> +err_out:
>> +       bpf_object_free_btf_module_names(obj);
> 
> I don't think we need to clean up here, bpf_object__close() will be
> closed and will take care of this cleanup, keep the logic simple
> 

Agreed.

>> +       return err;
>> +}
>> +
>> +static bool is_module_btf_needed(const struct bpf_object *obj, const char *name)
> 
> let's call this is_btf_mod_allowed()
> 

Sure, I'll rename it to is_btf_mod_allowed().

>> +{
>> +       size_t i;
>> +
>> +       if (!obj->btf_module_names)
>> +               return true;
>> +
>> +       for (i = 0; i < obj->nr_btf_module_names; i++) {
>> +               if (strcmp(obj->btf_module_names[i], name) == 0)
>> +                       return true;
>> +       }
>> +
>> +       pr_debug("skipping module BTF '%s', not in btf_module_names\n", name);
> 
> drop this pr_debug(), do you think it will be very useful? this whole
> feature feels super niche, whoever is going to use it should know what
> they are doing, so I find it unlikely that they might be surprised by
> ignoring kernel module they explicitly excluded
> 

Makes sense. I'll remove it.

>> +       return false;
>> +}
>> +
>> +static bool no_module_btfs_needed(const struct bpf_object *obj)
>> +{
>> +       return obj->btf_module_names && !obj->nr_btf_module_names;
>> +}
>> +
>> +static bool all_needed_module_btfs_loaded(const struct bpf_object *obj)
>> +{
>> +       return obj->btf_module_names &&
>> +              obj->nr_btf_module_names == obj->btf_module_cnt;
>> +}
>> +
> 
> these two helpers are just verbosity, let's get rid of them and do
> corresponding checks inline
> 

Understood. I'll remove both helpers and inline the checks.

>>  static int load_module_btfs(struct bpf_object *obj)
>>  {
>>         struct bpf_btf_info info;
>> @@ -5825,6 +5925,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>         if (!kernel_supports(obj, FEAT_MODULE_BTF))
>>                 return 0;
>>
>> +       if (no_module_btfs_needed(obj))
>> +               return 0;
> 
> under new convention, it will be `if (btf_mod_allowlist_cnt == 0) return 0;`
> 

Right. I'll handle it with the new convention.

>> +
>>         while (true) {
>>                 err = bpf_btf_get_next_id(id, &id);
>>                 if (err && errno == ENOENT)
>> @@ -5867,6 +5970,11 @@ static int load_module_btfs(struct bpf_object *obj)
>>                         continue;
>>                 }
>>
>> +               if (!is_module_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 +5999,9 @@ static int load_module_btfs(struct bpf_object *obj)
>>                         break;
>>                 }
>>                 obj->btf_module_cnt++;
>> +
>> +               if (all_needed_module_btfs_loaded(obj))
>> +                       break;
>>         }
>>
>>         if (err) {
>> @@ -8508,6 +8619,10 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>>                 }
>>         }
>>
>> +       err = bpf_object_init_btf_module_names(obj, opts);
>> +       if (err)
>> +               goto out;
>> +
>>         err = bpf_object__elf_init(obj);
>>         err = err ? : bpf_object__elf_collect(obj);
>>         err = err ? : bpf_object__collect_externs(obj);
>> @@ -9629,6 +9744,8 @@ void bpf_object__close(struct bpf_object *obj)
>>                 close(obj->jumptable_maps[i].fd);
>>         zfree(&obj->jumptable_maps);
>>
>> +       bpf_object_free_btf_module_names(obj);
>> +
> 
> just inline that simple clean up logic here without helper function
> 

Will do. I'll inline this.

>>         free(obj);
>>  }
>>
>> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
>> index b965ad571540..1cabf7e46554 100644
>> --- a/tools/lib/bpf/libbpf.h
>> +++ b/tools/lib/bpf/libbpf.h
>> @@ -224,10 +224,32 @@ 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.
>> +        * nr_btf_module_names specifies the number of entries in
>> +        * btf_module_names.
> 
> not really, it's not "what should be loaded" but what is allowed to be
> loaded. quite a difference, because with "should be loaded" we should
> be failing if we don't find all those modules, but I don't think that
> was the intent, was it?
> 

Yes, exactly as you said.

>> +        *
>> +        * With btf_module_names:
>> +        * - when provided, only the BTFs of the specified modules are loaded;
>> +        * - when an empty list is provided, no module BTFs are loaded;
>> +        * - when NULL, all module BTFs are loaded as before.
>> +        *
> 
> also mention that count should be zero for empty or NULL list
> 

Sure, I'll clarify the count requirement in the documentation.

>> +        * The list must contain valid, non-empty module names and must not
>> +        * contain duplicate entries; otherwise -EINVAL is returned.
>> +        *
>> +        * This affects:
> 
> just say that this allowlist affects any place where kernel module BTF
> might be needed by limiting which kernel module BTFs libbpf will
> consult. no need to enumerate all the different places where we use
> BTF, it will get out of sync too easily.
> 

Makes sense. I'll simplify the documentation to describe the general
allowlist behavior.

>> +        * - BPF CO-RE relocations against types defined in modules;
>> +        * - BTF-based resolution of attach targets;
>> +        * - module-qualified tracing multi-attach targets;
>> +        * - struct_ops kernel type resolution;
>> +        * - extern (ksym) resolution for kernel symbols defined in modules.
>> +        */
>> +       const char **btf_module_names;
>> +       size_t nr_btf_module_names;
>>
>>         size_t :0;
>>  };
>> -#define bpf_object_open_opts__last_field bpf_token_path
>> +#define bpf_object_open_opts__last_field nr_btf_module_names
>>
>>  /**
>>   * @brief **bpf_object__open()** creates a bpf_object by opening
>> --
>> 2.34.1
>>


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

* Re: [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading
  2026-09-03  0:12   ` Andrii Nakryiko
@ 2026-09-03 12:52     ` Fuyu Zhao
  0 siblings, 0 replies; 14+ messages in thread
From: Fuyu Zhao @ 2026-09-03 12:52 UTC (permalink / raw)
  To: Andrii Nakryiko; +Cc: bpf, eddyz87, alan.maguire



On 9/3/2026 8:12 AM, Andrii Nakryiko wrote:
> On Mon, Aug 31, 2026 at 6:38 AM Fuyu Zhao <zhaofuyu@vivo.com> wrote:
>>
>> Add selftests covering selective kernel module BTF loading through
>> bpf_object_open_opts.
>>
>> The tests verify that:
>> - the existing behavior is preserved when btf_module_names is not
>>   specified, and loading succeeds when the required module BTF is
>>   specified;
>> - a required module BTF is skipped when the module is not specified in
>>   btf_module_names;
>> - an empty btf_module_names list skips loading all module BTFs;
>> - invalid module BTF name lists are rejected.
>>
>> Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
>> ---
>>  .../bpf/prog_tests/btf_module_names.c         | 162 ++++++++++++++++++
>>  .../selftests/bpf/progs/btf_module_names.c    |  13 ++
>>  2 files changed, 175 insertions(+)
>>  create mode 100644 tools/testing/selftests/bpf/prog_tests/btf_module_names.c
>>  create mode 100644 tools/testing/selftests/bpf/progs/btf_module_names.c
>>
>> diff --git a/tools/testing/selftests/bpf/prog_tests/btf_module_names.c b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
>> new file mode 100644
>> index 000000000000..68f4a8d73560
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c
>> @@ -0,0 +1,162 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +#include <test_progs.h>
>> +#include <bpf/btf.h>
>> +#include "btf_module_names.skel.h"
>> +
>> +static void btf_module_names_load(void)
>> +{
>> +       struct btf_module_names *skel = NULL;
>> +       int ret;
>> +       const char *mod_names[] = { "bpf_testmod" };
>> +
>> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +               .btf_module_names = mod_names,
>> +               .nr_btf_module_names = 1,
>> +       );
>> +
>> +       /* Verify backward compatibility. */
>> +       skel = btf_module_names__open_opts(NULL);
>> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts default"))
>> +               goto out;
>> +
>> +       ret = btf_module_names__load(skel);
>> +       ASSERT_OK(ret, "btf_module_names__load default");
>> +
>> +       btf_module_names__destroy(skel);
>> +       skel = NULL;
>> +
>> +       skel = btf_module_names__open_opts(&opts);
>> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
>> +               goto out;
>> +
>> +       ret = btf_module_names__load(skel);
>> +       ASSERT_OK(ret, "btf_module_names__load");
>> +out:
>> +       btf_module_names__destroy(skel);
>> +}
>> +
>> +/*
>> + * Verify that an unrequested module BTF is skipped. The BPF program
>> + * requires the BTF of bpf_testmod, but bpf_testmod is not specified in
>> + * btf_module_names, so its BTF is skipped and the BPF program fails to load.
>> + */
>> +static void btf_module_names_skip(void)
>> +{
>> +       struct btf_module_names *skel = NULL;
>> +       int ret;
>> +       const char *mod_names[] = { "module_nonexist" };
>> +
>> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +               .btf_module_names = mod_names,
>> +               .nr_btf_module_names = 1,
>> +       );
>> +
>> +       skel = btf_module_names__open_opts(&opts);
>> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts"))
>> +               goto out;
>> +
>> +       ret = btf_module_names__load(skel);
>> +       ASSERT_EQ(ret, -ESRCH, "btf_module_names__load");
>> +
>> +out:
>> +       btf_module_names__destroy(skel);
>> +}
>> +
>> +/*
>> + * Verify that an empty filter skips loading all module BTFs. The BPF
>> + * program requires bpf_testmod BTF, so it fails to load.
>> + */
>> +static void btf_module_names_empty(void)
>> +{
>> +       struct btf_module_names *skel = NULL;
>> +       int ret;
>> +       const char *mod_names[] = { NULL };
>> +
>> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +               /* Non-NULL pointer with zero entries represents an empty list. */
>> +               .btf_module_names = mod_names,
>> +               .nr_btf_module_names = 0,
>> +       );
>> +
>> +       skel = btf_module_names__open_opts(&opts);
>> +       if (!ASSERT_OK_PTR(skel, "btf_module_names__open_opts empty"))
>> +               goto out;
>> +
>> +       ret = btf_module_names__load(skel);
>> +       ASSERT_EQ(ret, -ESRCH, "btf_module_names__load empty");
>> +
>> +out:
>> +       btf_module_names__destroy(skel);
>> +}
>> +
>> +static void btf_module_names_invalid(void)
>> +{
>> +       struct btf_module_names *skel = NULL;
>> +       const char *names[] = { NULL };
>> +       const char *empty_names[] = { "" };
>> +       const char *duplicate_names[] = {
>> +               "bpf_testmod", "bpf_testmod",
>> +       };
>> +       LIBBPF_OPTS(bpf_object_open_opts, opts,
>> +               .btf_module_names = names,
>> +               .nr_btf_module_names = 1,
>> +       );
>> +
>> +       skel = btf_module_names__open_opts(&opts);
>> +       ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
>> +                 "btf_module_names__open_opts null");
>> +       btf_module_names__destroy(skel);
>> +
>> +       opts.btf_module_names = empty_names;
>> +       skel = btf_module_names__open_opts(&opts);
>> +       ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
> 
> please don't use libbpf_get_error(), this is legacy and is
> discouraged. check that skell is null and then look at errno
> 
> 

Sure, I'll switch to checking skel and errno instead.

Thanks for taking the time to review my code.
Fuyu

>> +                 "btf_module_names__open_opts empty");
>> +       btf_module_names__destroy(skel);
>> +
>> +       opts.btf_module_names = duplicate_names;
>> +       opts.nr_btf_module_names = 2;
>> +       skel = btf_module_names__open_opts(&opts);
>> +       ASSERT_EQ(libbpf_get_error(skel), -EINVAL,
>> +                 "btf_module_names__open_opts duplicate name");
>> +       btf_module_names__destroy(skel);
>> +}
>> +
>> +void test_btf_module_names(void)
>> +{
>> +       struct btf *vmlinux_btf = NULL;
>> +       struct btf *module_btf = NULL;
>> +
>> +       if (!env.has_testmod) {
>> +               test__skip();
>> +               return;
>> +       }
>> +
>> +       vmlinux_btf = btf__load_vmlinux_btf();
>> +       if (libbpf_get_error(vmlinux_btf)) {
>> +               test__skip();
>> +               return;
>> +       }
>> +
>> +       /* Ensure bpf_testmod BTF is available. */
>> +       module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
>> +       if (libbpf_get_error(module_btf)) {
>> +               btf__free(vmlinux_btf);
>> +               test__skip();
>> +               return;
>> +       }
>> +
>> +       btf__free(module_btf);
>> +       btf__free(vmlinux_btf);
>> +
>> +       if (test__start_subtest("btf_module_names_load"))
>> +               btf_module_names_load();
>> +
>> +       if (test__start_subtest("btf_module_names_skip"))
>> +               btf_module_names_skip();
>> +
>> +       if (test__start_subtest("btf_module_names_empty"))
>> +               btf_module_names_empty();
>> +
>> +       if (test__start_subtest("btf_module_names_invalid"))
>> +               btf_module_names_invalid();
>> +}
>> diff --git a/tools/testing/selftests/bpf/progs/btf_module_names.c b/tools/testing/selftests/bpf/progs/btf_module_names.c
>> new file mode 100644
>> index 000000000000..232adc0f33f6
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/btf_module_names.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_btf_module_names)
>> +{
>> +       return 0;
>> +}
>> +
>> +char _license[] SEC("license") = "GPL";
>> --
>> 2.34.1
>>


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

end of thread, other threads:[~2026-09-03 12:52 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 13:38 [PATCH bpf-next v6 0/2] libbpf: Improve BPF load performance by selectively loading module BTFs Fuyu Zhao
2026-08-31 13:38 ` [PATCH bpf-next v6 1/2] libbpf: support selective kernel module BTF loading via bpf_object_open_opts Fuyu Zhao
2026-08-31 14:46   ` bot+bpf-ci
2026-09-01  3:55     ` Fuyu Zhao
2026-08-31 14:46   ` Alan Maguire
2026-09-01  3:15     ` Fuyu Zhao
2026-09-01  7:33       ` Alan Maguire
2026-09-03  0:12   ` Andrii Nakryiko
2026-09-03 12:52     ` Fuyu Zhao
2026-08-31 13:38 ` [PATCH bpf-next v6 2/2] selftests/bpf: add tests for selective module BTF loading Fuyu Zhao
2026-08-31 14:46   ` bot+bpf-ci
2026-09-01  3:56     ` Fuyu Zhao
2026-09-03  0:12   ` Andrii Nakryiko
2026-09-03 12:52     ` Fuyu Zhao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox