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 Signed-off-by: Fuyu Zhao --- tools/lib/bpf/libbpf.c | 113 +++++++++++++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.h | 24 ++++++++- 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 514e4e9daa82..1f061f4873b5 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,99 @@ 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; + } + + 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 +5921,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 +5966,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 +5995,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 +8615,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 +9740,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..b8c1d98d69d8 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 not contain duplicate entries; otherwise -EINVAL is + * returned. + * + * This affects: + * - BPF CO-RE relocations against types defined in modules; + * - BTF-based resolution of function attach targets for + * fentry/fexit/fmod_ret/freplace/LSM programs; + * - 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 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 --- .../bpf/prog_tests/btf_module_names.c | 139 ++++++++++++++++++ .../selftests/bpf/progs/btf_module_names.c | 13 ++ 2 files changed, 152 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..3dc72bef014c --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/btf_module_names.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "btf_module_names.skel.h" + +static void btf_module_names_load(void) +{ + struct btf_module_names *skel = NULL; + int ret; + static 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; + static 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; + static const char *mod_names[] = { "" }; + + LIBBPF_OPTS(bpf_object_open_opts, opts, + .btf_module_names = mod_names, + ); + + 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) +{ + if (!env.has_testmod) { + test__skip(); + return; + } + + 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 +#include +#include + +SEC("fentry/bpf_testmod_loop_test") +int BPF_PROG(test_btf_module_names) +{ + return 0; +} + +char _license[] SEC("license") = "GPL"; -- 2.34.1