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

Currently, during BPF object loading, load_module_btfs() unconditionally
iterates over all 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.

This series introduces an optional ".kmod_btfs" ELF section, allowing BPF
programs to declare the modules whose BTFs should be loaded by
load_module_btfs(). libbpf uses this information to skip unrelated module
BTFs and stops the iteration early once all declared modules are found.
Without this section, the existing behavior remains unchanged.

Performance impact (<skel>__open_and_load() time):

  Modules loaded | Without .kmod_btfs | With .kmod_btfs | Speedup
  ---------------|--------------------|-----------------|--------
  1              | 35.0 ms            | 35.0 ms         | Baseline
  10             | 36.5 ms            | 35.1 ms         | +3.8%
  100            | 46.2 ms            | 35.9 ms         | +22.3%
  300            | 65.2 ms            | 38.0 ms         | +41.7%
  
There are use cases where BPF programs are loaded on demand rather than
as a one-time setup. In our Android testing, the impact is even more
pronounced: the total BPF loading time exceeds 300 ms with 93 module
BTFs, with loading module BTFs accounting for around 69% of the total
loading time. Preloading all BPF programs is not suitable in this case,
as unused programs should remain unloaded to minimize memory usage.

The existing module qualification in SEC(), such as
SEC("fentry/mymod:foo"), is only supported for BPF program types with
explicit attach targets. It only guides find_kernel_btf_id() to search
for the BTF ID in the specified module after load_module_btfs(), and
does not reduce the number of module BTFs loaded by load_module_btfs().

Changelog:
v2:
- 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 .kmod_btfs
    section
  selftests/bpf: add tests for selective kmod BTF loading

 tools/lib/bpf/bpf_helpers.h                   |  14 +++
 tools/lib/bpf/libbpf.c                        | 106 ++++++++++++++++++
 .../selftests/bpf/prog_tests/kmod_btfs.c      |  52 +++++++++
 tools/testing/selftests/bpf/progs/kmod_btfs.c |  14 +++
 .../selftests/bpf/progs/kmod_btfs_mix.c       |  15 +++
 .../selftests/bpf/progs/kmod_btfs_nonexist.c  |  18 +++
 6 files changed, 219 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs_mix.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs_nonexist.c

-- 
2.34.1


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

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

Add support for a new ELF section ".kmod_btfs" that allows BPF programs
to declare which kernel modules need BTF loading. This avoids loading
all module BTFs and speeds up program load when only a subset of modules
is needed.

Specifically, this patch introduces two internal functions:
  - bpf_object__collect_kmod_btf_names(): parses the ".kmod_btfs"
    section and collects the declared module names.
  - is_kmod_btf_needed(): determines whether a given module's BTF
    should be loaded, allowing libbpf to skip unneeded modules.

Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
---
 tools/lib/bpf/bpf_helpers.h |  14 +++++
 tools/lib/bpf/libbpf.c      | 106 ++++++++++++++++++++++++++++++++++++
 2 files changed, 120 insertions(+)

diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
index 9d160b5b9c0e..171ea055cd32 100644
--- a/tools/lib/bpf/bpf_helpers.h
+++ b/tools/lib/bpf/bpf_helpers.h
@@ -188,6 +188,20 @@ enum libbpf_tristate {
 	TRI_MODULE = 2,
 };
 
+/* Helper typedef for declaring kernel module names that need BTF loading.
+ *
+ * Usage: define an array in the ".kmod_btfs" ELF section to specify
+ * which modules need BTF loading:
+ *
+ *   DEFINE_KMOD_BTFS(_needed_kmods)= { "module1", "module2", ... };
+ *
+ * This avoids unnecessary BTF loading and speeds up the BPF program
+ * load process.
+ */
+#define KMOD_NAME_LEN 64
+#define DEFINE_KMOD_BTFS(name) \
+	SEC(".kmod_btfs") char name[][KMOD_NAME_LEN]
+
 #define __kconfig __attribute__((section(".kconfig")))
 #define __ksym __attribute__((section(".ksyms")))
 #define __kptr_untrusted __attribute__((btf_type_tag("kptr_untrusted")))
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 514e4e9daa82..9fe8a653e8b6 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -548,6 +548,7 @@ struct bpf_struct_ops {
 #define STRUCT_OPS_SEC ".struct_ops"
 #define STRUCT_OPS_LINK_SEC ".struct_ops.link"
 #define ARENA_SEC ".addr_space.1"
+#define KMODS_BTFS_SEC ".kmod_btfs"
 
 enum libbpf_map_type {
 	LIBBPF_MAP_UNSPEC,
@@ -703,6 +704,9 @@ enum bpf_object_state {
 	OBJ_LOADED,
 };
 
+/* Should match the definition in bpf_helpers.h */
+#define KMOD_NAME_LEN 64
+
 struct bpf_object {
 	char name[BPF_OBJ_NAME_LEN];
 	char license[64];
@@ -779,6 +783,14 @@ struct bpf_object {
 	char *token_path;
 	int token_fd;
 
+	/* kernel module BTFs to load, declared in ".kmod_btfs" ELF section */
+	struct {
+		char (*data)[KMOD_NAME_LEN];
+		size_t nr_names;
+		size_t nr_loaded;
+		struct hashmap *hashmap;
+	} *kmod_btfs;
+
 	char path[];
 };
 
@@ -901,6 +913,75 @@ bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
 	return -ENOMEM;
 }
 
+static size_t mod_name_hash_fn(long key, void *ctx)
+{
+	return str_hash((char *)key);
+}
+
+static bool mod_name_equal_fn(long key1, long key2, void *ctx)
+{
+	return strcmp((char *)key1, (char *)key2) == 0;
+}
+
+static int
+bpf_object__collect_kmod_btf_names(struct bpf_object *obj, Elf_Data *sec_data,
+				   const char *sec_name)
+{
+	int module_cnt, i, err = 0;
+
+	if (obj->kmod_btfs) {
+		pr_warn("sec '%s': duplicate sections detected\n", sec_name);
+		return -EEXIST;
+	}
+
+	if (sec_data->d_size % KMOD_NAME_LEN != 0) {
+		pr_warn("sec '%s': size %zu should be multiple of %d\n",
+			sec_name, sec_data->d_size, KMOD_NAME_LEN);
+		return -EINVAL;
+	}
+
+	module_cnt = sec_data->d_size / KMOD_NAME_LEN;
+	obj->kmod_btfs = calloc(1, sizeof(*obj->kmod_btfs));
+	if (!obj->kmod_btfs)
+		return -ENOMEM;
+
+	obj->kmod_btfs->data = calloc(module_cnt, KMOD_NAME_LEN);
+	if (!obj->kmod_btfs->data) {
+		err = -ENOMEM;
+		goto err_out;
+	}
+	memcpy(obj->kmod_btfs->data, sec_data->d_buf, sec_data->d_size);
+
+	obj->kmod_btfs->hashmap = hashmap__new(mod_name_hash_fn,
+					       mod_name_equal_fn, NULL);
+	if (IS_ERR(obj->kmod_btfs->hashmap)) {
+		err = PTR_ERR(obj->kmod_btfs->hashmap);
+		goto err_out;
+	}
+
+	for (i = 0; i < module_cnt; i++) {
+		obj->kmod_btfs->data[i][KMOD_NAME_LEN - 1] = '\0';
+		if (hashmap__find(obj->kmod_btfs->hashmap,
+				  obj->kmod_btfs->data[i], NULL)) {
+			pr_warn("sec '%s': ignored duplicate module '%s'\n",
+				sec_name, obj->kmod_btfs->data[i]);
+			continue;
+		}
+		err = hashmap__set(obj->kmod_btfs->hashmap, obj->kmod_btfs->data[i],
+				   0, NULL, NULL);
+		if (err)
+			goto err_out;
+		obj->kmod_btfs->nr_names++;
+	}
+	return 0;
+
+err_out:
+	hashmap__free(obj->kmod_btfs->hashmap);
+	zfree(&obj->kmod_btfs->data);
+	zfree(&obj->kmod_btfs);
+	return err;
+}
+
 static int
 bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
 			 const char *sec_name, int sec_idx)
@@ -4034,6 +4115,10 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
 				memcpy(obj->jumptables_data, data->d_buf, data->d_size);
 				obj->jumptables_data_sz = data->d_size;
 				obj->efile.jumptables_data_shndx = idx;
+			} else if (strcmp(name, KMODS_BTFS_SEC) == 0) {
+				err = bpf_object__collect_kmod_btf_names(obj, data, name);
+				if (err)
+					return err;
 			} else {
 				pr_info("elf: skipping unrecognized data section(%d) %s\n",
 					idx, name);
@@ -5803,6 +5888,11 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
 	return 0;
 }
 
+static bool is_kmod_btf_needed(struct bpf_object *obj, const char *name)
+{
+	return hashmap_find(obj->kmod_btfs->hashmap, (long)name, NULL);
+}
+
 static int load_module_btfs(struct bpf_object *obj)
 {
 	struct bpf_btf_info info;
@@ -5867,6 +5957,12 @@ static int load_module_btfs(struct bpf_object *obj)
 			continue;
 		}
 
+		if (obj->kmod_btfs && obj->kmod_btfs->hashmap &&
+		    !is_kmod_btf_needed(obj, name)) {
+			close(fd);
+			continue;
+		}
+
 		btf = btf_get_from_fd(fd, obj->btf_vmlinux);
 		err = libbpf_get_error(btf);
 		if (err) {
@@ -5891,6 +5987,10 @@ static int load_module_btfs(struct bpf_object *obj)
 			break;
 		}
 		obj->btf_module_cnt++;
+
+		if (obj->kmod_btfs &&
+		    obj->kmod_btfs->nr_names == obj->kmod_btfs->nr_loaded)
+			break;
 	}
 
 	if (err) {
@@ -9030,6 +9130,12 @@ static void bpf_object_cleanup_btf(struct bpf_object *obj)
 	/* clean up vmlinux BTF */
 	btf__free(obj->btf_vmlinux);
 	obj->btf_vmlinux = NULL;
+
+	if (obj->kmod_btfs) {
+		hashmap__free(obj->kmod_btfs->hashmap);
+		zfree(&obj->kmod_btfs->data);
+		zfree(&obj->kmod_btfs);
+	}
 }
 
 static void bpf_object_post_load_cleanup(struct bpf_object *obj)
-- 
2.34.1


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

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

Add test cases to verify the new ".kmod_btfs" section logic in libbpf.
The added test cases cover three main scenarios:

  - Valid module: targeting an existing kernel module BTF to ensure
    successful loading.
  - Non-existent module: targeting a fake or missing module, verifying
    that the process results in an expected failure.
  - Duplicates and unneeded: providing a mix of repeated and extra
    module names to ensure the parsing logic remains robust.

Signed-off-by: Fuyu Zhao <zhaofuyu@vivo.com>
---
 .../selftests/bpf/prog_tests/kmod_btfs.c      | 52 +++++++++++++++++++
 tools/testing/selftests/bpf/progs/kmod_btfs.c | 14 +++++
 .../selftests/bpf/progs/kmod_btfs_mix.c       | 15 ++++++
 .../selftests/bpf/progs/kmod_btfs_nonexist.c  | 18 +++++++
 4 files changed, 99 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs_mix.c
 create mode 100644 tools/testing/selftests/bpf/progs/kmod_btfs_nonexist.c

diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
new file mode 100644
index 000000000000..d875fcce6681
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "kmod_btfs.skel.h"
+#include "kmod_btfs_nonexist.skel.h"
+#include "kmod_btfs_mix.skel.h"
+
+static void kmod_btfs_pass(void)
+{
+	struct kmod_btfs *kmod_btfs_skel;
+
+	kmod_btfs_skel = kmod_btfs__open_and_load();
+	if (!ASSERT_OK_PTR(kmod_btfs_skel, "kmod_btfs__open_and_load"))
+		return;
+
+	kmod_btfs__destroy(kmod_btfs_skel);
+}
+
+static void kmod_btfs_nonexist(void)
+{
+	struct kmod_btfs_nonexist *kmod_btfs_nonexist_skel;
+
+	kmod_btfs_nonexist_skel = kmod_btfs_nonexist__open_and_load();
+	ASSERT_NULL(kmod_btfs_nonexist_skel, "kmod_btfs_nonexist__open_and_load");
+}
+
+static void kmod_btfs_mix(void)
+{
+	struct kmod_btfs_mix *kmod_btfs_mix_skel;
+
+	kmod_btfs_mix_skel = kmod_btfs_mix__open_and_load();
+	if (!ASSERT_OK_PTR(kmod_btfs_mix_skel, "kmod_btfs_mix__open_and_load"))
+		return;
+
+	kmod_btfs_mix__destroy(kmod_btfs_mix_skel);
+}
+
+void test_kmod_btfs(void)
+{
+	if (!env.has_testmod) {
+		test__skip();
+		return;
+	}
+
+	if (test__start_subtest("kmod_btfs_pass"))
+		kmod_btfs_pass();
+
+	if (test__start_subtest("kmod_btfs_nonexist"))
+		kmod_btfs_nonexist();
+
+	if (test__start_subtest("kmod_btfs_mix"))
+		kmod_btfs_mix();
+}
diff --git a/tools/testing/selftests/bpf/progs/kmod_btfs.c b/tools/testing/selftests/bpf/progs/kmod_btfs.c
new file mode 100644
index 000000000000..8b986bc2e9ff
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kmod_btfs.c
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("fexit/bpf_testmod_loop_test")
+int test_kmod_btfs(void *ctx)
+{
+	return 0;
+}
+
+DEFINE_KMOD_BTFS(_needed_kmods) = { "bpf_testmod" };
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kmod_btfs_mix.c b/tools/testing/selftests/bpf/progs/kmod_btfs_mix.c
new file mode 100644
index 000000000000..f445cf56ab16
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kmod_btfs_mix.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("fexit/bpf_testmod_loop_test")
+int test_kmod_btfs_mix(void *ctx)
+{
+	return 0;
+}
+
+/* mix of duplicated and unneeded modules */
+DEFINE_KMOD_BTFS(_needed_kmods) = { "bpf_testmod", "bpf_testmod", "bpf_test_no_cfi" };
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/kmod_btfs_nonexist.c b/tools/testing/selftests/bpf/progs/kmod_btfs_nonexist.c
new file mode 100644
index 000000000000..1638361e5732
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/kmod_btfs_nonexist.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("fexit/bpf_testmod_loop_test")
+int test_kmod_btfs_nonexist(void *ctx)
+{
+	return 0;
+}
+
+/*
+ * This should fail to load, because .kmod_btfs does not contain
+ * the needed module 'bpf_testmod'.
+ */
+DEFINE_KMOD_BTFS(_needed_kmods) = { "module_nonexist" };
+
+char _license[] SEC("license") = "GPL";
-- 
2.34.1


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

* Re: [RFC PATCH bpf-next v2 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-13  3:26 ` [RFC PATCH bpf-next v2 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
@ 2026-08-13  3:37   ` sashiko-bot
  2026-08-13  4:22   ` bot+bpf-ci
  1 sibling, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-13  3:37 UTC (permalink / raw)
  To: Fuyu Zhao; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The tracking counter `obj->kmod_btfs->nr_loaded` is never updated, rendering the early termination optimization for BTF loading completely ineffective.
--

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

selftests/bpf: add tests for selective kmod BTF loading

Add test cases to verify the new ".kmod_btfs" section logic in libbpf.
The added test cases cover three main scenarios: valid module, non-existent
module, and duplicates/unneeded modules.

> diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> index 0000000000000..d875fcce6681d
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> @@ -0,0 +1,52 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <test_progs.h>
> +#include "kmod_btfs.skel.h"
> +#include "kmod_btfs_nonexist.skel.h"
> +#include "kmod_btfs_mix.skel.h"
> +

[ ... ]

[Severity: Medium]
While reviewing these tests, I noticed a potential regression in the libbpf
code they exercise. In load_module_btfs():

tools/lib/bpf/libbpf.c:load_module_btfs() {
...
		if (obj->kmod_btfs &&
		    obj->kmod_btfs->nr_names == obj->kmod_btfs->nr_loaded)
			break;
...
}

Is obj->kmod_btfs->nr_loaded ever incremented when a module BTF is
successfully loaded?

If it is not incremented, will this early termination condition ever be
met? It appears this might unnecessarily iterate over all remaining
system BTFs, which would bypass the intended performance optimization
of the .kmod_btfs section.

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

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

* Re: [RFC PATCH bpf-next v2 1/2] libbpf: support selective kernel module BTF loading via .kmod_btfs section
  2026-08-13  3:26 ` [RFC PATCH bpf-next v2 1/2] libbpf: support selective kernel module BTF loading via .kmod_btfs section Fuyu Zhao
@ 2026-08-13  3:37   ` sashiko-bot
  2026-08-13  4:22   ` bot+bpf-ci
  1 sibling, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-13  3:37 UTC (permalink / raw)
  To: Fuyu Zhao; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Missing increment of `nr_loaded` prevents early loop termination.
- [Low] Multi-line comment formatting violation.
--

--- Patch [1]: [RFC PATCH bpf-next v2 1/2] libbpf: support selective kernel module BTF loading via .kmod_btfs section ---
commit 3f4fb04bbfb95d7f7ea0012b5651135983af6e62
Author: Fuyu Zhao <zhaofuyu@vivo.com>

libbpf: support selective kernel module BTF loading via .kmod_btfs section

This patch adds support for a ".kmod_btfs" ELF section to allow BPF programs
to specify which kernel modules need BTF loading. It introduces functions to
collect the module names and filter the BTF loading process to avoid loading
unneeded modules.

> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
> index 9d160b5b9c0ee..171ea055cd323 100644
> --- a/tools/lib/bpf/bpf_helpers.h
> +++ b/tools/lib/bpf/bpf_helpers.h
> @@ -188,6 +188,20 @@ enum libbpf_tristate {
>  	TRI_MODULE = 2,
>  };
>  
> +/* Helper typedef for declaring kernel module names that need BTF loading.
> + *

[Severity: Low]
This isn't a bug, but the BPF subsystem coding style expects multi-line
comments to have the opening /* on its own line. Should this be reformatted
to match the preferred kernel style?

> + * Usage: define an array in the ".kmod_btfs" ELF section to specify
> + * which modules need BTF loading:
> + *

[ ... ]

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 514e4e9daa825..9fe8a653e8b6b 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c

[ ... ]

> @@ -5891,6 +5987,10 @@ static int load_module_btfs(struct bpf_object *obj)
>  			break;
>  		}
>  		obj->btf_module_cnt++;
> +
> +		if (obj->kmod_btfs &&
> +		    obj->kmod_btfs->nr_names == obj->kmod_btfs->nr_loaded)
> +			break;

[Severity: Medium]
Is obj->kmod_btfs->nr_loaded missing an increment?

Looking at the loop in load_module_btfs(), when a module BTF is successfully
loaded and obj->btf_module_cnt is incremented, obj->kmod_btfs->nr_loaded
doesn't appear to be updated anywhere.

Will this prevent the intended optimization of breaking out of the loop early,
causing it to always scan all remaining kernel BTF IDs unnecessarily?

>  	}
>  
>  	if (err) {

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

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

* Re: [RFC PATCH bpf-next v2 2/2] selftests/bpf: add tests for selective kmod BTF loading
  2026-08-13  3:26 ` [RFC PATCH bpf-next v2 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
  2026-08-13  3:37   ` sashiko-bot
@ 2026-08-13  4:22   ` bot+bpf-ci
  1 sibling, 0 replies; 7+ messages in thread
From: bot+bpf-ci @ 2026-08-13  4:22 UTC (permalink / raw)
  To: zhaofuyu, bpf, andrii, alan.maguire
  Cc: ast, daniel, eddyz87, memxor, martin.lau, song, yonghong.song,
	jolsa, emil, ihor.solodrai, shuah, yatsenko, linux-kernel,
	linux-kselftest, zhaofuyu, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai

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

> diff --git a/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> new file mode 100644
> index 0000000000000..d875fcce6681d
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/kmod_btfs.c
> @@ -0,0 +1,52 @@

[ ... ]

> +static void kmod_btfs_nonexist(void)
> +{
> +	struct kmod_btfs_nonexist *kmod_btfs_nonexist_skel;
> +
> +	kmod_btfs_nonexist_skel = kmod_btfs_nonexist__open_and_load();
> +	ASSERT_NULL(kmod_btfs_nonexist_skel, "kmod_btfs_nonexist__open_and_load");
> +}

On the path where this assertion unexpectedly fails (i.e. the skeleton
loads when it should not), does the bpf_object leak?  There is no
'kmod_btfs_nonexist__destroy()' call before the function returns.

The other two subtests (kmod_btfs_pass and kmod_btfs_mix) call
'__destroy()'.  Test_progs runs hundreds of subtests in one process,
so leaked fds stay open for the rest of the run.  The usual pattern is:

	if (!ASSERT_NULL(skel, "kmod_btfs_nonexist__open_and_load"))
		kmod_btfs_nonexist__destroy(skel);

Also, the ASSERT_NULL check accepts any load failure at all.  Without
privileges, load_module_btfs() returns 0 early ('skipping module BTFs
loading, missing privileges'), so '__open_and_load()' returns NULL and
this subtest passes without exercising '.kmod_btfs' handling.  The same
is true if vmlinux BTF is unavailable, ftrace cannot hook the target,
or there is a typo in the SEC() name.

The positive baseline that rules those out lives in kmod_btfs_pass, but
test_progs subtests are individually selectable:

	./test_progs -t kmod_btfs/kmod_btfs_nonexist

so running this subtest alone gives green with no baseline.  Would it
be clearer to check the error more precisely (find_kernel_btf_id()
returns -ESRCH here), or load a known-good variant inside the same
subtest?

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/kmod_btfs_mix.c b/tools/testing/selftests/bpf/progs/kmod_btfs_mix.c
> new file mode 100644
> index 0000000000000..f445cf56ab161
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/kmod_btfs_mix.c
> @@ -0,0 +1,15 @@

[ ... ]

> +/* mix of duplicated and unneeded modules */
> +DEFINE_KMOD_BTFS(_needed_kmods) = { "bpf_testmod", "bpf_testmod", "bpf_test_no_cfi" };

Is 'bpf_test_no_cfi' loaded when this subtest runs?  Looking at
test_progs.c, load_bpf_testmod() at lines 2083-2092 loads only
bpf_testmod.ko.  The bpf_test_no_cfi.ko module is inserted and
immediately removed by prog_tests/test_struct_ops_no_cfi.c via
open()/finit_module()/delete_module().

That means the third entry has no module BTF in the system and exercises
the same path as the 'module_nonexist' entry in kmod_btfs_nonexist.c:
the name is simply never matched in load_module_btfs().

That leaves the interesting case untested: a module whose BTF is present
but which the program does not need, which would prove libbpf actually
skips it as the commit message advertises ('providing a mix of repeated
and extra module names').  Would naming a module that is guaranteed
loaded exercise that path?


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

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

* Re: [RFC PATCH bpf-next v2 1/2] libbpf: support selective kernel module BTF loading via .kmod_btfs section
  2026-08-13  3:26 ` [RFC PATCH bpf-next v2 1/2] libbpf: support selective kernel module BTF loading via .kmod_btfs section Fuyu Zhao
  2026-08-13  3:37   ` sashiko-bot
@ 2026-08-13  4:22   ` bot+bpf-ci
  1 sibling, 0 replies; 7+ messages in thread
From: bot+bpf-ci @ 2026-08-13  4:22 UTC (permalink / raw)
  To: zhaofuyu, bpf, andrii, alan.maguire
  Cc: ast, daniel, eddyz87, memxor, martin.lau, song, yonghong.song,
	jolsa, emil, ihor.solodrai, shuah, yatsenko, linux-kernel,
	linux-kselftest, zhaofuyu, ast, andrii, daniel, martin.lau,
	eddyz87, yonghong.song, clm, ihor.solodrai

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

> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
> index 9d160b5b9c0ee..171ea055cd323 100644
> --- a/tools/lib/bpf/bpf_helpers.h
> +++ b/tools/lib/bpf/bpf_helpers.h
> @@ -188,6 +188,20 @@ enum libbpf_tristate {
>  	TRI_MODULE = 2,
>  };
>
> +/* Helper typedef for declaring kernel module names that need BTF loading.
> + *
> + * Usage: define an array in the ".kmod_btfs" ELF section to specify
> + * which modules need BTF loading:
> + *
> + *   DEFINE_KMOD_BTFS(_needed_kmods)= { "module1", "module2", ... };
> + *
> + * This avoids unnecessary BTF loading and speeds up the BPF program
> + * load process.
> + */
> +#define KMOD_NAME_LEN 64
> +#define DEFINE_KMOD_BTFS(name) \
> +	SEC(".kmod_btfs") char name[][KMOD_NAME_LEN]

KMOD_NAME_LEN establishes the on-disk record size for the .kmod_btfs section
format, yet it's duplicated in two files with only a prose comment connecting
them:

  tools/lib/bpf/bpf_helpers.h:201   #define KMOD_NAME_LEN 64
  tools/lib/bpf/libbpf.c:707-708    /* Should match the definition in ... */
                                    #define KMOD_NAME_LEN 64

If the two ever diverge, bpf_object__collect_kmod_btf_names() will silently
mis-slice the section. The d_size % KMOD_NAME_LEN check only catches
non-multiples, not a mismatch between the two values. Could this use a shared
header or a static assert to ensure they stay in sync?

Also, the new macro has no #ifndef guard. This header is included by nearly
every BPF program alongside vmlinux.h and user headers. Other unprefixed
names this header exports (NULL at lines 78-80, KERNEL_VERSION at 82-84) all
have #ifndef wrappers. Should KMOD_NAME_LEN follow that pattern?

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 514e4e9daa825..9fe8a653e8b6b 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c

[ ... ]

> @@ -5803,6 +5888,11 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
>  	return 0;
>  }
>
> +static bool is_kmod_btf_needed(struct bpf_object *obj, const char *name)
> +{
> +	return hashmap_find(obj->kmod_btfs->hashmap, (long)name, NULL);
> +}

This calls the raw hashmap_find() with a manual (long) cast, rather than
the hashmap__find() wrapper that hashmap.h provides:

  hashmap.h:159
  #define hashmap__find(map, key, value) \
      hashmap_find((map), (long)(key), hashmap_cast_ptr(value))

The header comments at lines 50 and 54 say the intent is to use the wrapper
("behind auxiliary macros") so that the _Static_assert on the value pointer
runs. Bypassing the macro also bypasses that check.

This is the only direct caller of the unversioned function in the whole
library. The other function added by this same patch does use the wrapper
(bpf_object__collect_kmod_btf_names at libbpf.c:964 uses hashmap__find), so
the two halves of the change are inconsistent. Should this use the wrapper
too?

[ ... ]

> @@ -9030,6 +9130,12 @@ static void bpf_object_cleanup_btf(struct bpf_object *obj)
>  	/* clean up vmlinux BTF */
>  	btf__free(obj->btf_vmlinux);
>  	obj->btf_vmlinux = NULL;
> +
> +	if (obj->kmod_btfs) {
> +		hashmap__free(obj->kmod_btfs->hashmap);
> +		zfree(&obj->kmod_btfs->data);
> +		zfree(&obj->kmod_btfs);
> +	}
>  }

The .kmod_btfs filter list is ELF open-time data, but it's freed from
bpf_object_cleanup_btf(), which runs while the bpf_object is still alive and
load_module_btfs() can still be called. The filter therefore stops applying
part-way through the object's lifetime. Concrete path:

  bpf_object_load()                       libbpf.c:9182
    ... -> bpf_object_post_load_cleanup() libbpf.c:9229
             -> bpf_object_cleanup_btf()  libbpf.c:9147
                  obj->btf_modules_loaded = false;   (libbpf.c:9128)
                  zfree(&obj->kmod_btfs);            (libbpf.c:9137)

  bpf_program__attach_tracing_multi()     libbpf.c:12791
    -> collect_func_ids_by_glob()         libbpf.c:12752
         -> find_attach_module()          libbpf.c:7877
              -> load_module_btfs()       libbpf.c:5896

At this second entry into load_module_btfs(), btf_modules_loaded is false so
the full scan re-runs, but obj->kmod_btfs is NULL. The filter at
libbpf.c:5960 is skipped and every module BTF in the system is loaded,
exactly what the section is supposed to prevent. collect_func_ids_by_glob()
also calls bpf_object_cleanup_btf(obj) unconditionally at its cleanup: label
(libbpf.c:12786), so the list is destroyed again on every attach.

The other open-time members with the same lifetime (obj->jumptables_data,
obj->arena_data, obj->kconfig) are freed in bpf_object__close()
(libbpf.c:9720-9733), not in the BTF cleanup helper. Should obj->kmod_btfs
be freed there instead?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/31664172915

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

end of thread, other threads:[~2026-08-13  4:22 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13  3:26 [RFC PATCH bpf-next v2 0/2] libbpf: Improve BPF load performance by selectively loading kmod BTFs Fuyu Zhao
2026-08-13  3:26 ` [RFC PATCH bpf-next v2 1/2] libbpf: support selective kernel module BTF loading via .kmod_btfs section Fuyu Zhao
2026-08-13  3:37   ` sashiko-bot
2026-08-13  4:22   ` bot+bpf-ci
2026-08-13  3:26 ` [RFC PATCH bpf-next v2 2/2] selftests/bpf: add tests for selective kmod BTF loading Fuyu Zhao
2026-08-13  3:37   ` sashiko-bot
2026-08-13  4:22   ` bot+bpf-ci

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