]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
resolve_btfids: Implement --patch_btfids
authorIhor Solodrai <ihor.solodrai@linux.dev>
Wed, 31 Dec 2025 01:25:57 +0000 (17:25 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 31 Dec 2025 17:04:09 +0000 (09:04 -0800)
Recent changes in BTF generation [1] rely on ${OBJCOPY} command to
update .BTF_ids section data in target ELF files.

This exposed a bug in llvm-objcopy --update-section code path, that
may lead to corruption of a target ELF file. Specifically, because of
the bug st_shndx of some symbols may be (incorrectly) set to 0xffff
(SHN_XINDEX) [2][3].

While there is a pending fix for LLVM, it'll take some time before it
lands (likely in 22.x). And the kernel build must keep working with
older LLVM toolchains in the foreseeable future.

Using GNU objcopy for .BTF_ids update would work, but it would require
changes to LLVM-based build process, likely breaking existing build
environments as discussed in [2].

To work around llvm-objcopy bug, implement --patch_btfids code path in
resolve_btfids as a drop-in replacement for:

    ${OBJCOPY} --update-section .BTF_ids=${btf_ids} ${elf}

Which works specifically for .BTF_ids section:

    ${RESOLVE_BTFIDS} --patch_btfids ${btf_ids} ${elf}

This feature in resolve_btfids can be removed at some point in the
future, when llvm-objcopy with a relevant bugfix becomes common.

[1] https://lore.kernel.org/bpf/20251219181321.1283664-1-ihor.solodrai@linux.dev/
[2] https://lore.kernel.org/bpf/20251224005752.201911-1-ihor.solodrai@linux.dev/
[3] https://github.com/llvm/llvm-project/issues/168060#issuecomment-3533552952

Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Link: https://lore.kernel.org/r/20251231012558.1699758-1-ihor.solodrai@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
scripts/gen-btf.sh
scripts/link-vmlinux.sh
tools/bpf/resolve_btfids/main.c
tools/testing/selftests/bpf/Makefile

index 12244dbe097c02643212927b60f65eedd2b0d099..0aec86615416223e239f8ed993607e9048101d20 100755 (executable)
@@ -123,7 +123,7 @@ embed_btf_data()
        fi
        local btf_ids="${ELF_FILE}.BTF_ids"
        if [ -f "${btf_ids}" ]; then
-               ${OBJCOPY} --update-section .BTF_ids=${btf_ids} ${ELF_FILE}
+               ${RESOLVE_BTFIDS} --patch_btfids ${btf_ids} ${ELF_FILE}
        fi
 }
 
index e2207e612ac3de12e17308ae93d0ede35a695470..1915adf3249b962d8eea66b69a3f76fe47f527d6 100755 (executable)
@@ -266,7 +266,7 @@ vmlinux_link "${VMLINUX}"
 
 if is_enabled CONFIG_DEBUG_INFO_BTF; then
        info OBJCOPY ${btfids_vmlinux}
-       ${OBJCOPY} --update-section .BTF_ids=${btfids_vmlinux} ${VMLINUX}
+       ${RESOLVE_BTFIDS} --patch_btfids ${btfids_vmlinux} ${VMLINUX}
 fi
 
 mksysmap "${VMLINUX}" System.map
index 2cbc252259be56ad28a814779d6435472b22fd23..df39982f51dfb7d0e01fcd7d1bfa32e5da6617a1 100644 (file)
@@ -862,8 +862,119 @@ static inline int make_out_path(char *buf, u32 buf_sz, const char *in_path, cons
        return 0;
 }
 
+/*
+ * Patch the .BTF_ids section of an ELF file with data from provided file.
+ * Equivalent to: objcopy --update-section .BTF_ids=<btfids> <elf>
+ *
+ * 1. Find .BTF_ids section in the ELF
+ * 2. Verify that blob file size matches section size
+ * 3. Update section data buffer with blob data
+ * 4. Write the ELF file
+ */
+static int patch_btfids(const char *btfids_path, const char *elf_path)
+{
+       Elf_Scn *scn = NULL;
+       FILE *btfids_file;
+       size_t shdrstrndx;
+       int fd, err = -1;
+       Elf_Data *data;
+       struct stat st;
+       GElf_Shdr sh;
+       char *name;
+       Elf *elf;
+
+       elf_version(EV_CURRENT);
+
+       fd = open(elf_path, O_RDWR, 0666);
+       if (fd < 0) {
+               pr_err("FAILED to open %s: %s\n", elf_path, strerror(errno));
+               return -1;
+       }
+
+       elf = elf_begin(fd, ELF_C_RDWR_MMAP, NULL);
+       if (!elf) {
+               close(fd);
+               pr_err("FAILED cannot create ELF descriptor: %s\n", elf_errmsg(-1));
+               return -1;
+       }
+
+       elf_flagelf(elf, ELF_C_SET, ELF_F_LAYOUT);
+
+       if (elf_getshdrstrndx(elf, &shdrstrndx) != 0) {
+               pr_err("FAILED cannot get shdr str ndx\n");
+               goto out;
+       }
+
+       while ((scn = elf_nextscn(elf, scn)) != NULL) {
+
+               if (gelf_getshdr(scn, &sh) != &sh) {
+                       pr_err("FAILED to get section header\n");
+                       goto out;
+               }
+
+               name = elf_strptr(elf, shdrstrndx, sh.sh_name);
+               if (!name)
+                       continue;
+
+               if (strcmp(name, BTF_IDS_SECTION) == 0)
+                       break;
+       }
+
+       if (!scn) {
+               pr_err("FAILED: section %s not found in %s\n", BTF_IDS_SECTION, elf_path);
+               goto out;
+       }
+
+       data = elf_getdata(scn, NULL);
+       if (!data) {
+               pr_err("FAILED to get %s section data from %s\n", BTF_IDS_SECTION, elf_path);
+               goto out;
+       }
+
+       if (stat(btfids_path, &st) < 0) {
+               pr_err("FAILED to stat %s: %s\n", btfids_path, strerror(errno));
+               goto out;
+       }
+
+       if ((size_t)st.st_size != data->d_size) {
+               pr_err("FAILED: size mismatch - %s section in %s is %zu bytes, %s is %zu bytes\n",
+                      BTF_IDS_SECTION, elf_path, data->d_size, btfids_path, (size_t)st.st_size);
+               goto out;
+       }
+
+       btfids_file = fopen(btfids_path, "rb");
+       if (!btfids_file) {
+               pr_err("FAILED to open %s: %s\n", btfids_path, strerror(errno));
+               goto out;
+       }
+
+       pr_debug("Copying data from %s to %s section of %s (%zu bytes)\n",
+                btfids_path, BTF_IDS_SECTION, elf_path, data->d_size);
+
+       if (fread(data->d_buf, data->d_size, 1, btfids_file) != 1) {
+               pr_err("FAILED to read %s\n", btfids_path);
+               fclose(btfids_file);
+               goto out;
+       }
+       fclose(btfids_file);
+
+       elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY);
+       if (elf_update(elf, ELF_C_WRITE) < 0) {
+               pr_err("FAILED to update ELF file %s\n", elf_path);
+               goto out;
+       }
+
+       err = 0;
+out:
+       elf_end(elf);
+       close(fd);
+
+       return err;
+}
+
 static const char * const resolve_btfids_usage[] = {
        "resolve_btfids [<options>] <ELF object>",
+       "resolve_btfids --patch_btfids <.BTF_ids file> <ELF object>",
        NULL
 };
 
@@ -880,6 +991,7 @@ int main(int argc, const char **argv)
                .funcs    = RB_ROOT,
                .sets     = RB_ROOT,
        };
+       const char *btfids_path = NULL;
        bool fatal_warnings = false;
        char out_path[PATH_MAX];
 
@@ -894,6 +1006,8 @@ int main(int argc, const char **argv)
                            "turn warnings into errors"),
                OPT_BOOLEAN(0, "distill_base", &obj.distill_base,
                            "distill --btf_base and emit .BTF.base section data"),
+               OPT_STRING(0, "patch_btfids", &btfids_path, "file",
+                          "path to .BTF_ids section data blob to patch into ELF file"),
                OPT_END()
        };
        int err = -1;
@@ -905,6 +1019,9 @@ int main(int argc, const char **argv)
 
        obj.path = argv[0];
 
+       if (btfids_path)
+               return patch_btfids(btfids_path, obj.path);
+
        if (load_btf(&obj))
                goto out;
 
index f28a32b16ff0e99f91ed01c80af52fc9ae570f03..9488d076c74070f7f57707f44b9d7472ced928e5 100644 (file)
@@ -657,7 +657,7 @@ $(TRUNNER_TEST_OBJS): $(TRUNNER_OUTPUT)/%.test.o:                   \
        $$(if $$(TEST_NEEDS_BTFIDS),                                            \
                $$(call msg,BTFIDS,$(TRUNNER_BINARY),$$@)                       \
                $(RESOLVE_BTFIDS) --btf $(TRUNNER_OUTPUT)/btf_data.bpf.o $$@;   \
-               $(OBJCOPY) --update-section .BTF_ids=$$@.BTF_ids $$@)
+               $(RESOLVE_BTFIDS) --patch_btfids $$@.BTF_ids $$@)
 
 $(TRUNNER_TEST_OBJS:.o=.d): $(TRUNNER_OUTPUT)/%.test.d:                        \
                            $(TRUNNER_TESTS_DIR)/%.c                    \