]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
bpftool: Support merging multiple module BTFs in btf dump
authorJosef Bacik <josef@toxicpanda.com>
Wed, 4 Mar 2026 20:56:51 +0000 (15:56 -0500)
committerAndrii Nakryiko <andrii@kernel.org>
Thu, 5 Mar 2026 23:03:02 +0000 (15:03 -0800)
Add support for specifying multiple file sources in 'bpftool btf dump'
to generate a single C header containing types from vmlinux plus
multiple kernel modules:

  bpftool btf dump file /sys/kernel/btf/mod1 file /sys/kernel/btf/mod2 format c

This is useful for BPF programs that need to access types defined in
kernel modules. Previously this required a separate bpftool invocation
for each module, producing separate headers that could not be combined
due to overlapping vmlinux type definitions.

The implementation collects all file paths, then for the multi-file
case creates an empty split BTF on the vmlinux base and iteratively
merges each module's types into it via btf__add_btf(). The single-file
code path is preserved exactly to avoid any regression risk.

Auto-detection of vmlinux as the base BTF from sysfs paths works as
before. If vmlinux itself appears in the file list it is skipped with
a warning since its types are already provided by the base.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Reviewed-by: Alan Maguire <alan.maguire@oracle.com>
Link: https://lore.kernel.org/bpf/b19c2760ffe48cec546dd3810d237f8cad20d606.1772657690.git.josef@toxicpanda.com
tools/bpf/bpftool/Documentation/bpftool-btf.rst
tools/bpf/bpftool/bash-completion/bpftool
tools/bpf/bpftool/btf.c

index d47dddc2b4ee306a418a3e243a9bf8bd52e387a7..cf75a7fa2d6bc063016705aef974f4a9b56bf23f 100644 (file)
@@ -27,7 +27,7 @@ BTF COMMANDS
 | **bpftool** **btf dump** *BTF_SRC* [**format** *FORMAT*] [**root_id** *ROOT_ID*]
 | **bpftool** **btf help**
 |
-| *BTF_SRC* := { **id** *BTF_ID* | **prog** *PROG* | **map** *MAP* [{**key** | **value** | **kv** | **all**}] | **file** *FILE* }
+| *BTF_SRC* := { **id** *BTF_ID* | **prog** *PROG* | **map** *MAP* [{**key** | **value** | **kv** | **all**}] | **file** *FILE* [**file** *FILE*]... }
 | *FORMAT* := { **raw** | **c** [**unsorted**] }
 | *MAP* := { **id** *MAP_ID* | **pinned** *FILE* }
 | *PROG* := { **id** *PROG_ID* | **pinned** *FILE* | **tag** *PROG_TAG* | **name** *PROG_NAME* }
@@ -58,9 +58,12 @@ bpftool btf dump *BTF_SRC* [format *FORMAT*] [root_id *ROOT_ID*]
     When **prog** is provided, it's expected that program has associated BTF
     object with BTF types.
 
-    When specifying *FILE*, an ELF file is expected, containing .BTF section
-    with well-defined BTF binary format data, typically produced by clang or
-    pahole.
+    When specifying *FILE*, an ELF file or a raw BTF file (e.g. from
+    ``/sys/kernel/btf/``) is expected.  Multiple **file** arguments may be
+    given to merge BTF from several kernel modules into a single output.
+    When sysfs paths are used, vmlinux BTF is loaded automatically as the
+    base; if vmlinux itself appears in the file list it is skipped.
+    A base BTF can also be specified explicitly with **-B**.
 
     **format** option can be used to override default (raw) output format. Raw
     (**raw**) or C-syntax (**c**) output formats are supported. With C-style
index a28f0cc522e48dd08cde4d287d0575c1f3c8c3fd..babb0d4e975397544319116a69b85cc1c41e0738 100644 (file)
@@ -961,10 +961,14 @@ _bpftool()
                         *)
                             # emit extra options
                             case ${words[3]} in
-                                id|file)
+                                id)
                                     COMPREPLY=( $( compgen -W "root_id" -- "$cur" ) )
                                     _bpftool_once_attr 'format'
                                     ;;
+                                file)
+                                    COMPREPLY=( $( compgen -W "root_id file" -- "$cur" ) )
+                                    _bpftool_once_attr 'format'
+                                    ;;
                                 map|prog)
                                     if [[ ${words[3]} == "map" ]] && [[ $cword == 6 ]]; then
                                         COMPREPLY+=( $( compgen -W "key value kv all" -- "$cur" ) )
index 946612029deeee8ef5062d8421ba1e1b92b4fdda..2e899e940034108b1ea362d091056700ab70be5a 100644 (file)
@@ -28,6 +28,7 @@
 #define FASTCALL_DECL_TAG      "bpf_fastcall"
 
 #define MAX_ROOT_IDS           16
+#define MAX_BTF_FILES          64
 
 static const char * const btf_kind_str[NR_BTF_KINDS] = {
        [BTF_KIND_UNKN]         = "UNKNOWN",
@@ -878,6 +879,45 @@ static bool btf_is_kernel_module(__u32 btf_id)
        return btf_info.kernel_btf && strncmp(btf_name, "vmlinux", sizeof(btf_name)) != 0;
 }
 
+static struct btf *merge_btf_files(const char **files, int nr_files,
+                                  struct btf *vmlinux_base)
+{
+       struct btf *combined, *mod;
+       int ret;
+
+       combined = btf__new_empty_split(vmlinux_base);
+       if (!combined) {
+               p_err("failed to create combined BTF: %s", strerror(errno));
+               return NULL;
+       }
+
+       for (int j = 0; j < nr_files; j++) {
+               mod = btf__parse_split(files[j], vmlinux_base);
+               if (!mod) {
+                       p_err("failed to load BTF from %s: %s", files[j], strerror(errno));
+                       btf__free(combined);
+                       return NULL;
+               }
+
+               ret = btf__add_btf(combined, mod);
+               btf__free(mod);
+               if (ret < 0) {
+                       p_err("failed to merge BTF from %s: %s", files[j], strerror(-ret));
+                       btf__free(combined);
+                       return NULL;
+               }
+       }
+
+       ret = btf__dedup(combined, NULL);
+       if (ret) {
+               p_err("failed to dedup combined BTF: %s", strerror(-ret));
+               btf__free(combined);
+               return NULL;
+       }
+
+       return combined;
+}
+
 static int do_dump(int argc, char **argv)
 {
        bool dump_c = false, sort_dump_c = true;
@@ -958,20 +998,76 @@ static int do_dump(int argc, char **argv)
                NEXT_ARG();
        } else if (is_prefix(src, "file")) {
                const char sysfs_prefix[] = "/sys/kernel/btf/";
+               struct btf *vmlinux_base = base_btf;
+               const char *files[MAX_BTF_FILES];
+               int nr_files = 0;
 
-               if (!base_btf &&
-                   strncmp(*argv, sysfs_prefix, sizeof(sysfs_prefix) - 1) == 0 &&
-                   strcmp(*argv, sysfs_vmlinux) != 0)
-                       base = get_vmlinux_btf_from_sysfs();
-
-               btf = btf__parse_split(*argv, base ?: base_btf);
-               if (!btf) {
-                       err = -errno;
-                       p_err("failed to load BTF from %s: %s",
-                             *argv, strerror(errno));
-                       goto done;
+               /* First grab our argument, filtering out the sysfs_vmlinux. */
+               if (strcmp(*argv, sysfs_vmlinux) != 0) {
+                       files[nr_files++] = *argv;
+               } else {
+                       p_info("skipping %s (will be loaded as base)", *argv);
                }
                NEXT_ARG();
+
+               while (argc && is_prefix(*argv, "file")) {
+                       NEXT_ARG();
+                       if (!REQ_ARGS(1)) {
+                               err = -EINVAL;
+                               goto done;
+                       }
+                       /* Filter out any sysfs vmlinux entries. */
+                       if (strcmp(*argv, sysfs_vmlinux) == 0) {
+                               p_info("skipping %s (will be loaded as base)", *argv);
+                               NEXT_ARG();
+                               continue;
+                       }
+                       if (nr_files >= MAX_BTF_FILES) {
+                               p_err("too many BTF files (max %d)", MAX_BTF_FILES);
+                               err = -E2BIG;
+                               goto done;
+                       }
+                       files[nr_files++] = *argv;
+                       NEXT_ARG();
+               }
+
+               /* Auto-detect vmlinux base if any file is from sysfs */
+               if (!vmlinux_base) {
+                       for (int j = 0; j < nr_files; j++) {
+                               if (strncmp(files[j], sysfs_prefix, sizeof(sysfs_prefix) - 1) == 0) {
+                                       base = get_vmlinux_btf_from_sysfs();
+                                       vmlinux_base = base;
+                                       break;
+                               }
+                       }
+               }
+
+               /* All files were the sysfs_vmlinux, handle it like we used to */
+               if (nr_files == 0) {
+                       nr_files = 1;
+                       files[0] = sysfs_vmlinux;
+               }
+
+               if (nr_files == 1) {
+                       btf = btf__parse_split(files[0], base ?: base_btf);
+                       if (!btf) {
+                               err = -errno;
+                               p_err("failed to load BTF from %s: %s", files[0], strerror(errno));
+                               goto done;
+                       }
+               } else {
+                       if (!vmlinux_base) {
+                               p_err("base BTF is required when merging multiple BTF files; use -B/--base-btf or use sysfs paths");
+                               err = -EINVAL;
+                               goto done;
+                       }
+
+                       btf = merge_btf_files(files, nr_files, vmlinux_base);
+                       if (!btf) {
+                               err = -errno;
+                               goto done;
+                       }
+               }
        } else {
                err = -1;
                p_err("unrecognized BTF source specifier: '%s'", src);
@@ -1445,7 +1541,8 @@ static int do_help(int argc, char **argv)
                "       %1$s %2$s dump BTF_SRC [format FORMAT] [root_id ROOT_ID]\n"
                "       %1$s %2$s help\n"
                "\n"
-               "       BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] | file FILE }\n"
+               "       BTF_SRC := { id BTF_ID | prog PROG | map MAP [{key | value | kv | all}] |\n"
+               "                    file FILE [file FILE]... }\n"
                "       FORMAT  := { raw | c [unsorted] }\n"
                "       " HELP_SPEC_MAP "\n"
                "       " HELP_SPEC_PROGRAM "\n"