lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <cover.1750980516.git.jpoimboe@kernel.org>
Date: Thu, 26 Jun 2025 16:54:47 -0700
From: Josh Poimboeuf <jpoimboe@...nel.org>
To: x86@...nel.org
Cc: linux-kernel@...r.kernel.org,
	Petr Mladek <pmladek@...e.com>,
	Miroslav Benes <mbenes@...e.cz>,
	Joe Lawrence <joe.lawrence@...hat.com>,
	live-patching@...r.kernel.org,
	Song Liu <song@...nel.org>,
	laokz <laokz@...mail.com>,
	Jiri Kosina <jikos@...nel.org>,
	Marcos Paulo de Souza <mpdesouza@...e.com>,
	Weinan Liu <wnliu@...gle.com>,
	Fazla Mehrab <a.mehrab@...edance.com>,
	Chen Zhongjin <chenzhongjin@...wei.com>,
	Puranjay Mohan <puranjay@...nel.org>,
	Dylan Hatch <dylanbhatch@...gle.com>
Subject: [PATCH v3 00/64] objtool,livepatch: klp-build livepatch module generation

This series introduces new objtool features and a klp-build script to
generate livepatch modules using a source .patch as input.

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
    which injects #line directives into the source .patch to preserve
    the original line numbers at compile time.

The primary user interface is the klp-build script which does the
following:

  - Builds an original kernel with -function-sections and
    -fdata-sections, plus objtool function checksumming.

  - Applies the .patch file and rebuilds the kernel using the same
    options.

  - Runs 'objtool klp diff' to detect changed functions and generate
    intermediate binary diff objects.

  - Builds a kernel module which links the diff objects with some
    livepatch module init code (scripts/livepatch/init.c).

  - Finalizes the livepatch module (aka work around linker wreckage)
    using 'objtool klp post-link'.

I've tested with a variety of patches on defconfig and Fedora-config
kernels with both GCC and Clang.

These patches can also be found at:

  git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git klp-build-v3

Please test!

[1] https://github.com/dynup/kpatch

Changes since v2: https://lore.kernel.org/cover.1725334260.git.jpoimboe@kernel.org

  - Fix bisection issue (Joe)
  - Add CONFIG_KLP_BUILD to prevent powerpc from doing objtool-on-vmlinux (Joe)
  - Use arch_pc_relative_reloc() in arch_insn_adjusted_addend() (Peter)
  - Drop "objtool: Suppress section skipping warnings with --dryrun" (Peter)
  - Resurrect --backup (Peter)
  - Remove 16 byte prefix assumption (Peter, robot)
  - Bump SEC_NAME_LEN to 1024 (Peter)
  - Add sprintf error checking (Peter)
  - Fix brace usage (Peter)
  - Improve buggy linker sh_entsize warning (Peter)
  - Refactor add_jump_destinations() some more to work with ITS retpolines
  - Simplify error handling boilerplate
  - Run "make clean" before original kernel build
  - Fix .cmd file copying for CONFIG_MODVERSIONS (Joe)
  - Add a line to setlocalversion rather than replacing the whole file
  - Fix unset 'found' variable (Joe)
  - Define section entry struct sizes in asm-offset.c and bounds.c (Brian)
  - Fix STACK_FRAME_NON_STANDARD entry size unification (robot)
  - Check for .rodata.*, .bss.* (Joe)
  - Copy import_ns= entries from .modinfo (Joe)
  - Strip signature from 'git format-patch' patches (Joe)
  - Fix add_jump_destinations() (robot)
  - ARCH=um fixes (robot)
  - Fix vmlinux.o/vmlinux sympos mismatch
  - Add klp-build --jobs option
  - Allow .git to be a file (Dylan)
  - Static branch/call fixes (Joe)
  - Disable .printk_index support for now (Joe)
  - Strip .BTF section (Joe)

Josh Poimboeuf (64):
  s390/vmlinux.lds.S: Prevent thunk functions from getting placed with
    normal text
  vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and related macros
  x86/module: Improve relocation error messages
  x86/kprobes: Remove STACK_FRAME_NON_STANDARD annotation
  compiler: Tweak __UNIQUE_ID() naming
  compiler.h: Make addressable symbols less of an eyesore
  elfnote: Change ELFNOTE() to use __UNIQUE_ID()
  kbuild: Remove 'kmod_' prefix from __KBUILD_MODNAME
  modpost: Ignore unresolved section bounds symbols
  x86/alternative: Refactor INT3 call emulation selftest
  objtool: Make find_symbol_containing() less arbitrary
  objtool: Fix broken error handling in read_symbols()
  objtool: Propagate elf_truncate_section() error in elf_write()
  objtool: Remove error handling boilerplate
  objtool: Add empty symbols to the symbol tree again
  objtool: Fix interval tree insertion for zero-length symbols
  objtool: Fix weak symbol detection
  objtool: Fix x86 addend calculation
  objtool: Fix __pa_symbol() relocation handling
  objtool: Fix "unexpected end of section" warning for alternatives
  objtool: Check for missing annotation entries in read_annotate()
  objtool: Const string cleanup
  objtool: Clean up compiler flag usage
  objtool: Remove .parainstructions reference
  objtool: Convert elf iterator macros to use 'struct elf'
  objtool: Add section/symbol type helpers
  objtool: Mark .cold subfunctions
  objtool: Fix weak symbol hole detection for .cold functions
  objtool: Mark prefix functions
  objtool: Simplify reloc offset calculation in unwind_read_hints()
  objtool: Avoid emptying lists for duplicate sections
  objtool: Rename --Werror to --werror
  objtool: Resurrect --backup option
  objtool: Reindent check_options[]
  objtool: Refactor add_jump_destinations()
  objtool: Simplify special symbol handling in elf_update_symbol()
  objtool: Generalize elf_create_symbol()
  objtool: Generalize elf_create_section()
  objtool: Add elf_create_data()
  objtool: Add elf_create_reloc() and elf_init_reloc()
  objtool: Add elf_create_file()
  kbuild,x86: Fix special section module permissions
  x86/alternative: Define ELF section entry size for alternatives
  x86/jump_label: Define ELF section entry size for jump labels
  x86/static_call: Define ELF section entry size of static calls
  x86/extable: Define ELF section entry size for exception table
  x86/bug: Define ELF section entry size for bug table
  x86/orc: Define ELF section entry size for unwind hints
  objtool: Unify STACK_FRAME_NON_STANDARD entry sizes
  objtool/klp: Add --checksum option to generate per-function checksums
  objtool/klp: Add --debug-checksum=<funcs> to show per-instruction
    checksums
  objtool/klp: Introduce klp diff subcommand for diffing object files
  objtool/klp: Add --debug option to show cloning decisions
  objtool/klp: Add post-link subcommand to finalize livepatch modules
  objtool: Disallow duplicate prefix symbols
  objtool: Add base objtool support for livepatch modules
  livepatch: Add CONFIG_KLP_BUILD
  kbuild,objtool: Defer objtool validation step for CONFIG_KLP_BUILD
  livepatch/klp-build: Introduce fix-patch-lines script to avoid
    __LINE__ diff noise
  livepatch/klp-build: Add stub init code for livepatch modules
  livepatch/klp-build: Introduce klp-build script for generating
    livepatch modules
  livepatch/klp-build: Add --debug option to show cloning decisions
  livepatch/klp-build: Add --show-first-changed option to show function
    divergence
  livepatch: Introduce source code helpers for livepatch modules

 MAINTAINERS                                   |    3 +-
 arch/Kconfig                                  |    3 +
 arch/s390/include/asm/nospec-insn.h           |    2 +-
 arch/s390/kernel/vmlinux.lds.S                |    2 +-
 arch/um/include/asm/Kbuild                    |    1 -
 arch/um/include/shared/common-offsets.h       |    3 +
 arch/x86/Kconfig                              |    2 +
 arch/x86/include/asm/alternative.h            |    5 +-
 arch/x86/include/asm/asm.h                    |   22 +-
 arch/x86/include/asm/bug.h                    |   45 +-
 arch/x86/include/asm/jump_label.h             |   17 +-
 arch/x86/include/asm/static_call.h            |    3 +-
 arch/x86/kernel/alternative.c                 |   51 +-
 arch/x86/kernel/asm-offsets.c                 |    3 +
 arch/x86/kernel/kprobes/opt.c                 |    4 -
 arch/x86/kernel/module.c                      |   15 +-
 arch/x86/um/shared/sysdep/kernel-offsets.h    |    2 +
 include/asm-generic/vmlinux.lds.h             |   40 +-
 include/linux/compiler.h                      |    8 +-
 include/linux/elfnote.h                       |   13 +-
 include/linux/init.h                          |    3 +-
 include/linux/livepatch.h                     |   25 +-
 include/linux/livepatch_external.h            |   76 +
 include/linux/livepatch_helpers.h             |   79 +
 include/linux/objtool.h                       |   13 +-
 include/linux/static_call.h                   |    6 -
 include/linux/static_call_types.h             |    6 +
 kernel/bounds.c                               |   13 +
 kernel/livepatch/Kconfig                      |   12 +
 kernel/livepatch/core.c                       |    8 +-
 scripts/Makefile.lib                          |    6 +-
 scripts/Makefile.modfinal                     |   19 +-
 scripts/Makefile.vmlinux_o                    |    2 +-
 scripts/link-vmlinux.sh                       |    3 +-
 scripts/livepatch/fix-patch-lines             |   79 +
 scripts/livepatch/init.c                      |  108 ++
 scripts/livepatch/klp-build                   |  831 ++++++++
 scripts/mod/devicetable-offsets.c             |    1 +
 scripts/mod/modpost.c                         |    5 +
 scripts/module.lds.S                          |   26 +-
 tools/include/linux/interval_tree_generic.h   |    2 +-
 tools/include/linux/livepatch_external.h      |   76 +
 tools/include/linux/static_call_types.h       |    6 +
 tools/include/linux/string.h                  |   14 +
 tools/objtool/Build                           |    4 +-
 tools/objtool/Makefile                        |   48 +-
 tools/objtool/arch/loongarch/decode.c         |    6 +-
 tools/objtool/arch/powerpc/decode.c           |    6 +-
 tools/objtool/arch/x86/decode.c               |   62 +-
 tools/objtool/arch/x86/special.c              |    2 +-
 tools/objtool/builtin-check.c                 |   95 +-
 tools/objtool/builtin-klp.c                   |   53 +
 tools/objtool/check.c                         |  826 ++++----
 tools/objtool/elf.c                           |  765 ++++++--
 tools/objtool/include/objtool/arch.h          |    5 +-
 tools/objtool/include/objtool/builtin.h       |    9 +-
 tools/objtool/include/objtool/check.h         |    6 +-
 tools/objtool/include/objtool/checksum.h      |   43 +
 .../objtool/include/objtool/checksum_types.h  |   25 +
 tools/objtool/include/objtool/elf.h           |  185 +-
 tools/objtool/include/objtool/klp.h           |   35 +
 tools/objtool/include/objtool/objtool.h       |    6 +-
 tools/objtool/include/objtool/util.h          |   19 +
 tools/objtool/include/objtool/warn.h          |   40 +
 tools/objtool/klp-diff.c                      | 1677 +++++++++++++++++
 tools/objtool/klp-post-link.c                 |  168 ++
 tools/objtool/objtool.c                       |   42 +-
 tools/objtool/orc_gen.c                       |    8 +-
 tools/objtool/special.c                       |   13 +-
 tools/objtool/sync-check.sh                   |    1 +
 tools/objtool/weak.c                          |    7 +
 71 files changed, 5007 insertions(+), 812 deletions(-)
 create mode 100644 include/linux/livepatch_external.h
 create mode 100644 include/linux/livepatch_helpers.h
 create mode 100755 scripts/livepatch/fix-patch-lines
 create mode 100644 scripts/livepatch/init.c
 create mode 100755 scripts/livepatch/klp-build
 create mode 100644 tools/include/linux/livepatch_external.h
 create mode 100644 tools/objtool/builtin-klp.c
 create mode 100644 tools/objtool/include/objtool/checksum.h
 create mode 100644 tools/objtool/include/objtool/checksum_types.h
 create mode 100644 tools/objtool/include/objtool/klp.h
 create mode 100644 tools/objtool/include/objtool/util.h
 create mode 100644 tools/objtool/klp-diff.c
 create mode 100644 tools/objtool/klp-post-link.c

Diff between v2 and v3:

diff --git a/arch/um/include/asm/Kbuild b/arch/um/include/asm/Kbuild
index 04ab3b653a48..1934fa0df888 100644
--- a/arch/um/include/asm/Kbuild
+++ b/arch/um/include/asm/Kbuild
@@ -5,7 +5,6 @@ generic-y += device.h
 generic-y += dma-mapping.h
 generic-y += emergency-restart.h
 generic-y += exec.h
-generic-y += extable.h
 generic-y += ftrace.h
 generic-y += hw_irq.h
 generic-y += irq_regs.h
diff --git a/arch/um/include/shared/common-offsets.h b/arch/um/include/shared/common-offsets.h
index 8ca66a1918c3..a6f77cb6aa7e 100644
--- a/arch/um/include/shared/common-offsets.h
+++ b/arch/um/include/shared/common-offsets.h
@@ -18,3 +18,6 @@ DEFINE(UM_NSEC_PER_USEC, NSEC_PER_USEC);
 DEFINE(UM_KERN_GDT_ENTRY_TLS_ENTRIES, GDT_ENTRY_TLS_ENTRIES);
 
 DEFINE(UM_SECCOMP_ARCH_NATIVE, SECCOMP_ARCH_NATIVE);
+
+DEFINE(ALT_INSTR_SIZE, sizeof(struct alt_instr));
+DEFINE(EXTABLE_SIZE,   sizeof(struct exception_table_entry));
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 62faa62b5959..448c6bfb71d6 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -266,6 +266,7 @@ config X86
 	select HAVE_FUNCTION_ERROR_INJECTION
 	select HAVE_KRETPROBES
 	select HAVE_RETHOOK
+	select HAVE_KLP_BUILD			if X86_64
 	select HAVE_LIVEPATCH			if X86_64
 	select HAVE_MIXED_BREAKPOINTS_REGS
 	select HAVE_MOD_ARCH_SPECIFIC
diff --git a/arch/x86/include/asm/alternative.h b/arch/x86/include/asm/alternative.h
index e206e0f96568..eb24d9ba30d7 100644
--- a/arch/x86/include/asm/alternative.h
+++ b/arch/x86/include/asm/alternative.h
@@ -16,8 +16,6 @@
 #define ALT_DIRECT_CALL(feature) ((ALT_FLAG_DIRECT_CALL << ALT_FLAGS_SHIFT) | (feature))
 #define ALT_CALL_ALWAYS		ALT_DIRECT_CALL(X86_FEATURE_ALWAYS)
 
-#define ALTINSTR_SIZE		14
-
 #ifndef __ASSEMBLER__
 
 #include <linux/stddef.h>
@@ -200,7 +198,7 @@ static inline int alternatives_text_reserved(void *start, void *end)
 
 #define ALTINSTR_ENTRY(ft_flags)					      \
 	".pushsection .altinstructions, \"aM\", @progbits, "		      \
-		      __stringify(ALTINSTR_SIZE) "\n"			      \
+		      __stringify(ALT_INSTR_SIZE) "\n"			      \
 	" .long 771b - .\n"				/* label           */ \
 	" .long 774f - .\n"				/* new instruction */ \
 	" .4byte " __stringify(ft_flags) "\n"		/* feature + flags */ \
@@ -363,7 +361,7 @@ void nop_func(void);
 741:									\
 	.skip -(((744f-743f)-(741b-740b)) > 0) * ((744f-743f)-(741b-740b)),0x90	;\
 742:									\
-	.pushsection .altinstructions, "aM", @progbits, ALTINSTR_SIZE ;	\
+	.pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE ;\
 	altinstr_entry 740b,743f,flag,742b-740b,744f-743f ;		\
 	.popsection ;							\
 	.pushsection .altinstr_replacement,"ax"	;			\
diff --git a/arch/x86/include/asm/asm.h b/arch/x86/include/asm/asm.h
index 62dff336f206..e9b6d2d006c6 100644
--- a/arch/x86/include/asm/asm.h
+++ b/arch/x86/include/asm/asm.h
@@ -136,9 +136,11 @@ static __always_inline __pure void *rip_rel_ptr(void *p)
 
 #ifdef __KERNEL__
 
-# include <asm/extable_fixup_types.h>
+#ifndef COMPILE_OFFSETS
+#include <asm/asm-offsets.h>
+#endif
 
-#define EXTABLE_SIZE 12
+# include <asm/extable_fixup_types.h>
 
 /* Exception table entry */
 #ifdef __ASSEMBLER__
diff --git a/arch/x86/include/asm/jump_label.h b/arch/x86/include/asm/jump_label.h
index 6081c33e1566..7a6b0e5d85c1 100644
--- a/arch/x86/include/asm/jump_label.h
+++ b/arch/x86/include/asm/jump_label.h
@@ -12,31 +12,34 @@
 #include <linux/stringify.h>
 #include <linux/types.h>
 
-#define JUMP_TABLE_ENTRY(key, label, size)				\
-	".pushsection __jump_table, \"aM\", @progbits, " size "\n\t"	\
-	_ASM_ALIGN "\n\t"						\
-	".long 1b - . \n\t"						\
-	".long " label " - . \n\t"					\
-	_ASM_PTR " " key " - . \n\t"					\
+#ifndef COMPILE_OFFSETS
+#include <generated/bounds.h>
+#endif
+
+#define JUMP_TABLE_ENTRY(key, label)				\
+	".pushsection __jump_table,  \"aM\", @progbits, "	\
+	__stringify(JUMP_ENTRY_SIZE) "\n\t"			\
+	_ASM_ALIGN "\n\t"					\
+	".long 1b - . \n\t"					\
+	".long " label " - . \n\t"				\
+	_ASM_PTR " " key " - . \n\t"				\
 	".popsection \n\t"
 
 /* This macro is also expanded on the Rust side. */
 #ifdef CONFIG_HAVE_JUMP_LABEL_HACK
-#define ARCH_STATIC_BRANCH_ASM(key, label, size)	\
+#define ARCH_STATIC_BRANCH_ASM(key, label)		\
 	"1: jmp " label " # objtool NOPs this \n\t"	\
-	JUMP_TABLE_ENTRY(key " + 2", label, size)
+	JUMP_TABLE_ENTRY(key " + 2", label)
 #else /* !CONFIG_HAVE_JUMP_LABEL_HACK */
-#define ARCH_STATIC_BRANCH_ASM(key, label, size)	\
+#define ARCH_STATIC_BRANCH_ASM(key, label)		\
 	"1: .byte " __stringify(BYTES_NOP5) "\n\t"	\
-	JUMP_TABLE_ENTRY(key, label, size)
+	JUMP_TABLE_ENTRY(key, label)
 #endif /* CONFIG_HAVE_JUMP_LABEL_HACK */
 
 static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch)
 {
-	asm goto(ARCH_STATIC_BRANCH_ASM("%c[key] + %c[branch]", "%l[l_yes]", "%c[size]")
-		 : : [key] "i" (key), [branch] "i" (branch),
-		     [size] "i" (sizeof(struct jump_entry))
-		 : : l_yes);
+	asm goto(ARCH_STATIC_BRANCH_ASM("%c0 + %c1", "%l[l_yes]")
+		: :  "i" (key), "i" (branch) : : l_yes);
 
 	return false;
 l_yes:
@@ -47,10 +50,8 @@ static __always_inline bool arch_static_branch_jump(struct static_key * const ke
 {
 	asm goto("1:"
 		"jmp %l[l_yes]\n\t"
-		JUMP_TABLE_ENTRY("%c[key] + %c[branch]", "%l[l_yes]", "%c[size]")
-		: : [key] "i" (key), [branch] "i" (branch),
-		    [size] "i" (sizeof(struct jump_entry))
-		: : l_yes);
+		JUMP_TABLE_ENTRY("%c0 + %c1", "%l[l_yes]")
+		: :  "i" (key), "i" (branch) : : l_yes);
 
 	return false;
 l_yes:
diff --git a/arch/x86/include/asm/static_call.h b/arch/x86/include/asm/static_call.h
index 41502bd2afd6..e03ad9bbbf59 100644
--- a/arch/x86/include/asm/static_call.h
+++ b/arch/x86/include/asm/static_call.h
@@ -58,7 +58,8 @@
 	ARCH_DEFINE_STATIC_CALL_TRAMP(name, __static_call_return0)
 
 #define ARCH_ADD_TRAMP_KEY(name)					\
-	asm(".pushsection .static_call_tramp_key, \"a\"		\n"	\
+	asm(".pushsection .static_call_tramp_key, \"aM\", @progbits, "	\
+	    __stringify(STATIC_CALL_TRAMP_KEY_SIZE) "\n"		\
 	    ".long " STATIC_CALL_TRAMP_STR(name) " - .		\n"	\
 	    ".long " STATIC_CALL_KEY_STR(name) " - .		\n"	\
 	    ".popsection					\n")
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 499dc1ca2050..bacd6c157626 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -622,8 +622,6 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
 	u8 *instr, *replacement;
 	struct alt_instr *a, *b;
 
-	BUILD_BUG_ON(ALTINSTR_SIZE != sizeof(struct alt_instr));
-
 	DPRINTK(ALT, "alt table %px, -> %px", start, end);
 
 	/*
diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c
index 6259b474073b..3d3eef7fae32 100644
--- a/arch/x86/kernel/asm-offsets.c
+++ b/arch/x86/kernel/asm-offsets.c
@@ -123,4 +123,7 @@ static void __used common(void)
 	OFFSET(ARIA_CTX_rounds, aria_ctx, rounds);
 #endif
 
+	BLANK();
+	DEFINE(ALT_INSTR_SIZE,	 sizeof(struct alt_instr));
+	DEFINE(EXTABLE_SIZE,	 sizeof(struct exception_table_entry));
 }
diff --git a/arch/x86/kernel/unwind_orc.c b/arch/x86/kernel/unwind_orc.c
index 4624d6d916a2..977ee75e047c 100644
--- a/arch/x86/kernel/unwind_orc.c
+++ b/arch/x86/kernel/unwind_orc.c
@@ -199,8 +199,6 @@ static struct orc_entry *orc_find(unsigned long ip)
 {
 	static struct orc_entry *orc;
 
-	BUILD_BUG_ON(UNWIND_HINT_SIZE != sizeof(struct unwind_hint));
-
 	if (ip == 0)
 		return &null_orc_entry;
 
diff --git a/arch/x86/um/shared/sysdep/kernel-offsets.h b/arch/x86/um/shared/sysdep/kernel-offsets.h
index 6fd1ed400399..8215a0200ddd 100644
--- a/arch/x86/um/shared/sysdep/kernel-offsets.h
+++ b/arch/x86/um/shared/sysdep/kernel-offsets.h
@@ -1,4 +1,5 @@
 /* SPDX-License-Identifier: GPL-2.0 */
+#define COMPILE_OFFSETS
 #include <linux/stddef.h>
 #include <linux/sched.h>
 #include <linux/elf.h>
@@ -7,6 +8,7 @@
 #include <linux/audit.h>
 #include <asm/mman.h>
 #include <asm/seccomp.h>
+#include <asm/extable.h>
 
 /* workaround for a warning with -Wmissing-prototypes */
 void foo(void);
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 3a0865e0a1a7..928e175254f6 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -91,7 +91,7 @@
  * but exclude '.text..*'.
  *
  * Special .text.* sections that are typically grouped separately, such as
- * .text.unlikely or .text.hot, must be matched explicitly before invoking
+ * .text.unlikely or .text.hot, must be matched explicitly before using
  * TEXT_MAIN.
  */
 #define TEXT_MAIN .text .text.[0-9a-zA-Z_]*
diff --git a/include/linux/compiler.h b/include/linux/compiler.h
index b90422671874..2039da81cf16 100644
--- a/include/linux/compiler.h
+++ b/include/linux/compiler.h
@@ -287,7 +287,7 @@ static inline void *offset_to_ptr(const int *off)
  */
 #define ___ADDRESSABLE(sym, __attrs)						\
 	static void * __used __attrs						\
-	__UNIQUE_ID(__PASTE(addressable_,sym)) = (void *)(uintptr_t)&sym;
+	__UNIQUE_ID(__PASTE(addressable_, sym)) = (void *)(uintptr_t)&sym;
 
 #define __ADDRESSABLE(sym) \
 	___ADDRESSABLE(sym, __section(".discard.addressable"))
diff --git a/include/linux/jump_label.h b/include/linux/jump_label.h
index 9ff1ecc8e7a8..fdb79dd1ebd8 100644
--- a/include/linux/jump_label.h
+++ b/include/linux/jump_label.h
@@ -110,20 +110,16 @@ struct static_key {
 #endif /* __ASSEMBLY__ */
 
 #ifdef CONFIG_JUMP_LABEL
+#include <asm/jump_label.h>
+
+#ifndef __ASSEMBLY__
+#ifdef CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE
 
-#if defined(CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE) && !defined(__ASSEMBLY__)
-/* Must be defined before including <asm/jump_label.h> */
 struct jump_entry {
 	s32 code;
 	s32 target;
 	long key;	// key may be far away from the core kernel under KASLR
 };
-#endif
-
-#include <asm/jump_label.h>
-
-#ifndef __ASSEMBLY__
-#ifdef CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE
 
 static inline unsigned long jump_entry_code(const struct jump_entry *entry)
 {
@@ -142,7 +138,7 @@ static inline struct static_key *jump_entry_key(const struct jump_entry *entry)
 	return (struct static_key *)((unsigned long)&entry->key + offset);
 }
 
-#else /* !CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE */
+#else
 
 static inline unsigned long jump_entry_code(const struct jump_entry *entry)
 {
@@ -159,7 +155,7 @@ static inline struct static_key *jump_entry_key(const struct jump_entry *entry)
 	return (struct static_key *)((unsigned long)entry->key & ~3UL);
 }
 
-#endif /* !CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE */
+#endif
 
 static inline bool jump_entry_is_branch(const struct jump_entry *entry)
 {
@@ -188,8 +184,8 @@ static inline int jump_entry_size(struct jump_entry *entry)
 #endif
 }
 
-#endif /* !__ASSEMBLY__ */
-#endif /* CONFIG_JUMP_LABEL */
+#endif
+#endif
 
 #ifndef __ASSEMBLY__
 
diff --git a/include/linux/livepatch_helpers.h b/include/linux/livepatch_helpers.h
index 09f4a2d53fd7..337bee91d7da 100644
--- a/include/linux/livepatch_helpers.h
+++ b/include/linux/livepatch_helpers.h
@@ -35,6 +35,17 @@
 	klp_post_unpatch_t __used __section(KLP_CALLBACK_PTRS)			\
 		__PASTE(__KLP_POST_UNPATCH_PREFIX, KLP_OBJNAME) = func
 
+/*
+ * KLP_STATIC_CALL
+ *
+ * Replace static_call() usage with this macro when create-diff-object
+ * recommends it due to the original static call key living in a module.
+ *
+ * This converts the static call to a regular indirect call.
+ */
+#define KLP_STATIC_CALL(name) \
+	((typeof(STATIC_CALL_TRAMP(name))*)(STATIC_CALL_KEY(name).func))
+
 /* Syscall patching */
 
 #define KLP_SYSCALL_DEFINE1(name, ...) KLP_SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
diff --git a/include/linux/objtool.h b/include/linux/objtool.h
index d4137a46ee70..7d7bb7f1af69 100644
--- a/include/linux/objtool.h
+++ b/include/linux/objtool.h
@@ -2,14 +2,16 @@
 #ifndef _LINUX_OBJTOOL_H
 #define _LINUX_OBJTOOL_H
 
+#ifndef COMPILE_OFFSETS
+#include <generated/bounds.h>
+#endif
+
 #include <linux/objtool_types.h>
 
 #ifdef CONFIG_OBJTOOL
 
 #include <asm/asm.h>
 
-#define UNWIND_HINT_SIZE 12
-
 #ifndef __ASSEMBLY__
 
 #define UNWIND_HINT(type, sp_reg, sp_offset, signal)		\
@@ -33,10 +35,9 @@
  *
  * For more information, see tools/objtool/Documentation/objtool.txt.
  */
-#define STACK_FRAME_NON_STANDARD(func)						\
-	asm(".pushsection .discard.func_stack_frame_non_standard, \"aw\"\n\t"	\
-	    ".long " __stringify(func) " - .\n\t"				\
-	    ".popsection")
+#define STACK_FRAME_NON_STANDARD(func) \
+	static void __used __section(".discard.func_stack_frame_non_standard") \
+		*__func_stack_frame_non_standard_##func = func
 
 /*
  * STACK_FRAME_NON_STANDARD_FP() is a frame-pointer-specific function ignore
@@ -105,7 +106,7 @@
 
 .macro STACK_FRAME_NON_STANDARD func:req
 	.pushsection .discard.func_stack_frame_non_standard, "aw"
-	.long \func - .
+	.quad \func
 	.popsection
 .endm
 
diff --git a/include/linux/static_call.h b/include/linux/static_call.h
index 78a77a4ae0ea..5210612817f2 100644
--- a/include/linux/static_call.h
+++ b/include/linux/static_call.h
@@ -172,12 +172,6 @@ struct static_call_mod {
 	struct static_call_site *sites;
 };
 
-/* For finding the key associated with a trampoline */
-struct static_call_tramp_key {
-	s32 tramp;
-	s32 key;
-};
-
 extern void __static_call_update(struct static_call_key *key, void *tramp, void *func);
 extern int static_call_mod_init(struct module *mod);
 extern int static_call_text_reserved(void *start, void *end);
diff --git a/include/linux/static_call_types.h b/include/linux/static_call_types.h
index 5a00b8b2cf9f..eb772df625d4 100644
--- a/include/linux/static_call_types.h
+++ b/include/linux/static_call_types.h
@@ -34,6 +34,12 @@ struct static_call_site {
 	s32 key;
 };
 
+/* For finding the key associated with a trampoline */
+struct static_call_tramp_key {
+	s32 tramp;
+	s32 key;
+};
+
 #define DECLARE_STATIC_CALL(name, func)					\
 	extern struct static_call_key STATIC_CALL_KEY(name);		\
 	extern typeof(func) STATIC_CALL_TRAMP(name);
diff --git a/kernel/bounds.c b/kernel/bounds.c
index 29b2cd00df2c..f9bc13727721 100644
--- a/kernel/bounds.c
+++ b/kernel/bounds.c
@@ -6,12 +6,16 @@
  */
 
 #define __GENERATING_BOUNDS_H
+#define COMPILE_OFFSETS
 /* Include headers that define the enum constants of interest */
 #include <linux/page-flags.h>
 #include <linux/mmzone.h>
 #include <linux/kbuild.h>
 #include <linux/log2.h>
 #include <linux/spinlock_types.h>
+#include <linux/jump_label.h>
+#include <linux/static_call_types.h>
+#include <linux/objtool_types.h>
 
 int main(void)
 {
@@ -28,6 +32,15 @@ int main(void)
 #else
 	DEFINE(LRU_GEN_WIDTH, 0);
 	DEFINE(__LRU_REFS_WIDTH, 0);
+#endif
+#if defined(CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE) && defined(CONFIG_JUMP_LABEL)
+	DEFINE(JUMP_ENTRY_SIZE, sizeof(struct jump_entry));
+#endif
+#ifdef CONFIG_HAVE_STATIC_CALL_INLINE
+	DEFINE(STATIC_CALL_TRAMP_KEY_SIZE, sizeof(struct static_call_tramp_key));
+#endif
+#ifdef CONFIG_OBJTOOL
+	DEFINE(UNWIND_HINT_SIZE, sizeof(struct unwind_hint));
 #endif
 	/* End of constants */
 
diff --git a/kernel/extable.c b/kernel/extable.c
index 0ae3ee2ef266..71f482581cab 100644
--- a/kernel/extable.c
+++ b/kernel/extable.c
@@ -55,8 +55,6 @@ const struct exception_table_entry *search_exception_tables(unsigned long addr)
 {
 	const struct exception_table_entry *e;
 
-	BUILD_BUG_ON(EXTABLE_SIZE != sizeof(struct exception_table_entry));
-
 	e = search_kernel_exception_table(addr);
 	if (!e)
 		e = search_module_extables(addr);
diff --git a/kernel/livepatch/Kconfig b/kernel/livepatch/Kconfig
index 53d51ed619a3..4c0a9c18d0b2 100644
--- a/kernel/livepatch/Kconfig
+++ b/kernel/livepatch/Kconfig
@@ -18,3 +18,15 @@ config LIVEPATCH
 	  module uses the interface provided by this option to register
 	  a patch, causing calls to patched functions to be redirected
 	  to new function code contained in the patch module.
+
+config HAVE_KLP_BUILD
+	bool
+	help
+	  Arch supports klp-build
+
+config KLP_BUILD
+	def_bool y
+	depends on LIVEPATCH && HAVE_KLP_BUILD
+	select OBJTOOL
+	help
+	  Enable klp-build support
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 41420d24e62e..28a1c08e3b22 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -197,7 +197,7 @@ objtool-args = $(objtool-args-y)					\
 	$(if $(delay-objtool), --link)					\
 	$(if $(part-of-module), --module)
 
-delay-objtool := $(or $(CONFIG_LTO_CLANG),$(CONFIG_X86_KERNEL_IBT),$(CONFIG_LIVEPATCH))
+delay-objtool := $(or $(CONFIG_LTO_CLANG),$(CONFIG_X86_KERNEL_IBT),$(CONFIG_KLP_BUILD))
 
 cmd_objtool = $(if $(objtool-enabled), ; $(objtool) $(objtool-args) $@)
 cmd_gen_objtooldep = $(if $(objtool-enabled), { echo ; echo '$@: $$(wildcard $(objtool))' ; } >> $(dot-target).cmd)
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 878d0d25a461..7a888e1ff70f 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -31,7 +31,8 @@ ccflags-remove-y := $(CC_FLAGS_CFI)
 ifdef CONFIG_NEED_MODULE_PERMISSIONS_FIX
 cmd_fix_mod_permissions =						\
 	$(OBJCOPY) --set-section-flags __jump_table=alloc,data		\
-		   --set-section-flags __bug_table=alloc,data $@
+		   --set-section-flags __bug_table=alloc,data $@	\
+		   --set-section-flags .static_call_sites=alloc,data $@
 endif
 
 quiet_cmd_ld_ko_o = LD [M]  $@
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index acffa3c935f2..59f875236292 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -61,7 +61,7 @@ vmlinux_link()
 	shift
 
 	if is_enabled CONFIG_LTO_CLANG || is_enabled CONFIG_X86_KERNEL_IBT ||
-	   is_enabled CONFIG_LIVEPATCH; then
+	   is_enabled CONFIG_KLP_BUILD; then
 		# Use vmlinux.o instead of performing the slow LTO link again.
 		objs=vmlinux.o
 		libs=
diff --git a/scripts/livepatch/fix-patch-lines b/scripts/livepatch/fix-patch-lines
index 73c5e3dea46e..fa7d4f6592e6 100755
--- a/scripts/livepatch/fix-patch-lines
+++ b/scripts/livepatch/fix-patch-lines
@@ -23,7 +23,7 @@ BEGIN {
 
 	in_hunk = 1
 
-	# for @@ -1,3 +1,4 @@:
+	# @@ -1,3 +1,4 @@:
 	#   1: line number in old file
 	#   3: how many lines the hunk covers in old file
 	#   1: line number in new file
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index f7d88726ed4f..e47056f75475 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -2,12 +2,11 @@
 # SPDX-License-Identifier: GPL-2.0
 #
 # Build a livepatch module
-#
 
-# shellcheck disable=SC1090
+# shellcheck disable=SC1090,SC2155
 
-if (( BASH_VERSINFO[0] < 4 \
-	|| (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then
+if (( BASH_VERSINFO[0]  < 4 || \
+     (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then
 		echo "error: this script requires bash 4.4+" >&2
 	exit 1
 fi
@@ -22,16 +21,16 @@ set -o nounset
 shopt -s lastpipe
 
 unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
+
 REPLACE=1
 SHORT_CIRCUIT=0
+JOBS="$(getconf _NPROCESSORS_ONLN)"
+VERBOSE="-s"
 shopt -o xtrace | grep -q 'on' && XTRACE=1
+
 # Avoid removing the previous $TMP_DIR until args have been fully processed.
 KEEP_TMP=1
 
-CPUS="$(getconf _NPROCESSORS_ONLN)"
-VERBOSE="-s"
-
-
 SCRIPT="$(basename "$0")"
 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines"
@@ -62,7 +61,7 @@ status() {
 }
 
 warn() {
-	echo "error: $(basename "$SCRIPT"): $*" >&2
+	echo "error: $SCRIPT: $*" >&2
 }
 
 die() {
@@ -99,7 +98,7 @@ cleanup() {
 	revert_patches "--recount"
 	restore_files
 	[[ "$KEEP_TMP" -eq 0 ]] && rm -rf "$TMP_DIR"
-	true
+	return 0
 }
 
 trap_err() {
@@ -116,12 +115,13 @@ Generate a livepatch module.
 
 Options:
    -f, --show-first-changed	Show address of first changed instruction
-   -o, --output <file.ko>	Output file [default: livepatch-<patch-name>.ko]
+   -j, --jobs=<jobs>		Build jobs to run simultaneously [default: $JOBS]
+   -o, --output=<file.ko>	Output file [default: livepatch-<patch-name>.ko]
        --no-replace		Disable livepatch atomic replace
    -v, --verbose		Pass V=1 to kernel/module builds
 
 Advanced Options:
-   -D, --debug			Show symbol/reloc cloning decisions
+   -d, --debug			Show symbol/reloc cloning decisions
    -S, --short-circuit=STEP	Start at build step (requires prior --keep-tmp)
 				   1|orig	Build original kernel (default)
 				   2|patched	Build patched kernel
@@ -142,8 +142,8 @@ process_args() {
 	local long
 	local args
 
-	short="hfo:vDS:T"
-	long="help,show-first-changed,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+	short="hfj:o:vdS:T"
+	long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
 
 	args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
 		echo; usage; exit
@@ -160,6 +160,10 @@ process_args() {
 				DIFF_CHECKSUM=1
 				shift
 				;;
+			-j | --jobs)
+				JOBS="$2"
+				shift 2
+				;;
 			-o | --output)
 				[[ "$2" != *.ko ]] && die "output filename should end with .ko"
 				OUTFILE="$2"
@@ -176,7 +180,7 @@ process_args() {
 				VERBOSE="V=1"
 				shift
 				;;
-			-D | --debug)
+			-d | --debug)
 				DEBUG_CLONE=1
 				keep_tmp=1
 				shift
@@ -233,14 +237,17 @@ validate_config() {
 	source "$CONFIG" || die "no .config file in $(dirname "$CONFIG")"
 	xtrace_restore
 
-	[[ -v CONFIG_LIVEPATCH ]]			\
-		|| die "CONFIG_LIVEPATCH not enabled"
+	[[ -v CONFIG_LIVEPATCH ]] ||			\
+		die "CONFIG_LIVEPATCH not enabled"
 
-	[[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]]	\
-		&& die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported"
+	[[ -v CONFIG_KLP_BUILD ]] ||			\
+		die "CONFIG_KLP_BUILD not enabled"
 
-	[[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]]		\
-		&& die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported"
+	[[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]] &&	\
+		die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported"
+
+	[[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]] &&	\
+		die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported"
 
 	return 0
 }
@@ -282,7 +289,7 @@ set_kernelversion() {
 	localversion="$(cd "$SRC" && KERNELVERSION="$localversion" ./scripts/setlocalversion)"
 	[[ -z "$localversion" ]] && die "setlocalversion failed"
 
-	echo "echo $localversion" > "$file"
+	sed -i "2i echo $localversion; exit 0" scripts/setlocalversion
 }
 
 get_patch_files() {
@@ -299,7 +306,7 @@ git_refresh() {
 	local patch="$1"
 	local files=()
 
-	[[ ! -d "$SRC/.git" ]] && return
+	[[ ! -e "$SRC/.git" ]] && return
 
 	get_patch_files "$patch" | mapfile -t files
 
@@ -334,7 +341,14 @@ apply_patch() {
 
 	[[ ! -f "$patch" ]] && die "$patch doesn't exist"
 
-	( cd "$SRC" && git apply "${extra_args[@]}" "$patch" )
+	(
+		cd "$SRC"
+
+		# The sed strips the version signature from 'git format-patch',
+		# otherwise 'git apply --recount' warns.
+		sed -n '/^-- /q;p' "$patch" |
+			git apply "${extra_args[@]}"
+	)
 
 	APPLIED_PATCHES+=("$patch")
 }
@@ -345,7 +359,12 @@ revert_patch() {
 	local extra_args=("$@")
 	local tmp=()
 
-	( cd "$SRC" && git apply --reverse "${extra_args[@]}" "$patch" )
+	(
+		cd "$SRC"
+
+		sed -n '/^-- /q;p' "$patch" |
+			git apply --reverse "${extra_args[@]}"
+	)
 	git_refresh "$patch"
 
 	for p in "${APPLIED_PATCHES[@]}"; do
@@ -373,9 +392,6 @@ revert_patches() {
 	done
 
 	APPLIED_PATCHES=()
-
-	# Make sure git actually sees the patches have been reverted.
-	[[ -d "$SRC/.git" ]] && (cd "$SRC" && git update-index -q --refresh)
 }
 
 validate_patches() {
@@ -412,10 +428,8 @@ refresh_patch() {
 	mkdir -p "$tmpdir/a"
 	mkdir -p "$tmpdir/b"
 
-	# Find all source files affected by the patch
-	grep0 -E '^(--- |\+\+\+ )[^ /]+' "$patch"	|
-		sed -E 's/(--- |\+\+\+ )[^ /]+\///'	|
-		sort | uniq | mapfile -t files
+	# Get all source files affected by the patch
+	get_patch_files "$patch" | mapfile -t files
 
 	# Copy orig source files to 'a'
 	( cd "$SRC" && echo "${files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" )
@@ -459,13 +473,26 @@ fix_patches() {
 	done
 }
 
+clean_kernel() {
+	local cmd=()
+
+	cmd=("make")
+	cmd+=("--silent")
+	cmd+=("-j$JOBS")
+	cmd+=("clean")
+
+	(
+		cd "$SRC"
+		"${cmd[@]}"
+	)
+}
+
 build_kernel() {
 	local log="$TMP_DIR/build.log"
 	local objtool_args=()
 	local cmd=()
 
 	objtool_args=("--checksum")
-	[[ -v OBJTOOL_ARGS ]] && objtool_args+=("${OBJTOOL_ARGS}")
 
 	cmd=("make")
 
@@ -487,7 +514,7 @@ build_kernel() {
 	cmd+=("KBUILD_MODPOST_WARN=1")
 
 	cmd+=("$VERBOSE")
-	cmd+=("-j$CPUS")
+	cmd+=("-j$JOBS")
 	cmd+=("KCFLAGS=-ffunction-sections -fdata-sections")
 	cmd+=("OBJTOOL_ARGS=${objtool_args[*]}")
 	cmd+=("vmlinux")
@@ -496,7 +523,7 @@ build_kernel() {
 	(
 		cd "$SRC"
 		"${cmd[@]}"							\
-			> >(tee -a "$log")					\
+			1> >(tee -a "$log")					\
 			2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2)
 	)
 }
@@ -512,18 +539,31 @@ find_objects() {
 		    -printf '%P\n'
 }
 
-# Copy all objects (.o archives) to $ORIG_DIR
+# Copy all .o archives to $ORIG_DIR
 copy_orig_objects() {
+	local files=()
 
 	rm -rf "$ORIG_DIR"
 	mkdir -p "$ORIG_DIR"
 
-	(
-		cd "$OBJ"
-		find_objects						\
-			| sed 's/\.ko$/.o/'				\
-			| xargs cp --parents --target-directory="$ORIG_DIR"
-	)
+	find_objects | mapfile -t files
+
+	xtrace_save "copying orig objects"
+	for _file in "${files[@]}"; do
+		local rel_file="${_file/.ko/.o}"
+		local file="$OBJ/$rel_file"
+		local file_dir="$(dirname "$file")"
+		local orig_file="$ORIG_DIR/$rel_file"
+		local orig_dir="$(dirname "$orig_file")"
+		local cmd_file="$file_dir/.$(basename "$file").cmd"
+
+		[[ ! -f "$file" ]] && die "missing $(basename "$file") for $_file"
+
+		mkdir -p "$orig_dir"
+		cp -f "$file" "$orig_dir"
+		[[ -e "$cmd_file" ]] && cp -f "$cmd_file" "$orig_dir"
+	done
+	xtrace_restore
 
 	mv -f "$TMP_DIR/build.log" "$ORIG_DIR"
 	touch "$TIMESTAMP"
@@ -531,9 +571,9 @@ copy_orig_objects() {
 
 # Copy all changed objects to $PATCHED_DIR
 copy_patched_objects() {
-	local found
 	local files=()
 	local opts=()
+	local found=0
 
 	rm -rf "$PATCHED_DIR"
 	mkdir -p "$PATCHED_DIR"
@@ -544,24 +584,25 @@ copy_patched_objects() {
 
 	find_objects "${opts[@]}" | mapfile -t files
 
-	xtrace_save "processing all objects"
+	xtrace_save "copying changed objects"
 	for _file in "${files[@]}"; do
 		local rel_file="${_file/.ko/.o}"
 		local file="$OBJ/$rel_file"
 		local orig_file="$ORIG_DIR/$rel_file"
 		local patched_file="$PATCHED_DIR/$rel_file"
+		local patched_dir="$(dirname "$patched_file")"
 
 		[[ ! -f "$file" ]] && die "missing $(basename "$file") for $_file"
 
 		cmp -s "$orig_file" "$file" && continue
 
-		mkdir -p "$(dirname "$patched_file")"
-		cp -f "$file" "$patched_file"
+		mkdir -p "$patched_dir"
+		cp -f "$file" "$patched_dir"
 		found=1
 	done
 	xtrace_restore
 
-	[[ -n "$found" ]] || die "no changes detected"
+	(( found == 0 )) && die "no changes detected"
 
 	mv -f "$TMP_DIR/build.log" "$PATCHED_DIR"
 }
@@ -610,9 +651,9 @@ diff_objects() {
 		(
 			cd "$ORIG_DIR"
 			"${cmd[@]}"							\
-				> >(tee -a "$log")					\
-				2> >(tee -a "$log" | "${filter[@]}" >&2)		\
-				|| die "objtool klp diff failed"
+				1> >(tee -a "$log")					\
+				2> >(tee -a "$log" | "${filter[@]}" >&2) ||		\
+				die "objtool klp diff failed"
 		)
 	done
 }
@@ -653,12 +694,12 @@ diff_checksums() {
 
 		(
 			cd "$ORIG_DIR"
-			"${cmd[@]}" "$opt" "$file" &> "$orig_log"	\
-				|| ( cat "$orig_log" >&2; die "objtool --debug-checksum failed" )
+			"${cmd[@]}" "$opt" "$file" &> "$orig_log" || \
+				( cat "$orig_log" >&2; die "objtool --debug-checksum failed" )
 
 			cd "$PATCHED_DIR"
-			"${cmd[@]}" "$opt" "$file" &> "$patched_log"	\
-				|| ( cat "$patched_log" >&2; die "objtool --debug-checksum failed" )
+			"${cmd[@]}" "$opt" "$file" &> "$patched_log" ||	\
+				( cat "$patched_log" >&2; die "objtool --debug-checksum failed" )
 		)
 
 		for func in ${funcs[$file]}; do
@@ -677,6 +718,7 @@ diff_checksums() {
 build_patch_module() {
 	local makefile="$KMOD_DIR/Kbuild"
 	local log="$KMOD_DIR/build.log"
+	local kmod_file
 	local cflags=()
 	local files=()
 	local cmd=()
@@ -694,19 +736,20 @@ build_patch_module() {
 
 	for file in "${files[@]}"; do
 		local rel_file="${file#"$DIFF_DIR"/}"
+		local orig_file="$ORIG_DIR/$rel_file"
+		local orig_dir="$(dirname "$orig_file")"
 		local kmod_file="$KMOD_DIR/$rel_file"
-		local cmd_file
+		local kmod_dir="$(dirname "$kmod_file")"
+		local cmd_file="$orig_dir/.$(basename "$file").cmd"
 
-		mkdir -p "$(dirname "$kmod_file")"
-		cp -f "$file" "$kmod_file"
+		mkdir -p "$kmod_dir"
+		cp -f "$file" "$kmod_dir"
+		[[ -e "$cmd_file" ]] && cp -f "$cmd_file" "$kmod_dir"
 
 		# Tell kbuild this is a prebuilt object
 		cp -f "$file" "${kmod_file}_shipped"
 
 		echo -n " $rel_file" >> "$makefile"
-
-		cmd_file="$ORIG_DIR/$(dirname "$rel_file")/.$(basename "$rel_file").cmd"
-		[[ -e "$cmd_file" ]] && cp -f "$cmd_file" "$(dirname "$kmod_file")"
 	done
 
 	echo >> "$makefile"
@@ -717,7 +760,7 @@ build_patch_module() {
 
 	cmd=("make")
 	cmd+=("$VERBOSE")
-	cmd+=("-j$CPUS")
+	cmd+=("-j$JOBS")
 	cmd+=("--directory=.")
 	cmd+=("M=$KMOD_DIR")
 	cmd+=("KCFLAGS=${cflags[*]}")
@@ -726,17 +769,22 @@ build_patch_module() {
 	(
 		cd "$SRC"
 		"${cmd[@]}"							\
-			>  >(tee -a "$log")					\
+			1> >(tee -a "$log")					\
 			2> >(tee -a "$log" >&2)
 	)
 
+	kmod_file="$KMOD_DIR/$NAME.ko"
+
 	# Save off the intermediate binary for debugging
-	cp -f "$KMOD_DIR/$NAME.ko" "$KMOD_DIR/$NAME.ko.orig"
+	cp -f "$kmod_file" "$kmod_file.orig"
+
+	# Work around issue where slight .config change makes corrupt BTF
+	objcopy --remove-section=.BTF "$kmod_file"
 
 	# Fix (and work around) linker wreckage for klp syms / relocs
-	"$SRC/tools/objtool/objtool" klp post-link "$KMOD_DIR/$NAME.ko" || die "objtool klp post-link failed"
+	"$SRC/tools/objtool/objtool" klp post-link "$kmod_file" || die "objtool klp post-link failed"
 
-	cp -f "$KMOD_DIR/$NAME.ko" "$OUTFILE"
+	cp -f "$kmod_file" "$OUTFILE"
 }
 
 
@@ -746,8 +794,10 @@ process_args "$@"
 do_init
 
 if (( SHORT_CIRCUIT <= 1 )); then
-	status "Building original kernel"
+	status "Validating patches"
 	validate_patches
+	status "Building original kernel"
+	clean_kernel
 	build_kernel
 	status "Copying original object files"
 	copy_orig_objects
diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c
index d3d00e85edf7..ef2ffb68f69d 100644
--- a/scripts/mod/devicetable-offsets.c
+++ b/scripts/mod/devicetable-offsets.c
@@ -1,4 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
+#define COMPILE_OFFSETS
 #include <linux/kbuild.h>
 #include <linux/mod_devicetable.h>
 
diff --git a/tools/include/linux/static_call_types.h b/tools/include/linux/static_call_types.h
index 5a00b8b2cf9f..eb772df625d4 100644
--- a/tools/include/linux/static_call_types.h
+++ b/tools/include/linux/static_call_types.h
@@ -34,6 +34,12 @@ struct static_call_site {
 	s32 key;
 };
 
+/* For finding the key associated with a trampoline */
+struct static_call_tramp_key {
+	s32 tramp;
+	s32 key;
+};
+
 #define DECLARE_STATIC_CALL(name, func)					\
 	extern struct static_call_key STATIC_CALL_KEY(name);		\
 	extern typeof(func) STATIC_CALL_TRAMP(name);
diff --git a/tools/include/linux/string.h b/tools/include/linux/string.h
index 8499f509f03e..51ad3cf4fa82 100644
--- a/tools/include/linux/string.h
+++ b/tools/include/linux/string.h
@@ -44,6 +44,20 @@ static inline bool strstarts(const char *str, const char *prefix)
 	return strncmp(str, prefix, strlen(prefix)) == 0;
 }
 
+/*
+ * Checks if a string ends with another.
+ */
+static inline bool str_ends_with(const char *str, const char *substr)
+{
+	size_t len = strlen(str);
+	size_t sublen = strlen(substr);
+
+	if (sublen > len)
+		return false;
+
+	return !strcmp(str + len - sublen, substr);
+}
+
 extern char * __must_check skip_spaces(const char *);
 
 extern char *strim(char *);
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index 3b40afe144fe..c4d6b90b1134 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -83,14 +83,8 @@ s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
 {
 	s64 addend = reloc_addend(reloc);
 
-	switch (reloc_type(reloc)) {
-	case R_X86_64_PC32:
-	case R_X86_64_PLT32:
+	if (arch_pc_relative_reloc(reloc))
 		addend += insn->offset + insn->len - reloc_offset(reloc);
-		break;
-	default:
-		break;
-	}
 
 	return phys_to_virt(addend);
 }
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index 84918593d935..cd2c4a387100 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -92,6 +92,7 @@ static const struct option check_options[] = {
 
 	OPT_GROUP("Options:"),
 	OPT_BOOLEAN(0,		 "backtrace", &opts.backtrace, "unwind on error"),
+	OPT_BOOLEAN(0,		 "backup", &opts.backup, "create backup (.orig) file on warning/error"),
 	OPT_STRING(0,		 "debug-checksum", &opts.debug_checksum,  "funcs", "enable checksum debug output"),
 	OPT_BOOLEAN(0,		 "dry-run", &opts.dryrun, "don't write modifications"),
 	OPT_BOOLEAN(0,		 "link", &opts.link, "object is a linked object"),
@@ -260,12 +261,9 @@ static void save_argv(int argc, const char **argv)
 	};
 }
 
-void print_args(void)
+int make_backup(void)
 {
-	char *backup = NULL;
-
-	if (opts.output || opts.dryrun)
-		goto print;
+	char *backup;
 
 	/*
 	 * Make a backup before kbuild deletes the file so the error
@@ -274,33 +272,32 @@ void print_args(void)
 	backup = malloc(strlen(objname) + strlen(ORIG_SUFFIX) + 1);
 	if (!backup) {
 		ERROR_GLIBC("malloc");
-		goto print;
+		return 1;
 	}
 
 	strcpy(backup, objname);
 	strcat(backup, ORIG_SUFFIX);
-	if (copy_file(objname, backup)) {
-		backup = NULL;
-		goto print;
-	}
+	if (copy_file(objname, backup))
+		return 1;
 
-print:
 	/*
-	 * Print the cmdline args to make it easier to recreate.  If '--output'
-	 * wasn't used, add it to the printed args with the backup as input.
+	 * Print the cmdline args to make it easier to recreate.
 	 */
+
 	fprintf(stderr, "%s", orig_argv[0]);
 
 	for (int i = 1; i < orig_argc; i++) {
 		char *arg = orig_argv[i];
 
-		if (backup && !strcmp(arg, objname))
+		/* Modify the printed args to use the backup */
+		if (!opts.output && !strcmp(arg, objname))
 			fprintf(stderr, " %s -o %s", backup, objname);
 		else
 			fprintf(stderr, " %s", arg);
 	}
 
 	fprintf(stderr, "\n");
+	return 0;
 }
 
 int objtool_run(int argc, const char **argv)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 9848ca612683..1eb6489ae459 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -187,20 +187,6 @@ static bool is_sibling_call(struct instruction *insn)
 	return (is_static_jump(insn) && insn_call_dest(insn));
 }
 
-/*
- * Checks if a string ends with another.
- */
-static bool str_ends_with(const char *s, const char *sub)
-{
-	const int slen = strlen(s);
-	const int sublen = strlen(sub);
-
-	if (sublen > slen)
-		return 0;
-
-	return !memcmp(s + slen - sublen, sub, sublen);
-}
-
 /*
  * Checks if a function is a Rust "noreturn" one.
  */
@@ -431,7 +417,6 @@ static int decode_instructions(struct objtool_file *file)
 	struct symbol *func;
 	unsigned long offset;
 	struct instruction *insn;
-	int ret;
 
 	for_each_sec(file->elf, sec) {
 		struct instruction *insns = NULL;
@@ -480,11 +465,8 @@ static int decode_instructions(struct objtool_file *file)
 			insn->offset = offset;
 			insn->prev_len = prev_len;
 
-			ret = arch_decode_instruction(file, sec, offset,
-						      sec_size(sec) - offset,
-						      insn);
-			if (ret)
-				return ret;
+			if (arch_decode_instruction(file, sec, offset, sec_size(sec) - offset, insn))
+				return -1;
 
 			prev_len = insn->len;
 
@@ -600,7 +582,7 @@ static int init_pv_ops(struct objtool_file *file)
 	};
 	const char *pv_ops;
 	struct symbol *sym;
-	int idx, nr, ret;
+	int idx, nr;
 
 	if (!opts.noinstr)
 		return 0;
@@ -622,9 +604,8 @@ static int init_pv_ops(struct objtool_file *file)
 		INIT_LIST_HEAD(&file->pv_ops[idx].targets);
 
 	for (idx = 0; (pv_ops = pv_ops_tables[idx]); idx++) {
-		ret = add_pv_ops(file, pv_ops);
-		if (ret)
-			return ret;
+		if (add_pv_ops(file, pv_ops))
+			return -1;
 	}
 
 	return 0;
@@ -660,7 +641,7 @@ static int create_static_call_sections(struct objtool_file *file)
 		 * site entries to take advantage of vmlinux static call
 		 * privileges.
 		 */
-		if (!!opts.dryrun || !file->klp)
+		if (!file->klp)
 			WARN("file already has .static_call_sites section, skipping");
 
 		return 0;
@@ -678,8 +659,15 @@ static int create_static_call_sections(struct objtool_file *file)
 	if (!sec)
 		return -1;
 
-	/* Allow modules to modify the low bits of static_call_site::key */
-	sec->sh.sh_flags |= SHF_WRITE;
+	/*
+	 * Set SHF_MERGE to prevent tooling from stripping entsize.
+	 *
+	 * SHF_WRITE would also get set here to allow modules to modify the low
+	 * bits of static_call_site::key, but the LLVM linker doesn't allow
+	 * SHF_MERGE+SHF_WRITE for whatever reason.  That gets fixed up by the
+	 * makefiles with CONFIG_NEED_MODULE_PERMISSIONS_FIX.
+	 */
+	sec->sh.sh_flags |= SHF_MERGE;
 
 	idx = 0;
 	list_for_each_entry(insn, &file->static_call_list, call_node) {
@@ -744,9 +732,7 @@ static int create_retpoline_sites_sections(struct objtool_file *file)
 
 	sec = find_section_by_name(file->elf, ".retpoline_sites");
 	if (sec) {
-		if (!opts.dryrun)
-			WARN("file already has .retpoline_sites, skipping");
-
+		WARN("file already has .retpoline_sites, skipping");
 		return 0;
 	}
 
@@ -784,9 +770,7 @@ static int create_return_sites_sections(struct objtool_file *file)
 
 	sec = find_section_by_name(file->elf, ".return_sites");
 	if (sec) {
-		if (!opts.dryrun)
-			WARN("file already has .return_sites, skipping");
-
+		WARN("file already has .return_sites, skipping");
 		return 0;
 	}
 
@@ -824,9 +808,7 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file)
 
 	sec = find_section_by_name(file->elf, ".ibt_endbr_seal");
 	if (sec) {
-		if (!opts.dryrun)
-			WARN("file already has .ibt_endbr_seal, skipping");
-
+		WARN("file already has .ibt_endbr_seal, skipping");
 		return 0;
 	}
 
@@ -883,9 +865,7 @@ static int create_cfi_sections(struct objtool_file *file)
 
 	sec = find_section_by_name(file->elf, ".cfi_sites");
 	if (sec) {
-		if (!opts.dryrun)
-			WARN("file already has .cfi_sites section, skipping");
-
+		WARN("file already has .cfi_sites section, skipping");
 		return 0;
 	}
 
@@ -937,7 +917,7 @@ static int create_mcount_loc_sections(struct objtool_file *file)
 		 * Livepatch modules have already extracted their __mcount_loc
 		 * entries to cover the !CONFIG_FTRACE_MCOUNT_USE_OBJTOOL case.
 		 */
-		if (!opts.dryrun && !file->klp)
+		if (!file->klp)
 			WARN("file already has __mcount_loc section, skipping");
 
 		return 0;
@@ -983,9 +963,7 @@ static int create_direct_call_sections(struct objtool_file *file)
 
 	sec = find_section_by_name(file->elf, ".call_sites");
 	if (sec) {
-		if (!opts.dryrun)
-			WARN("file already has .call_sites section, skipping");
-
+		WARN("file already has .call_sites section, skipping");
 		return 0;
 	}
 
@@ -1548,7 +1526,6 @@ static int add_jump_destinations(struct objtool_file *file)
 {
 	struct instruction *insn;
 	struct reloc *reloc;
-	int ret;
 
 	for_each_insn(file, insn) {
 		struct symbol *func = insn_func(insn);
@@ -1556,7 +1533,6 @@ static int add_jump_destinations(struct objtool_file *file)
 		struct section *dest_sec;
 		struct symbol *dest_sym;
 		unsigned long dest_off;
-		bool dest_undef = false;
 
 		if (!is_static_jump(insn))
 			continue;
@@ -1573,78 +1549,85 @@ static int add_jump_destinations(struct objtool_file *file)
 		if (!reloc) {
 			dest_sec = insn->sec;
 			dest_off = arch_jump_destination(insn);
-		} else if (is_undef_sym(reloc->sym)) {
-			dest_sym = reloc->sym;
-			dest_undef = true;
+			dest_sym = dest_sec->sym;
 		} else {
-			dest_sec = reloc->sym->sec;
-			dest_off = reloc->sym->sym.st_value +
-				   arch_insn_adjusted_addend(insn, reloc);
-		}
-
-		if (!dest_undef) {
-			dest_insn = find_insn(file, dest_sec, dest_off);
-			if (!dest_insn) {
-				struct symbol *sym = find_symbol_by_offset(dest_sec, dest_off);
-
-				/*
-				 * retbleed_untrain_ret() jumps to
-				 * __x86_return_thunk(), but objtool can't find
-				 * the thunk's starting RET instruction,
-				 * because the RET is also in the middle of
-				 * another instruction.  Objtool only knows
-				 * about the outer instruction.
-				 */
-				if (sym && sym->embedded_insn) {
-					add_return_call(file, insn, false);
+			dest_sym = reloc->sym;
+			if (is_undef_sym(dest_sym)) {
+				if (dest_sym->retpoline_thunk) {
+					if (add_retpoline_call(file, insn))
+						return -1;
 					continue;
 				}
 
-				/*
-				 * GCOV/KCOV dead code can jump to the end of
-				 * the function/section.
-				 */
-				if (file->ignore_unreachables && func &&
-				    dest_sec == insn->sec &&
-				    dest_off == func->offset + func->len)
+				if (dest_sym->return_thunk) {
+					add_return_call(file, insn, true);
 					continue;
+				}
 
-				ERROR_INSN(insn, "can't find jump dest instruction at %s+0x%lx",
-					  dest_sec->name, dest_off);
-				return -1;
+				/* External symbol */
+				if (func) {
+					/* External sibling call */
+					if (add_call_dest(file, insn, dest_sym, true))
+						return -1;
+					continue;
+				}
+
+				/* Non-func asm code jumping to external symbol */
+				continue;
 			}
 
+			dest_sec = dest_sym->sec;
+			dest_off = dest_sym->offset + arch_insn_adjusted_addend(insn, reloc);
+		}
+
+		dest_insn = find_insn(file, dest_sec, dest_off);
+		if (!dest_insn) {
+			struct symbol *sym = find_symbol_by_offset(dest_sec, dest_off);
+
+			/*
+			 * retbleed_untrain_ret() jumps to
+			 * __x86_return_thunk(), but objtool can't find
+			 * the thunk's starting RET instruction,
+			 * because the RET is also in the middle of
+			 * another instruction.  Objtool only knows
+			 * about the outer instruction.
+			 */
+			if (sym && sym->embedded_insn) {
+				add_return_call(file, insn, false);
+				continue;
+			}
+
+			/*
+			 * GCOV/KCOV dead code can jump to the end of
+			 * the function/section.
+			 */
+			if (file->ignore_unreachables && func &&
+			    dest_sec == insn->sec &&
+			    dest_off == func->offset + func->len)
+				continue;
+
+			ERROR_INSN(insn, "can't find jump dest instruction at %s",
+				   offstr(dest_sec, dest_off));
+			return -1;
+		}
+
+		if (!dest_sym || is_sec_sym(dest_sym)) {
 			dest_sym = dest_insn->sym;
 			if (!dest_sym)
 				goto set_jump_dest;
 		}
 
-		if (dest_sym->retpoline_thunk) {
-			ret = add_retpoline_call(file, insn);
-			if (ret)
-				return ret;
+		if (dest_sym->retpoline_thunk && dest_insn->offset == dest_sym->offset) {
+			if (add_retpoline_call(file, insn))
+				return -1;
 			continue;
 		}
 
-		if (dest_sym->return_thunk) {
+		if (dest_sym->return_thunk && dest_insn->offset == dest_sym->offset) {
 			add_return_call(file, insn, true);
 			continue;
 		}
 
-		if (dest_undef) {
-			/* External symbol */
-			if (func) {
-				/* External sibling call */
-				ret = add_call_dest(file, insn, dest_sym, true);
-				if (ret)
-					return ret;
-				continue;
-			}
-
-			/* Non-func asm code jumping to external symbol */
-			continue;
-		}
-
 		if (!insn->sym || insn->sym == dest_insn->sym)
 			goto set_jump_dest;
 
@@ -1675,9 +1658,8 @@ static int add_jump_destinations(struct objtool_file *file)
 
 		if (is_first_func_insn(file, dest_insn)) {
 			/* Internal sibling call */
-			ret = add_call_dest(file, insn, dest_sym, true);
-			if (ret)
-				return ret;
+			if (add_call_dest(file, insn, dest_sym, true))
+				return -1;
 			continue;
 		}
 
@@ -1708,7 +1690,6 @@ static int add_call_destinations(struct objtool_file *file)
 	unsigned long dest_off;
 	struct symbol *dest;
 	struct reloc *reloc;
-	int ret;
 
 	for_each_insn(file, insn) {
 		struct symbol *func = insn_func(insn);
@@ -1720,9 +1701,8 @@ static int add_call_destinations(struct objtool_file *file)
 			dest_off = arch_jump_destination(insn);
 			dest = find_call_destination(insn->sec, dest_off);
 
-			ret = add_call_dest(file, insn, dest, false);
-			if (ret)
-				return ret;
+			if (add_call_dest(file, insn, dest, false))
+				return -1;
 
 			if (func && func->ignore)
 				continue;
@@ -1746,19 +1726,16 @@ static int add_call_destinations(struct objtool_file *file)
 				return -1;
 			}
 
-			ret = add_call_dest(file, insn, dest, false);
-			if (ret)
-				return ret;
+			if (add_call_dest(file, insn, dest, false))
+				return -1;
 
 		} else if (reloc->sym->retpoline_thunk) {
-			ret = add_retpoline_call(file, insn);
-			if (ret)
-				return ret;
+			if (add_retpoline_call(file, insn))
+				return -1;
 
 		} else {
-			ret = add_call_dest(file, insn, reloc->sym, false);
-			if (ret)
-				return ret;
+			if (add_call_dest(file, insn, reloc->sym, false))
+				return -1;
 		}
 	}
 
@@ -1976,7 +1953,6 @@ static int add_special_section_alts(struct objtool_file *file)
 	struct instruction *orig_insn, *new_insn;
 	struct special_alt *special_alt, *tmp;
 	struct alternative *alt;
-	int ret;
 
 	if (special_get_alts(file->elf, &special_alts))
 		return -1;
@@ -2008,16 +1984,12 @@ static int add_special_section_alts(struct objtool_file *file)
 				continue;
 			}
 
-			ret = handle_group_alt(file, special_alt, orig_insn,
-					       &new_insn);
-			if (ret)
-				return ret;
+			if (handle_group_alt(file, special_alt, orig_insn, &new_insn))
+				return -1;
 
 		} else if (special_alt->jump_or_nop) {
-			ret = handle_jump_alt(file, special_alt, orig_insn,
-					      &new_insn);
-			if (ret)
-				return ret;
+			if (handle_jump_alt(file, special_alt, orig_insn, &new_insn))
+				return -1;
 		}
 
 		alt = calloc(1, sizeof(*alt));
@@ -2205,15 +2177,13 @@ static int add_func_jump_tables(struct objtool_file *file,
 				  struct symbol *func)
 {
 	struct instruction *insn;
-	int ret;
 
 	func_for_each_insn(file, func, insn) {
 		if (!insn_jump_table(insn))
 			continue;
 
-		ret = add_jump_table(file, insn);
-		if (ret)
-			return ret;
+		if (add_jump_table(file, insn))
+			return -1;
 	}
 
 	return 0;
@@ -2227,7 +2197,6 @@ static int add_func_jump_tables(struct objtool_file *file,
 static int add_jump_table_alts(struct objtool_file *file)
 {
 	struct symbol *func;
-	int ret;
 
 	if (!file->rodata)
 		return 0;
@@ -2237,9 +2206,8 @@ static int add_jump_table_alts(struct objtool_file *file)
 			continue;
 
 		mark_func_jump_tables(file, func);
-		ret = add_func_jump_tables(file, func);
-		if (ret)
-			return ret;
+		if (add_func_jump_tables(file, func))
+			return -1;
 	}
 
 	return 0;
@@ -2356,7 +2324,7 @@ static int read_annotate(struct objtool_file *file,
 	struct instruction *insn;
 	struct reloc *reloc;
 	uint64_t offset;
-	int type, ret;
+	int type;
 
 	sec = find_section_by_name(file->elf, ".discard.annotate_insn");
 	if (!sec)
@@ -2390,9 +2358,8 @@ static int read_annotate(struct objtool_file *file,
 			return -1;
 		}
 
-		ret = func(file, type, insn);
-		if (ret < 0)
-			return ret;
+		if (func(file, type, insn))
+			return -1;
 	}
 
 	return 0;
@@ -2636,70 +2603,57 @@ static bool validate_branch_enabled(void)
 
 static int decode_sections(struct objtool_file *file)
 {
-	int ret;
-
 	file->klp = is_livepatch_module(file);
 
 	mark_rodata(file);
 
-	ret = init_pv_ops(file);
-	if (ret)
-		return ret;
+	if (init_pv_ops(file))
+		return -1;
 
 	/*
 	 * Must be before add_{jump_call}_destination.
 	 */
-	ret = classify_symbols(file);
-	if (ret)
-		return ret;
+	if (classify_symbols(file))
+		return -1;
 
-	ret = decode_instructions(file);
-	if (ret)
-		return ret;
+	if (decode_instructions(file))
+		return -1;
 
-	ret = add_ignores(file);
-	if (ret)
-		return ret;
+	if (add_ignores(file))
+		return -1;
 
 	add_uaccess_safe(file);
 
-	ret = read_annotate(file, __annotate_early);
-	if (ret)
-		return ret;
+	if (read_annotate(file, __annotate_early))
+		return -1;
 
 	/*
 	 * Must be before add_jump_destinations(), which depends on 'func'
 	 * being set for alternatives, to enable proper sibling call detection.
 	 */
 	if (validate_branch_enabled() || opts.noinstr) {
-		ret = add_special_section_alts(file);
-		if (ret)
-			return ret;
+		if (add_special_section_alts(file))
+			return -1;
 	}
 
-	ret = add_jump_destinations(file);
-	if (ret)
-		return ret;
+	if (add_jump_destinations(file))
+		return -1;
 
 	/*
 	 * Must be before add_call_destination(); it changes INSN_CALL to
 	 * INSN_JUMP.
 	 */
-	ret = read_annotate(file, __annotate_ifc);
-	if (ret)
-		return ret;
+	if (read_annotate(file, __annotate_ifc))
+		return -1;
 
-	ret = add_call_destinations(file);
-	if (ret)
-		return ret;
+	if (add_call_destinations(file))
+		return -1;
 
-	ret = add_jump_table_alts(file);
-	if (ret)
-		return ret;
+	if (add_jump_table_alts(file))
+		return -1;
 
-	ret = read_unwind_hints(file);
-	if (ret)
-		return ret;
+	if (read_unwind_hints(file))
+		return -1;
 
 	/* Must be after add_jump_destinations() */
 	mark_holes(file);
@@ -2708,9 +2662,8 @@ static int decode_sections(struct objtool_file *file)
 	 * Must be after add_call_destinations() such that it can override
 	 * dead_end_function() marks.
 	 */
-	ret = read_annotate(file, __annotate_late);
-	if (ret)
-		return ret;
+	if (read_annotate(file, __annotate_late))
+		return -1;
 
 	return 0;
 }
@@ -3464,7 +3417,7 @@ static bool pv_call_dest(struct objtool_file *file, struct instruction *insn)
 	if (!reloc || strcmp(reloc->sym->name, "pv_ops"))
 		return false;
 
-	idx = (arch_insn_adjusted_addend(insn, reloc) / sizeof(void *));
+	idx = arch_insn_adjusted_addend(insn, reloc) / sizeof(void *);
 
 	if (file->pv_ops[idx].clean)
 		return true;
@@ -3692,45 +3645,44 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
 				 struct instruction *insn)
 {
 	struct reloc *reloc = insn_reloc(file, insn);
-	struct symbol *dest = insn_call_dest(insn);
+	unsigned long offset;
+	struct symbol *sym;
 
-	if (dest && !reloc) {
-		checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, 1);
-		checksum_update(func, insn, dest->name, strlen(dest->name));
-	} else if (!insn->fake) {
-		checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
+	if (insn->fake)
+		return;
+
+	checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
+
+	if (!reloc) {
+		struct symbol *call_dest = insn_call_dest(insn);
+
+		if (call_dest)
+			checksum_update(func, insn, call_dest->demangled_name,
+					strlen(call_dest->demangled_name));
+		return;
 	}
 
-	if (reloc) {
-		struct symbol *sym = reloc->sym;
+	sym = reloc->sym;
+	offset = arch_insn_adjusted_addend(insn, reloc);
 
-		if (sym->sec && is_string_sec(sym->sec)) {
-			s64 addend;
-			char *str;
+	if (is_string_sec(sym->sec)) {
+		char *str;
 
-			addend = arch_insn_adjusted_addend(insn, reloc);
-
-			str = sym->sec->data->d_buf + sym->offset + addend;
-
-			checksum_update(func, insn, str, strlen(str));
-
-		} else {
-			u64 offset = arch_insn_adjusted_addend(insn, reloc);
-
-			if (is_sec_sym(sym)) {
-				sym = find_symbol_containing(reloc->sym->sec, offset);
-				if (!sym)
-					return;
-
-				offset -= sym->offset;
-			}
-
-			checksum_update(func, insn, sym->demangled_name,
-					    strlen(sym->demangled_name));
-
-			checksum_update(func, insn, &offset, sizeof(offset));
-		}
+		str = sym->sec->data->d_buf + sym->offset + offset;
+		checksum_update(func, insn, str, strlen(str));
+		return;
 	}
+
+	if (is_sec_sym(sym)) {
+		sym = find_symbol_containing(reloc->sym->sec, offset);
+		if (!sym)
+			return;
+
+		offset -= sym->offset;
+	}
+
+	checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name));
+	checksum_update(func, insn, &offset, sizeof(offset));
 }
 
 /*
@@ -4992,9 +4944,11 @@ int check(struct objtool_file *file)
 	if (opts.verbose) {
 		if (opts.werror && warnings)
 			WARN("%d warning(s) upgraded to errors", warnings);
-		print_args();
 		disas_warned_funcs(file);
 	}
 
+	if (opts.backup && make_backup())
+		return 1;
+
 	return ret;
 }
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
index ae1c852ff8d8..f9eed5d50de5 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -492,8 +492,8 @@ static int elf_add_symbol(struct elf *elf, struct symbol *sym)
 	elf_hash_add(symbol, &sym->hash, sym->idx);
 	elf_hash_add(symbol_name, &sym->name_hash, str_hash(sym->name));
 
-	if (is_func_sym(sym) && sym->len == 16 &&
-	    (strstarts(sym->name, "__pfx") || strstarts(sym->name, "__cfi_")))
+	if (is_func_sym(sym) &&
+	    (strstarts(sym->name, "__pfx_") || strstarts(sym->name, "__cfi_")))
 		sym->prefix = 1;
 
 	if (strstarts(sym->name, ".klp.sym"))
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index e8eb3c54c373..e60438577000 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -30,6 +30,7 @@ struct opts {
 
 	/* options: */
 	bool backtrace;
+	bool backup;
 	const char *debug_checksum;
 	bool dryrun;
 	bool link;
@@ -49,7 +50,7 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[]);
 
 int objtool_run(int argc, const char **argv);
 
-void print_args(void);
+int make_backup(void);
 
 int cmd_klp(int argc, const char **argv);
 
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index adfe508f96f5..1212f81f40e0 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -17,7 +17,7 @@
 #include <objtool/checksum_types.h>
 #include <arch/elf.h>
 
-#define SEC_NAME_LEN		512
+#define SEC_NAME_LEN		1024
 #define SYM_NAME_LEN		512
 
 #ifdef LIBELF_USE_DEPRECATED
diff --git a/tools/objtool/include/objtool/util.h b/tools/objtool/include/objtool/util.h
new file mode 100644
index 000000000000..a0180b312f73
--- /dev/null
+++ b/tools/objtool/include/objtool/util.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#include <objtool/warn.h>
+
+#define snprintf_check(str, size, format, args...)			\
+({									\
+	int __ret = snprintf(str, size, format, args);			\
+	if (__ret < 0)							\
+		ERROR_GLIBC("snprintf");				\
+	else if (__ret >= size)						\
+		ERROR("snprintf() failed for '" format "'", args);	\
+	else								\
+		__ret = 0;						\
+	__ret;								\
+})
+
+#endif /* _UTIL_H */
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index 144525e74da3..15b554b53da6 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -11,6 +11,7 @@
 #include <objtool/warn.h>
 #include <objtool/arch.h>
 #include <objtool/klp.h>
+#include <objtool/util.h>
 #include <arch/special.h>
 
 #include <linux/livepatch_external.h>
@@ -80,42 +81,11 @@ static char *escape_str(const char *orig)
 	return new;
 }
 
-/*
- * Do a sanity check to make sure the changed object was built with
- * -ffunction-sections and -fdata-sections.
- */
-static int validate_ffunction_fdata_sections(struct elf *elf)
-{
-	struct symbol *sym;
-	bool found_text = false, found_data = false;
-
-	for_each_sym(elf, sym) {
-		char sec_name[SEC_NAME_LEN];
-
-		if (!found_text && is_func_sym(sym)) {
-			snprintf(sec_name, SEC_NAME_LEN, ".text.%s", sym->name);
-			if (!strcmp(sym->sec->name, sec_name))
-				found_text = true;
-		}
-
-		if (!found_data && is_object_sym(sym)) {
-			snprintf(sec_name, SEC_NAME_LEN, ".data.%s", sym->name);
-			if (!strcmp(sym->sec->name, sec_name))
-				found_data = true;
-		}
-
-		if (found_text && found_data)
-			return 0;
-	}
-
-	ERROR("changed object '%s' not built with -ffunction-sections and -fdata-sections", elf->name);
-	return -1;
-}
-
 static int read_exports(void)
 {
 	const char *symvers = "Module.symvers";
 	char line[1024], *path = NULL;
+	unsigned int line_num = 1;
 	FILE *file;
 
 	file = fopen(symvers, "r");
@@ -134,12 +104,12 @@ static int read_exports(void)
 	}
 
 	while (fgets(line, 1024, file)) {
-		char *sym, *mod, *exp;
+		char *sym, *mod, *type;
 		struct export *export;
 
 		sym = strchr(line, '\t');
 		if (!sym) {
-			ERROR("malformed Module.symvers");
+			ERROR("malformed Module.symvers (sym) at line %d", line_num);
 			return -1;
 		}
 
@@ -147,22 +117,22 @@ static int read_exports(void)
 
 		mod = strchr(sym, '\t');
 		if (!mod) {
-			ERROR("malformed Module.symvers");
+			ERROR("malformed Module.symvers (mod) at line %d", line_num);
 			return -1;
 		}
 
 		*mod++ = '\0';
 
-		exp = strchr(mod, '\t');
-		if (!exp) {
-			ERROR("malformed Module.symvers");
+		type = strchr(mod, '\t');
+		if (!type) {
+			ERROR("malformed Module.symvers (type) at line %d", line_num);
 			return -1;
 		}
 
-		*exp++ = '\0';
+		*type++ = '\0';
 
 		if (*sym == '\0' || *mod == '\0') {
-			ERROR("malformed Module.symvers");
+			ERROR("malformed Module.symvers at line %d", line_num);
 			return -1;
 		}
 
@@ -177,6 +147,7 @@ static int read_exports(void)
 			ERROR_GLIBC("strdup");
 			return -1;
 		}
+
 		export->sym = strdup(sym);
 		if (!export->sym) {
 			ERROR_GLIBC("strdup");
@@ -244,18 +215,20 @@ static struct symbol *first_file_symbol(struct elf *elf)
 {
 	struct symbol *sym;
 
-	for_each_sym(elf, sym)
+	for_each_sym(elf, sym) {
 		if (is_file_sym(sym))
 			return sym;
+	}
 
 	return NULL;
 }
 
 static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym)
 {
-	for_each_sym_continue(elf, sym)
+	for_each_sym_continue(elf, sym) {
 		if (is_file_sym(sym))
 			return sym;
+	}
 
 	return NULL;
 }
@@ -267,11 +240,14 @@ static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym)
 static bool is_uncorrelated_static_local(struct symbol *sym)
 {
 	static const char * const vars[] = {
-		"__key.",
-		"__warned.",
 		"__already_done.",
 		"__func__.",
+		"__key.",
+		"__warned.",
+		"_entry.",
+		"_entry_ptr.",
 		"_rs.",
+		"descriptor.",
 		"CSWTCH.",
 	};
 
@@ -324,14 +300,19 @@ static bool is_special_section(struct section *sec)
 		SYM_CHECKSUM_SEC,
 	};
 
-	for (int i = 0; i < ARRAY_SIZE(specials); i++)
+	if (is_text_sec(sec))
+		return false;
+
+	for (int i = 0; i < ARRAY_SIZE(specials); i++) {
 		if (!strcmp(sec->name, specials[i]))
 			return true;
+	}
 
-	/* Most .discard sections are special */
-	for (int i = 0; i < ARRAY_SIZE(non_special_discards); i++)
+	/* Most .discard data sections are special */
+	for (int i = 0; i < ARRAY_SIZE(non_special_discards); i++) {
 		if (!strcmp(sec->name, non_special_discards[i]))
 			return false;
+	}
 
 	return strstarts(sec->name, ".discard.");
 }
@@ -347,9 +328,10 @@ static bool is_special_section_aux(struct section *sec)
 		".altinstr_aux",
 	};
 
-	for (int i = 0; i < ARRAY_SIZE(specials_aux); i++)
+	for (int i = 0; i < ARRAY_SIZE(specials_aux); i++) {
 		if (!strcmp(sec->name, specials_aux[i]))
 			return true;
+	}
 
 	return false;
 }
@@ -460,6 +442,7 @@ static int correlate_symbols(struct elfs *e)
 /* "sympos" is used by livepatch to disambiguate duplicate symbol names */
 static unsigned long find_sympos(struct elf *elf, struct symbol *sym)
 {
+	bool vmlinux = str_ends_with(objname, "vmlinux.o");
 	unsigned long sympos = 0, nr_matches = 0;
 	bool has_dup = false;
 	struct symbol *s;
@@ -467,13 +450,43 @@ static unsigned long find_sympos(struct elf *elf, struct symbol *sym)
 	if (sym->bind != STB_LOCAL)
 		return 0;
 
-	for_each_sym(elf, s) {
-		if (!strcmp(s->name, sym->name)) {
-			nr_matches++;
-			if (s == sym)
-				sympos = nr_matches;
-			else
-				has_dup = true;
+	if (vmlinux && sym->type == STT_FUNC) {
+		/*
+		 * HACK: Unfortunately, symbol ordering can differ between
+		 * vmlinux.o and vmlinux due to the linker script emitting
+		 * .text.unlikely* before .text*.  Count .text.unlikely* first.
+		 *
+		 * TODO: Disambiguate symbols more reliably (checksums?)
+		 */
+		for_each_sym(elf, s) {
+			if (strstarts(s->sec->name, ".text.unlikely") &&
+			    !strcmp(s->name, sym->name)) {
+				nr_matches++;
+				if (s == sym)
+					sympos = nr_matches;
+				else
+					has_dup = true;
+			}
+		}
+		for_each_sym(elf, s) {
+			if (!strstarts(s->sec->name, ".text.unlikely") &&
+			    !strcmp(s->name, sym->name)) {
+				nr_matches++;
+				if (s == sym)
+					sympos = nr_matches;
+				else
+					has_dup = true;
+			}
+		}
+	} else {
+		for_each_sym(elf, s) {
+			if (!strcmp(s->name, sym->name)) {
+				nr_matches++;
+				if (s == sym)
+					sympos = nr_matches;
+				else
+					has_dup = true;
+			}
 		}
 	}
 
@@ -565,7 +578,7 @@ static const char *sym_type(struct symbol *sym)
 static const char *sym_bind(struct symbol *sym)
 {
 	switch (sym->bind) {
-	case STB_LOCAL :  return "LOCAL";
+	case STB_LOCAL:   return "LOCAL";
 	case STB_GLOBAL:  return "GLOBAL";
 	case STB_WEAK:    return "WEAK";
 	default:	  return "UNKNOWN";
@@ -727,7 +740,7 @@ static struct export *find_export(struct symbol *sym)
 
 static const char *__find_modname(struct elfs *e)
 {
-	struct section  *sec;
+	struct section *sec;
 	char *name;
 
 	sec = find_section_by_name(e->orig, ".modinfo");
@@ -795,7 +808,7 @@ static bool klp_reloc_needed(struct reloc *patched_reloc)
 
 	/*
 	 * If exported by a module, it has to be a klp reloc.  Thanks to the
-	 * clusterfoot that is late module patching, the patch module is
+	 * clusterfunk that is late module patching, the patch module is
 	 * allowed to be loaded before any modules it depends on.
 	 *
 	 * If exported by vmlinux, a normal reloc will do.
@@ -942,8 +955,9 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc,
 	}
 
 	/* symbol format: .klp.sym.modname.sym_name,sympos */
-	snprintf(sym_name, SYM_NAME_LEN, KLP_SYM_PREFIX "%s.%s,%ld",
-		 sym_modname, sym_orig_name, sympos);
+	if (snprintf_check(sym_name, SYM_NAME_LEN, KLP_SYM_PREFIX "%s.%s,%ld",
+		      sym_modname, sym_orig_name, sympos))
+		return -1;
 
 	klp_sym = find_symbol_by_name(e->out, sym_name);
 	if (!klp_sym) {
@@ -1155,11 +1169,102 @@ static bool should_keep_special_sym(struct elf *elf, struct symbol *sym)
 	return false;
 }
 
+/*
+ * Klp relocations aren't allowed for __jump_table and .static_call_sites if
+ * the referenced symbol lives in a kernel module, because such klp relocs may
+ * be applied after static branch/call init, resulting in code corruption.
+ *
+ * Validate a special section entry to avoid that.  Note that an inert
+ * tracepoint is harmless enough, in that case just skip the entry and print a
+ * warning.  Otherwise, return an error.
+ *
+ * This is only a temporary limitation which will be fixed when livepatch adds
+ * support for submodules: fully self-contained modules which are embedded in
+ * the top-level livepatch module's data and which can be loaded on demand when
+ * their corresponding to-be-patched module gets loaded.  Then klp relocs can
+ * be retired.
+ *
+ * Return:
+ *   -1: error: validation failed
+ *    1: warning: tracepoint skipped
+ *    0: success
+ */
+static int validate_special_section_klp_reloc(struct elfs *e, struct symbol *sym)
+{
+	bool static_branch = !strcmp(sym->sec->name, "__jump_table");
+	bool static_call   = !strcmp(sym->sec->name, ".static_call_sites");
+	struct symbol *code_sym = NULL;
+	unsigned long code_offset = 0;
+	struct reloc *reloc;
+	int ret = 0;
+
+	if (!static_branch && !static_call)
+		return 0;
+
+	sym_for_each_reloc(e->patched, sym, reloc) {
+		const char *sym_modname;
+		struct export *export;
+
+		/* Static branch/call keys are always STT_OBJECT */
+		if (reloc->sym->type != STT_OBJECT) {
+
+			/* Save code location which can be printed below */
+			if (reloc->sym->type == STT_FUNC && !code_sym) {
+				code_sym = reloc->sym;
+				code_offset = reloc_addend(reloc);
+			}
+
+			continue;
+		}
+
+		if (!klp_reloc_needed(reloc))
+			continue;
+
+		export = find_export(reloc->sym);
+		if (export) {
+			sym_modname = export->mod;
+		} else {
+			sym_modname = find_modname(e);
+			if (!sym_modname)
+				return -1;
+		}
+
+		/* vmlinux keys are ok */
+		if (!strcmp(sym_modname, "vmlinux"))
+			continue;
+
+		if (static_branch) {
+			if (strstarts(reloc->sym->name, "__tracepoint_")) {
+				WARN("%s: disabling unsupported tracepoint %s",
+				     code_sym->name, reloc->sym->name + 13);
+				ret = 1;
+				continue;
+			}
+
+			ERROR("%s+0x%lx: unsupported static branch key %s.  Use static_key_enabled() instead",
+			      code_sym->name, code_offset, reloc->sym->name);
+			return -1;
+		}
+
+		/* static call */
+		if (strstarts(reloc->sym->name, "__SCK__tp_func_")) {
+			ret = 1;
+			continue;
+		}
+
+		ERROR("%s()+0x%lx: unsupported static call key %s.  Use KLP_STATIC_CALL() instead",
+		      code_sym->name, code_offset, reloc->sym->name);
+		return -1;
+	}
+
+	return ret;
+}
+
 static int special_section_entry_size(struct section *sec)
 {
 	unsigned int reloc_size;
 
-	if (sec->sh.sh_entsize)
+	if ((sec->sh.sh_flags & SHF_MERGE) && sec->sh.sh_entsize)
 		return sec->sh.sh_entsize;
 
 	if (!sec->rsec)
@@ -1176,12 +1281,13 @@ static int special_section_entry_size(struct section *sec)
 static int create_fake_symbol(struct elf *elf, struct section *sec,
 			      unsigned long offset, size_t size)
 {
+	char name[SYM_NAME_LEN];
 	unsigned int type;
-	char name[256];
 	static int ctr;
 	char *c;
 
-	snprintf(name, 256, "__DISCARD_%s_%d", sec->name, ctr++);
+	if (snprintf_check(name, SYM_NAME_LEN, "__DISCARD_%s_%d", sec->name, ctr++))
+		return -1;
 
 	for (c = name; *c; c++)
 		if (*c == '.')
@@ -1206,7 +1312,15 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec)
 
 	entry_size = special_section_entry_size(patched_sec);
 	if (!entry_size) {
-		ERROR("%s: unknown entry size", patched_sec->name);
+		/*
+		 * Any special section more complex than a simple array of
+		 * pointers must have its entry size specified in sh_entsize
+		 * (and the SHF_MERGE flag set so the linker preserves it).
+		 *
+		 * Clang older than version 20 doesn't properly preserve
+		 * sh_entsize and will error out here.
+		 */
+		ERROR("%s: buggy linker and/or missing sh_entsize", patched_sec->name);
 		return -1;
 	}
 
@@ -1242,12 +1356,20 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec)
 	 * reference included functions.
 	 */
 	sec_for_each_sym(patched_sec, patched_sym) {
+		int ret;
+
 		if (!is_object_sym(patched_sym))
 			continue;
 
 		if (!should_keep_special_sym(e->patched, patched_sym))
 			continue;
 
+		ret = validate_special_section_klp_reloc(e, patched_sym);
+		if (ret < 0)
+			return -1;
+		if (ret > 0)
+			continue;
+
 		if (!clone_symbol(e, patched_sym, true))
 			return -1;
 	}
@@ -1388,7 +1510,9 @@ static int create_klp_sections(struct elfs *e)
 	 * friends, and add them to the klp object.
 	 */
 
-	snprintf(sym_name, SYM_NAME_LEN, KLP_PRE_PATCH_PREFIX "%s", modname);
+	if (snprintf_check(sym_name, SYM_NAME_LEN, KLP_PRE_PATCH_PREFIX "%s", modname))
+		return -1;
+
 	sym = find_symbol_by_name(e->out, sym_name);
 	if (sym) {
 		struct reloc *reloc;
@@ -1402,7 +1526,9 @@ static int create_klp_sections(struct elfs *e)
 			return -1;
 	}
 
-	snprintf(sym_name, SYM_NAME_LEN, KLP_POST_PATCH_PREFIX "%s", modname);
+	if (snprintf_check(sym_name, SYM_NAME_LEN, KLP_POST_PATCH_PREFIX "%s", modname))
+		return -1;
+
 	sym = find_symbol_by_name(e->out, sym_name);
 	if (sym) {
 		struct reloc *reloc;
@@ -1416,7 +1542,9 @@ static int create_klp_sections(struct elfs *e)
 			return -1;
 	}
 
-	snprintf(sym_name, SYM_NAME_LEN, KLP_PRE_UNPATCH_PREFIX "%s", modname);
+	if (snprintf_check(sym_name, SYM_NAME_LEN, KLP_PRE_UNPATCH_PREFIX "%s", modname))
+		return -1;
+
 	sym = find_symbol_by_name(e->out, sym_name);
 	if (sym) {
 		struct reloc *reloc;
@@ -1430,7 +1558,9 @@ static int create_klp_sections(struct elfs *e)
 			return -1;
 	}
 
-	snprintf(sym_name, SYM_NAME_LEN, KLP_POST_UNPATCH_PREFIX "%s", modname);
+	if (snprintf_check(sym_name, SYM_NAME_LEN, KLP_POST_UNPATCH_PREFIX "%s", modname))
+		return -1;
+
 	sym = find_symbol_by_name(e->out, sym_name);
 	if (sym) {
 		struct reloc *reloc;
@@ -1447,6 +1577,51 @@ static int create_klp_sections(struct elfs *e)
 	return 0;
 }
 
+/*
+ * Copy all .modinfo import_ns= tags to ensure all namespaced exported symbols
+ * can be accessed via normal relocs.
+ */
+static int copy_import_ns(struct elfs *e)
+{
+	struct section *patched_sec, *out_sec = NULL;
+	char *import_ns, *data_end;
+
+	patched_sec = find_section_by_name(e->patched, ".modinfo");
+	if (!patched_sec)
+		return 0;
+
+	import_ns = patched_sec->data->d_buf;
+	if (!import_ns)
+		return 0;
+
+	for (data_end = import_ns + sec_size(patched_sec);
+	     import_ns < data_end;
+	     import_ns += strlen(import_ns) + 1) {
+
+		import_ns = memmem(import_ns, data_end - import_ns, "import_ns=", 10);
+		if (!import_ns)
+			return 0;
+
+		if (!out_sec) {
+			out_sec = find_section_by_name(e->out, ".modinfo");
+			if (!out_sec) {
+				out_sec = elf_create_section(e->out, ".modinfo", 0,
+							     patched_sec->sh.sh_entsize,
+							     patched_sec->sh.sh_type,
+							     patched_sec->sh.sh_addralign,
+							     patched_sec->sh.sh_flags);
+				if (!out_sec)
+					return -1;
+			}
+		}
+
+		if (!elf_add_data(e->out, out_sec, import_ns, strlen(import_ns) + 1))
+			return -1;
+	}
+
+	return 0;
+}
+
 int cmd_klp_diff(int argc, const char **argv)
 {
 	struct elfs e = {0};
@@ -1479,10 +1654,6 @@ int cmd_klp_diff(int argc, const char **argv)
 	if (mark_changed_functions(&e))
 		return 0;
 
-	if (validate_ffunction_fdata_sections(e.orig) ||
-	    validate_ffunction_fdata_sections(e.patched))
-		return -1;
-
 	e.out = elf_create_file(&e.orig->ehdr, argv[2]);
 	if (!e.out)
 		return -1;
@@ -1496,9 +1667,11 @@ int cmd_klp_diff(int argc, const char **argv)
 	if (create_klp_sections(&e))
 		return -1;
 
+	if (copy_import_ns(&e))
+		return -1;
+
 	if  (elf_write(e.out))
 		return -1;
 
 	return elf_close(e.out);
 }
-
diff --git a/tools/objtool/klp-post-link.c b/tools/objtool/klp-post-link.c
index 05be6251e35f..c013e39957b1 100644
--- a/tools/objtool/klp-post-link.c
+++ b/tools/objtool/klp-post-link.c
@@ -16,6 +16,7 @@
 #include <objtool/objtool.h>
 #include <objtool/warn.h>
 #include <objtool/klp.h>
+#include <objtool/util.h>
 #include <linux/livepatch_external.h>
 
 static int fix_klp_relocs(struct elf *elf)
@@ -81,8 +82,10 @@ static int fix_klp_relocs(struct elf *elf)
 		 */
 
 		/* section format: .klp.rela.sec_objname.section_name */
-		snprintf(rsec_name, SEC_NAME_LEN, KLP_RELOC_SEC_PREFIX "%s.%s",
-			 sym_modname, sec->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) {
diff --git a/tools/objtool/special.c b/tools/objtool/special.c
index 9cfdd424e173..fc2cf8dba1c0 100644
--- a/tools/objtool/special.c
+++ b/tools/objtool/special.c
@@ -133,7 +133,7 @@ int special_get_alts(struct elf *elf, struct list_head *alts)
 	struct section *sec;
 	unsigned int nr_entries;
 	struct special_alt *alt;
-	int idx, ret;
+	int idx;
 
 	INIT_LIST_HEAD(alts);
 
@@ -157,11 +157,8 @@ int special_get_alts(struct elf *elf, struct list_head *alts)
 			}
 			memset(alt, 0, sizeof(*alt));
 
-			ret = get_alt_entry(elf, entry, sec, idx, alt);
-			if (ret > 0)
-				continue;
-			if (ret < 0)
-				return ret;
+			if (get_alt_entry(elf, entry, sec, idx, alt))
+				return -1;
 
 			list_add_tail(&alt->list, alts);
 		}

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ