On 8/15/2026 3:57 AM, Andrii Nakryiko wrote:
> On Wed, Aug 12, 2026 at 8:26 PM Fuyu Zhao <[email protected]> wrote:
>>
>> 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 <[email protected]>
>> ---
>> 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]
>> +
>
> I don't really like another special ELF section just to provide a
> custom list of modules.
>
> If we have to do a customization of BTF searching behavior of libbpf,
> let's do this through bpf_object_open_opts struct and pass NULL or a
> empty/non-empty list of kernel modules, or whatever other appropriate
> way to specify this.
>
> But in any case, extending ELF format for BPF object file for this
> seems like an overkill and overcomplication.
>
> pw-bot: cr
>
Hi Andrii,
Thanks for the feedback. I also considered passing the module list through
bpf_object_open_opts.
The main reason for using an ELF section is that it keeps the module
dependency information together with the BPF object and requires minimal
changes for existing users.
However, I understand the concern that extending the ELF format introduces
additional complexity. I'll explore bpf_object_open_opts or other more
suitable approaches.
Best regards,
Fuyu
>> #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
>>