From dd590d4d57ebeeb826823c288741f2ed20f452af Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 17 Sep 2025 09:03:59 -0700 Subject: objtool/klp: Introduce klp diff subcommand for diffing object files Add a new klp diff subcommand which performs a binary diff between two object files and extracts changed functions into a new object which can then be linked into a livepatch module. This builds on concepts from the longstanding out-of-tree kpatch [1] project which began in 2012 and has been used for many years to generate livepatch modules for production kernels. However, this is a complete rewrite which incorporates hard-earned lessons from 12+ years of maintaining kpatch. Key improvements compared to kpatch-build: - Integrated with objtool: Leverages objtool's existing control-flow graph analysis to help detect changed functions. - Works on vmlinux.o: Supports late-linked objects, making it compatible with LTO, IBT, and similar. - Simplified code base: ~3k fewer lines of code. - Upstream: No more out-of-tree #ifdef hacks, far less cruft. - Cleaner internals: Vastly simplified logic for symbol/section/reloc inclusion and special section extraction. - Robust __LINE__ macro handling: Avoids false positive binary diffs caused by the __LINE__ macro by introducing a fix-patch-lines script (coming in a later patch) which injects #line directives into the source .patch to preserve the original line numbers at compile time. Note the end result of this subcommand is not yet functionally complete. Livepatch needs some ELF magic which linkers don't like: - Two relocation sections (.rela*, .klp.rela*) for the same text section. - Use of SHN_LIVEPATCH to mark livepatch symbols. Unfortunately linkers tend to mangle such things. To work around that, klp diff generates a linker-compliant intermediate binary which encodes the relevant KLP section/reloc/symbol metadata. After module linking, a klp post-link step (coming soon) will clean up the mess and convert the linked .ko into a fully compliant livepatch module. Note this subcommand requires the diffed binaries to have been compiled with -ffunction-sections and -fdata-sections, and processed with 'objtool --checksum'. Those constraints will be handled by a klp-build script introduced in a later patch. Without '-ffunction-sections -fdata-sections', reliable object diffing would be infeasible due to toolchain limitations: - For intra-file+intra-section references, the compiler might occasionally generated hard-coded instruction offsets instead of relocations. - Section-symbol-based references can be ambiguous: - Overlapping or zero-length symbols create ambiguity as to which symbol is being referenced. - A reference to the end of a symbol (e.g., checking array bounds) can be misinterpreted as a reference to the next symbol, or vice versa. A potential future alternative to '-ffunction-sections -fdata-sections' would be to introduce a toolchain option that forces symbol-based (non-section) relocations. Acked-by: Petr Mladek Tested-by: Joe Lawrence Signed-off-by: Josh Poimboeuf --- tools/objtool/Build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tools/objtool/Build') diff --git a/tools/objtool/Build b/tools/objtool/Build index a3cdf8af6635..0b01657671d7 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -8,8 +8,8 @@ objtool-y += builtin-check.o objtool-y += elf.o objtool-y += objtool.o -objtool-$(BUILD_ORC) += orc_gen.o -objtool-$(BUILD_ORC) += orc_dump.o +objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o +objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o objtool-y += libstring.o objtool-y += libctype.o -- cgit v1.2.3 From ebe864b55304f74c4e1a8b6c899e34446b2be424 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 17 Sep 2025 09:04:01 -0700 Subject: objtool/klp: Add post-link subcommand to finalize livepatch modules Livepatch needs some ELF magic which linkers don't like: - Two relocation sections (.rela*, .klp.rela*) for the same text section. - Use of SHN_LIVEPATCH to mark livepatch symbols. Unfortunately linkers tend to mangle such things. To work around that, klp diff generates a linker-compliant intermediate binary which encodes the relevant KLP section/reloc/symbol metadata. After module linking, the .ko then needs to be converted to an actual livepatch module. Introduce a new klp post-link subcommand to do so. Acked-by: Petr Mladek Tested-by: Joe Lawrence Signed-off-by: Josh Poimboeuf --- tools/objtool/Build | 2 +- tools/objtool/builtin-klp.c | 1 + tools/objtool/include/objtool/klp.h | 4 + tools/objtool/klp-post-link.c | 168 ++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 tools/objtool/klp-post-link.c (limited to 'tools/objtool/Build') diff --git a/tools/objtool/Build b/tools/objtool/Build index 0b01657671d7..8cd71b9a5eef 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -9,7 +9,7 @@ objtool-y += elf.o objtool-y += objtool.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o -objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o +objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o objtool-y += libstring.o objtool-y += libctype.o diff --git a/tools/objtool/builtin-klp.c b/tools/objtool/builtin-klp.c index 9b13dd1182af..56d5a5b92f72 100644 --- a/tools/objtool/builtin-klp.c +++ b/tools/objtool/builtin-klp.c @@ -14,6 +14,7 @@ struct subcmd { static struct subcmd subcmds[] = { { "diff", "Generate binary diff of two object files", cmd_klp_diff, }, + { "post-link", "Finalize klp symbols/relocs after module linking", cmd_klp_post_link, }, }; static void cmd_klp_usage(void) diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 07928fac059b..ad830a7ce55b 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -2,6 +2,9 @@ #ifndef _OBJTOOL_KLP_H #define _OBJTOOL_KLP_H +#define SHF_RELA_LIVEPATCH 0x00100000 +#define SHN_LIVEPATCH 0xff20 + /* * __klp_objects and __klp_funcs are created by klp diff and used by the patch * module init code to build the klp_patch, klp_object and klp_func structs @@ -27,5 +30,6 @@ struct klp_reloc { }; int cmd_klp_diff(int argc, const char **argv); +int cmd_klp_post_link(int argc, const char **argv); #endif /* _OBJTOOL_KLP_H */ diff --git a/tools/objtool/klp-post-link.c b/tools/objtool/klp-post-link.c new file mode 100644 index 000000000000..c013e39957b1 --- /dev/null +++ b/tools/objtool/klp-post-link.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Read the intermediate KLP reloc/symbol representations created by klp diff + * and convert them to the proper format required by livepatch. This needs to + * run last to avoid linker wreckage. Linkers don't tend to handle the "two + * rela sections for a single base section" case very well, nor do they like + * SHN_LIVEPATCH. + * + * This is the final tool in the livepatch module generation pipeline: + * + * kernel builds -> objtool klp diff -> module link -> objtool klp post-link + */ + +#include +#include +#include +#include +#include +#include +#include + +static int fix_klp_relocs(struct elf *elf) +{ + struct section *symtab, *klp_relocs; + + klp_relocs = find_section_by_name(elf, KLP_RELOCS_SEC); + if (!klp_relocs) + return 0; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("missing .symtab"); + return -1; + } + + for (int i = 0; i < sec_size(klp_relocs) / sizeof(struct klp_reloc); i++) { + struct klp_reloc *klp_reloc; + unsigned long klp_reloc_off; + struct section *sec, *tmp, *klp_rsec; + unsigned long offset; + struct reloc *reloc; + char sym_modname[64]; + char rsec_name[SEC_NAME_LEN]; + u64 addend; + struct symbol *sym, *klp_sym; + + klp_reloc_off = i * sizeof(*klp_reloc); + klp_reloc = klp_relocs->data->d_buf + klp_reloc_off; + + /* + * Read __klp_relocs[i]: + */ + + /* klp_reloc.sec_offset */ + reloc = find_reloc_by_dest(elf, klp_relocs, + klp_reloc_off + offsetof(struct klp_reloc, offset)); + if (!reloc) { + ERROR("malformed " KLP_RELOCS_SEC " section"); + return -1; + } + + sec = reloc->sym->sec; + offset = reloc_addend(reloc); + + /* klp_reloc.sym */ + reloc = find_reloc_by_dest(elf, klp_relocs, + klp_reloc_off + offsetof(struct klp_reloc, sym)); + if (!reloc) { + ERROR("malformed " KLP_RELOCS_SEC " section"); + return -1; + } + + klp_sym = reloc->sym; + addend = reloc_addend(reloc); + + /* symbol format: .klp.sym.modname.sym_name,sympos */ + if (sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname) != 1) + ERROR("can't find modname in klp symbol '%s'", klp_sym->name); + + /* + * Create the KLP rela: + */ + + /* section format: .klp.rela.sec_objname.section_name */ + if (snprintf_check(rsec_name, SEC_NAME_LEN, + KLP_RELOC_SEC_PREFIX "%s.%s", + sym_modname, sec->name)) + return -1; + + klp_rsec = find_section_by_name(elf, rsec_name); + if (!klp_rsec) { + klp_rsec = elf_create_section(elf, rsec_name, 0, + elf_rela_size(elf), + SHT_RELA, elf_addr_size(elf), + SHF_ALLOC | SHF_INFO_LINK | SHF_RELA_LIVEPATCH); + if (!klp_rsec) + return -1; + + klp_rsec->sh.sh_link = symtab->idx; + klp_rsec->sh.sh_info = sec->idx; + klp_rsec->base = sec; + } + + tmp = sec->rsec; + sec->rsec = klp_rsec; + if (!elf_create_reloc(elf, sec, offset, klp_sym, addend, klp_reloc->type)) + return -1; + sec->rsec = tmp; + + /* + * Fix up the corresponding KLP symbol: + */ + + klp_sym->sym.st_shndx = SHN_LIVEPATCH; + if (!gelf_update_sym(symtab->data, klp_sym->idx, &klp_sym->sym)) { + ERROR_ELF("gelf_update_sym"); + return -1; + } + + /* + * Disable the original non-KLP reloc by converting it to R_*_NONE: + */ + + reloc = find_reloc_by_dest(elf, sec, offset); + sym = reloc->sym; + sym->sym.st_shndx = SHN_LIVEPATCH; + set_reloc_type(elf, reloc, 0); + if (!gelf_update_sym(symtab->data, sym->idx, &sym->sym)) { + ERROR_ELF("gelf_update_sym"); + return -1; + } + } + + return 0; +} + +/* + * This runs on the livepatch module after all other linking has been done. It + * converts the intermediate __klp_relocs section into proper KLP relocs to be + * processed by livepatch. This needs to run last to avoid linker wreckage. + * Linkers don't tend to handle the "two rela sections for a single base + * section" case very well, nor do they appreciate SHN_LIVEPATCH. + */ +int cmd_klp_post_link(int argc, const char **argv) +{ + struct elf *elf; + + argc--; + argv++; + + if (argc != 1) { + fprintf(stderr, "%d\n", argc); + fprintf(stderr, "usage: objtool link \n"); + return -1; + } + + elf = elf_open_read(argv[0], O_RDWR); + if (!elf) + return -1; + + if (fix_klp_relocs(elf)) + return -1; + + if (elf_write(elf)) + return -1; + + return elf_close(elf); +} -- cgit v1.2.3