Merge branch 'libbpf-improve-bpf-load-performance-by-selectively-loading-module-btfs'

Fuyu Zhao says:

====================
libbpf: improve BPF load performance by selectively loading module BTFs

Currently, 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 small subset of modules.

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 reduce this overhead, as module BTFs
are loaded before the module-qualified target is resolved.

Use btf_module_allowlist and btf_module_allowlist_cnt to skip unrelated
module BTFs during iteration and stop once all allowed module BTFs have
been loaded. When the allowlist is not specified, 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:
v9:
- Change btf_module_allowlist_cnt to size_t in opts and use ssize_t
  internally. (Andrii)
- Clarify the empty allowlist test comment. (bot+bpf-ci)
- Match subtest names with function names. (bot+bpf-ci)

v8:
- Link: https://lore.kernel.org/bpf/20260911120820.46900-1-zhaofuyu@vivo.com/
- Reset btf_module_allowlist_cnt on allocation failure to avoid a NULL
  pointer dereference during cleanup. (sashiko-bot)
- Fix errno handling in invalid input tests. (sashiko-bot)
- Remove the redundant default opts test. (Jiri)
- Test skipping unlisted module BTFs using bpf_program__set_attach_target(). (Jiri)
- Simplify module BTF loading logic by using btf_module_allowlist_cnt
  alone for subsequent checks. (Jiri)
- Fix remaining libbpf_get_error() usage in selftests. (Andrii)

v7:
- Link: https://lore.kernel.org/bpf/20260907053847.1115-1-zhaofuyu@vivo.com/
- Rename btf_module_names to btf_module_allowlist and use _cnt naming.
  (Andrii)
- Simplify allowlist handling and documentation, and drop unnecessary
  logging, cleanup, and helper logic. (Andrii)
- Simplify selftests, add invalid allowlist/count coverage, and add skip
  reasons for missing BTF prerequisites. (Andrii, bot+bpf-ci)

v6:
- Link: https://lore.kernel.org/bpf/20260831133809.1161-1-zhaofuyu@vivo.com/
- 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/
====================

Link: https://patch.msgid.link/20260915124104.77287-1-zhaofuyu@vivo.com
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
Andrii Nakryiko 2026-09-21 09:26:41 -07:00
commit 83aa20cc35
4 changed files with 260 additions and 1 deletions

View File

@ -751,6 +751,8 @@ struct bpf_object {
bool btf_modules_loaded;
size_t btf_module_cnt;
size_t btf_module_cap;
char **btf_module_allowlist;
ssize_t btf_module_allowlist_cnt;
/* optional log settings passed to BPF_BTF_LOAD and BPF_PROG_LOAD commands */
char *log_buf;
@ -5843,6 +5845,21 @@ int bpf_core_add_cands(struct bpf_core_cand *local_cand,
return 0;
}
static bool is_btf_mod_allowed(const struct bpf_object *obj, const char *name)
{
ssize_t i;
if (obj->btf_module_allowlist_cnt < 0)
return true;
for (i = 0; i < obj->btf_module_allowlist_cnt; i++) {
if (strcmp(obj->btf_module_allowlist[i], name) == 0)
return true;
}
return false;
}
static int load_module_btfs(struct bpf_object *obj)
{
struct bpf_btf_info info;
@ -5865,6 +5882,9 @@ static int load_module_btfs(struct bpf_object *obj)
if (!kernel_supports(obj, FEAT_MODULE_BTF))
return 0;
if (obj->btf_module_allowlist_cnt == 0)
return 0;
while (true) {
err = bpf_btf_get_next_id(id, &id);
if (err && errno == ENOENT)
@ -5907,6 +5927,11 @@ static int load_module_btfs(struct bpf_object *obj)
continue;
}
if (!is_btf_mod_allowed(obj, name)) {
close(fd);
continue;
}
btf = btf_get_from_fd(fd, obj->btf_vmlinux);
err = libbpf_get_error(btf);
if (err) {
@ -5931,6 +5956,9 @@ static int load_module_btfs(struct bpf_object *obj)
break;
}
obj->btf_module_cnt++;
if (obj->btf_module_allowlist_cnt == obj->btf_module_cnt)
break;
}
if (err) {
@ -8481,6 +8509,8 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
const struct bpf_object_open_opts *opts)
{
const char *kconfig, *btf_tmp_path, *token_path;
size_t mod_allow_cnt, i, j;
const char **mod_allow;
struct bpf_object *obj;
int err;
char *log_buf;
@ -8525,6 +8555,22 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
if (token_path && strlen(token_path) >= PATH_MAX)
return ERR_PTR(-ENAMETOOLONG);
mod_allow = OPTS_GET(opts, btf_module_allowlist, NULL);
mod_allow_cnt = OPTS_GET(opts, btf_module_allowlist_cnt, 0);
if (mod_allow_cnt > SSIZE_MAX || (!mod_allow && mod_allow_cnt > 0))
return ERR_PTR(-EINVAL);
for (i = 0; i < mod_allow_cnt; i++) {
if (!mod_allow[i] || !mod_allow[i][0])
return ERR_PTR(-EINVAL);
for (j = 0; j < i; j++) {
if (strcmp(mod_allow[i], mod_allow[j]) == 0)
return ERR_PTR(-EINVAL);
}
}
obj = bpf_object__new(path, obj_buf, obj_buf_sz, obj_name);
if (IS_ERR(obj))
return obj;
@ -8563,6 +8609,24 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
}
}
obj->btf_module_allowlist_cnt = mod_allow ? mod_allow_cnt : -1;
if (mod_allow_cnt > 0) {
obj->btf_module_allowlist =
calloc(mod_allow_cnt, sizeof(*obj->btf_module_allowlist));
if (!obj->btf_module_allowlist) {
err = -ENOMEM;
goto out;
}
for (i = 0; i < mod_allow_cnt; i++) {
obj->btf_module_allowlist[i] = strdup(mod_allow[i]);
if (!obj->btf_module_allowlist[i]) {
err = -ENOMEM;
goto out;
}
}
}
err = bpf_object__elf_init(obj);
err = err ? : bpf_object__elf_collect(obj);
err = err ? : bpf_object__collect_externs(obj);
@ -9685,6 +9749,12 @@ void bpf_object__close(struct bpf_object *obj)
close(obj->jumptable_maps[i].fd);
zfree(&obj->jumptable_maps);
if (obj->btf_module_allowlist) {
for (i = 0; i < obj->btf_module_allowlist_cnt; i++)
zfree(&obj->btf_module_allowlist[i]);
zfree(&obj->btf_module_allowlist);
}
free(obj);
}

View File

@ -224,10 +224,24 @@ struct bpf_object_open_opts {
* point (/sys/fs/bpf), in case this default behavior is undesirable.
*/
const char *bpf_token_path;
/*
* Optional allowlist of kernel module names whose BTFs libbpf is
* allowed to load. The allowlist limits which kernel module BTFs libbpf
* will consult wherever module BTF might be needed.
*
* When the option is not specified, the existing behavior remains
* unchanged. An explicitly specified empty list prevents libbpf from
* consulting any kernel module BTFs.
*
* The list must contain valid, non-empty module names and must not
* contain duplicate entries; otherwise -EINVAL is returned.
*/
const char **btf_module_allowlist;
size_t btf_module_allowlist_cnt;
size_t :0;
};
#define bpf_object_open_opts__last_field bpf_token_path
#define bpf_object_open_opts__last_field btf_module_allowlist_cnt
/**
* @brief **bpf_object__open()** creates a bpf_object by opening

View File

@ -0,0 +1,162 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <bpf/btf.h>
#include "btf_module_allowlist.skel.h"
static void test_load_with_list(const char **list, size_t count,
int expected_ret)
{
struct btf_module_allowlist *skel;
int ret;
LIBBPF_OPTS(bpf_object_open_opts, opts,
.btf_module_allowlist = list,
.btf_module_allowlist_cnt = count,
);
skel = btf_module_allowlist__open_opts(&opts);
if (!ASSERT_OK_PTR(skel, "btf_module_allowlist__open_opts"))
return;
ret = btf_module_allowlist__load(skel);
ASSERT_EQ(ret, expected_ret, "btf_module_allowlist__load");
btf_module_allowlist__destroy(skel);
}
static void btf_module_allowlist_default(void)
{
struct btf_module_allowlist *skel;
int ret;
skel = btf_module_allowlist__open();
if (!ASSERT_OK_PTR(skel, "btf_module_allowlist__open"))
return;
ret = btf_module_allowlist__load(skel);
ASSERT_OK(ret, "btf_module_allowlist__load");
btf_module_allowlist__destroy(skel);
}
static void btf_module_allowlist_allow(void)
{
const char *mod_list[] = { "bpf_testmod" };
test_load_with_list(mod_list, 1, 0);
}
/*
* A non-NULL allowlist with a count of 0 explicitly disables module BTFs.
* The pointer must be non-NULL to distinguish this from an unspecified
* allowlist.
*/
static void btf_module_allowlist_emptylist(void)
{
const char *mod_list[] = { NULL };
test_load_with_list(mod_list, 0, -ESRCH);
}
/*
* bpf_testmod is not in the allowlist, so its BTF should not be loaded.
*/
static void btf_module_allowlist_skipunlisted(void)
{
const char *mod_list[] = { "module_nonexist" };
struct btf_module_allowlist *skel;
int ret;
LIBBPF_OPTS(bpf_object_open_opts, opts,
.btf_module_allowlist = mod_list,
.btf_module_allowlist_cnt = 1,
);
skel = btf_module_allowlist__open_opts(&opts);
if (!ASSERT_OK_PTR(skel, "btf_module_allowlist__open_opts"))
return;
ret = bpf_program__set_attach_target(skel->progs.test_btf_module_allowlist, 0,
"bpf_testmod:bpf_testmod_fentry_test1");
ASSERT_EQ(ret, -ESRCH, "bpf_program__set_attach_target");
btf_module_allowlist__destroy(skel);
}
static void test_invalid_input(const char **list, size_t count,
const char *test_name)
{
struct btf_module_allowlist *skel;
char assert_name[64];
int err;
LIBBPF_OPTS(bpf_object_open_opts, opts,
.btf_module_allowlist = list,
.btf_module_allowlist_cnt = count,
);
snprintf(assert_name, sizeof(assert_name), "%s: open_opts", test_name);
skel = btf_module_allowlist__open_opts(&opts);
err = errno;
if (!ASSERT_NULL(skel, assert_name)) {
btf_module_allowlist__destroy(skel);
return;
}
snprintf(assert_name, sizeof(assert_name), "%s: open_opts err", test_name);
ASSERT_EQ(err, EINVAL, assert_name);
}
static void btf_module_allowlist_invalidinput(void)
{
const char *names[] = { NULL };
const char *empty_names[] = { "" };
const char *duplicate_names[] = { "bpf_testmod", "bpf_testmod" };
test_invalid_input(NULL, 1, "null_list");
test_invalid_input(names, 1, "null_name");
test_invalid_input(empty_names, 1, "empty_name");
test_invalid_input(duplicate_names, 2, "duplicate_name");
test_invalid_input(names, (size_t)SSIZE_MAX + 1, "large_count");
}
void test_btf_module_allowlist(void)
{
struct btf *vmlinux_btf = NULL;
struct btf *module_btf = NULL;
if (!env.has_testmod) {
printf("%s:SKIP: bpf_testmod is not available\n", __func__);
test__skip();
return;
}
vmlinux_btf = btf__load_vmlinux_btf();
if (!vmlinux_btf) {
printf("%s:SKIP: vmlinux_btf is not available\n", __func__);
test__skip();
return;
}
/* Ensure bpf_testmod BTF is available. */
module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
if (!module_btf) {
printf("%s:SKIP: bpf_testmod's BTF is not available\n", __func__);
btf__free(vmlinux_btf);
test__skip();
return;
}
btf__free(module_btf);
btf__free(vmlinux_btf);
if (test__start_subtest("btf_module_allowlist_default"))
btf_module_allowlist_default();
if (test__start_subtest("btf_module_allowlist_allow"))
btf_module_allowlist_allow();
if (test__start_subtest("btf_module_allowlist_skipunlisted"))
btf_module_allowlist_skipunlisted();
if (test__start_subtest("btf_module_allowlist_emptylist"))
btf_module_allowlist_emptylist();
if (test__start_subtest("btf_module_allowlist_invalidinput"))
btf_module_allowlist_invalidinput();
}

View File

@ -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_allowlist)
{
return 0;
}
char _license[] SEC("license") = "GPL";