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-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <9ceb13e03c3af0b4823ec53a97f2a2d82c0328b3.1725334260.git.jpoimboe@kernel.org>
Date: Mon,  2 Sep 2024 21:00:14 -0700
From: Josh Poimboeuf <jpoimboe@...nel.org>
To: live-patching@...r.kernel.org
Cc: linux-kernel@...r.kernel.org,
	x86@...nel.org,
	Miroslav Benes <mbenes@...e.cz>,
	Petr Mladek <pmladek@...e.com>,
	Joe Lawrence <joe.lawrence@...hat.com>,
	Jiri Kosina <jikos@...nel.org>,
	Peter Zijlstra <peterz@...radead.org>,
	Marcos Paulo de Souza <mpdesouza@...e.com>,
	Song Liu <song@...nel.org>
Subject: [RFC 31/31] objtool, livepatch: Livepatch module generation

Add a klp-build script which makes use of a new "objtool klp" subcommand
to generate livepatch modules using a source patch as input.

The concept is similar to kpatch-build which has been a successful
out-of-tree project for over a decade.  It takes a source .patch as an
input, builds kernels before and after, does a binary diff, and copies
any changed functions into a new object file which is then linked into a
livepatch module.

By making use of existing objtool functionalities, and taking from
lessons learned over the last decade of maintaining kpatch-build, the
overall design is much simpler.  In fact, it's a complete redesign and
has been written from scratch (no copied code).

Advantages over kpatch-build:

  - Runs on vmlinux.o, so it's compatible with late-linked features like
    IBT and LTO

  - Much simpler design: ~3k fewer LOC

  - Makes use of existing objtool CFG functionality to create checksums
    to trivially detect changed functions

  - Offset __LINE__ changes are no longer a problem thanks to the
    adjust-patch-lines script

  - In-tree means less cruft, easier maintenance, and a larger pool of
    potential maintainers

To use, run the following from the kernel source root:

  scripts/livepatch/klp-build /path/to/my.patch

If it succeeds, the patch module (livepatch.ko) will be created in the
current directory.

TODO:

  - specify module name on cmdline
  - handle edge cases like correlation of static locals
  - support other arches (currently x86-64 only)
  - support clang
  - performance optimization
  - automated testing

Signed-off-by: Josh Poimboeuf <jpoimboe@...nel.org>
---
 .gitignore                              |    3 +
 include/linux/livepatch.h               |   25 +-
 include/linux/livepatch_ext.h           |   83 ++
 include/linux/livepatch_patch.h         |   73 ++
 kernel/livepatch/core.c                 |    4 +-
 scripts/livepatch/adjust-patch-lines    |  181 ++++
 scripts/livepatch/klp-build             |  355 ++++++++
 scripts/livepatch/module.c              |  120 +++
 scripts/module.lds.S                    |    9 +-
 tools/include/linux/livepatch_ext.h     |   83 ++
 tools/objtool/Build                     |    4 +-
 tools/objtool/Makefile                  |   33 +-
 tools/objtool/arch/x86/decode.c         |   40 +
 tools/objtool/check.c                   |   29 +-
 tools/objtool/elf.c                     |   18 +-
 tools/objtool/include/objtool/arch.h    |    1 +
 tools/objtool/include/objtool/builtin.h |    1 +
 tools/objtool/include/objtool/elf.h     |   20 +-
 tools/objtool/include/objtool/klp.h     |   25 +
 tools/objtool/include/objtool/objtool.h |    2 +-
 tools/objtool/klp-diff.c                | 1112 +++++++++++++++++++++++
 tools/objtool/klp-link.c                |  122 +++
 tools/objtool/klp.c                     |   57 ++
 tools/objtool/objtool.c                 |    6 +
 tools/objtool/sync-check.sh             |    1 +
 tools/objtool/weak.c                    |    7 +
 26 files changed, 2361 insertions(+), 53 deletions(-)
 create mode 100644 include/linux/livepatch_ext.h
 create mode 100644 include/linux/livepatch_patch.h
 create mode 100755 scripts/livepatch/adjust-patch-lines
 create mode 100755 scripts/livepatch/klp-build
 create mode 100644 scripts/livepatch/module.c
 create mode 100644 tools/include/linux/livepatch_ext.h
 create mode 100644 tools/objtool/include/objtool/klp.h
 create mode 100644 tools/objtool/klp-diff.c
 create mode 100644 tools/objtool/klp-link.c
 create mode 100644 tools/objtool/klp.c

diff --git a/.gitignore b/.gitignore
index c59dc60ba62e..28bb70c9e808 100644
--- a/.gitignore
+++ b/.gitignore
@@ -171,3 +171,6 @@ sphinx_*/
 
 # Rust analyzer configuration
 /rust-project.json
+
+# Livepatch module build directory
+/klp-tmp
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index 51a258c24ff5..d54e4dfe320e 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -13,6 +13,7 @@
 #include <linux/ftrace.h>
 #include <linux/completion.h>
 #include <linux/list.h>
+#include <linux/livepatch_ext.h>
 #include <linux/livepatch_sched.h>
 
 #if IS_ENABLED(CONFIG_LIVEPATCH)
@@ -77,30 +78,6 @@ struct klp_func {
 	bool transition;
 };
 
-struct klp_object;
-
-/**
- * struct klp_callbacks - pre/post live-(un)patch callback structure
- * @pre_patch:		executed before code patching
- * @post_patch:		executed after code patching
- * @pre_unpatch:	executed before code unpatching
- * @post_unpatch:	executed after code unpatching
- * @post_unpatch_enabled:	flag indicating if post-unpatch callback
- * 				should run
- *
- * All callbacks are optional.  Only the pre-patch callback, if provided,
- * will be unconditionally executed.  If the parent klp_object fails to
- * patch for any reason, including a non-zero error status returned from
- * the pre-patch callback, no further callbacks will be executed.
- */
-struct klp_callbacks {
-	int (*pre_patch)(struct klp_object *obj);
-	void (*post_patch)(struct klp_object *obj);
-	void (*pre_unpatch)(struct klp_object *obj);
-	void (*post_unpatch)(struct klp_object *obj);
-	bool post_unpatch_enabled;
-};
-
 /**
  * struct klp_object - kernel object structure for live patching
  * @name:	module name (or NULL for vmlinux)
diff --git a/include/linux/livepatch_ext.h b/include/linux/livepatch_ext.h
new file mode 100644
index 000000000000..4b71e72952d5
--- /dev/null
+++ b/include/linux/livepatch_ext.h
@@ -0,0 +1,83 @@
+/* SPDX License-Identifier: GPL-2.0-or-later */
+/*
+ * External livepatch interfaces for patch creation tooling
+ *
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+
+#ifndef _LINUX_LIVEPATCH_EXT_H_
+#define _LINUX_LIVEPATCH_EXT_H_
+
+#include <linux/types.h>
+
+#define KLP_RELOC_SEC_PREFIX	".klp.rela."
+#define KLP_SYM_PREFIX		".klp.sym."
+
+#define KLP_CALLBACKS_SEC		".discard.klp_callbacks"
+
+#define __KLP_PRE_PATCH_PREFIX		__klp_pre_patch_callback_
+#define __KLP_POST_PATCH_PREFIX		__klp_post_patch_callback_
+#define __KLP_PRE_UNPATCH_PREFIX	__klp_pre_unpatch_callback_
+#define __KLP_POST_UNPATCH_PREFIX	__klp_post_unpatch_callback_
+
+#define KLP_PRE_PATCH_PREFIX		__stringify(__KLP_PRE_PATCH_PREFIX)
+#define KLP_POST_PATCH_PREFIX		__stringify(__KLP_POST_PATCH_PREFIX)
+#define KLP_PRE_UNPATCH_PREFIX		__stringify(__KLP_PRE_UNPATCH_PREFIX)
+#define KLP_POST_UNPATCH_PREFIX		__stringify(__KLP_POST_UNPATCH_PREFIX)
+
+struct klp_object;
+
+typedef int (*klp_pre_patch_t)(struct klp_object *obj);
+typedef void (*klp_post_patch_t)(struct klp_object *obj);
+typedef void (*klp_pre_unpatch_t)(struct klp_object *obj);
+typedef void (*klp_post_unpatch_t)(struct klp_object *obj);
+
+/**
+ * struct klp_callbacks - pre/post live-(un)patch callback structure
+ * @pre_patch:		executed before code patching
+ * @post_patch:		executed after code patching
+ * @pre_unpatch:	executed before code unpatching
+ * @post_unpatch:	executed after code unpatching
+ * @post_unpatch_enabled:	flag indicating if post-unpatch callback
+ *				should run
+ *
+ * All callbacks are optional.  Only the pre-patch callback, if provided,
+ * will be unconditionally executed.  If the parent klp_object fails to
+ * patch for any reason, including a non-zero error status returned from
+ * the pre-patch callback, no further callbacks will be executed.
+ */
+struct klp_callbacks {
+	klp_pre_patch_t		pre_patch;
+	klp_post_patch_t	post_patch;
+	klp_pre_unpatch_t	pre_unpatch;
+	klp_post_unpatch_t	post_unpatch;
+	bool post_unpatch_enabled;
+};
+
+/*
+ * 'struct klp_{func,object}_ext' are compact "external" representations of
+ * 'struct klp_{func,object}'.   They are used by objtool for livepatch
+ * generation.  The structs are then read by the livepatch module and converted
+ * to the real structs before calling klp_enable_patch().
+ *
+ * TODO make these the official API for klp_enable_patch().  That should
+ * simplify livepatch's interface as well as its data structure lifetime
+ * management.
+ *
+ * TODO possibly use struct_group_tagged() to declare these within the original
+ * structs.
+ */
+struct klp_func_ext {
+	const char *old_name;
+	void *new_func;
+	unsigned long sympos;
+};
+
+struct klp_object_ext {
+	const char *name;
+	struct klp_func_ext *funcs;
+	struct klp_callbacks callbacks;
+	unsigned int nr_funcs;
+};
+
+#endif /* _LINUX_LIVEPATCH_EXT_H_ */
diff --git a/include/linux/livepatch_patch.h b/include/linux/livepatch_patch.h
new file mode 100644
index 000000000000..6f3b930cdc5a
--- /dev/null
+++ b/include/linux/livepatch_patch.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Helper macros for livepatch source patches
+ *
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+#ifndef _LINUX_LIVEPATCH_PATCH_H_
+#define _LINUX_LIVEPATCH_PATCH_H_
+
+#include <linux/livepatch.h>
+
+#ifdef MODULE
+#define KLP_OBJNAME __KBUILD_MODNAME
+#else
+#define KLP_OBJNAME vmlinux
+#endif
+
+#define KLP_PRE_PATCH_CALLBACK(func)						\
+	klp_pre_patch_t __used __section(KLP_CALLBACKS_SEC)			\
+		__PASTE(__KLP_PRE_PATCH_PREFIX, KLP_OBJNAME) = func
+
+#define KLP_POST_PATCH_CALLBACK(func)						\
+	klp_post_patch_t __used __section(KLP_CALLBACKS_SEC)			\
+		__PASTE(__KLP_POST_PATCH_PREFIX, KLP_OBJNAME) = func
+
+#define KLP_PRE_UNPATCH_CALLBACK(func)						\
+	klp_pre_unpatch_t __used __section(KLP_CALLBACKS_SEC)			\
+		__PASTE(__KLP_PRE_UNPATCH_PREFIX, KLP_OBJNAME) = func
+
+#define KLP_POST_UNPATCH_CALLBACK(func)						\
+	klp_post_unpatch_t __used __section(KLP_CALLBACKS_SEC)			\
+		__PASTE(__KLP_POST_UNPATCH_PREFIX, KLP_OBJNAME) = func
+
+#define KLP_SYSCALL_METADATA(sname)					\
+	static struct syscall_metadata __used				\
+	  __section("__syscalls_metadata")				\
+	  *__p_syscall_meta_##sname = NULL;				\
+									\
+	static struct trace_event_call __used				\
+	  __section("_ftrace_events")					\
+	  *__event_enter_##sname = NULL
+
+#define KLP_SYSCALL_DEFINE1(name, ...) KLP_SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
+#define KLP_SYSCALL_DEFINE2(name, ...) KLP_SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
+#define KLP_SYSCALL_DEFINE3(name, ...) KLP_SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
+#define KLP_SYSCALL_DEFINE4(name, ...) KLP_SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
+#define KLP_SYSCALL_DEFINE5(name, ...) KLP_SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
+#define KLP_SYSCALL_DEFINE6(name, ...) KLP_SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
+
+#define KLP_SYSCALL_DEFINEx(x, sname, ...)				\
+	KLP_SYSCALL_METADATA(sname);					\
+	__KLP_SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
+
+#ifdef CONFIG_X86_64
+
+// TODO move this to arch/x86/include/asm/syscall_wrapper.h and share code
+#define __KLP_SYSCALL_DEFINEx(x, name, ...)			\
+	static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__));	\
+	static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
+	__X64_SYS_STUBx(x, name, __VA_ARGS__)				\
+	__IA32_SYS_STUBx(x, name, __VA_ARGS__)				\
+	static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))	\
+	{								\
+		long ret = __klp_do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
+		__MAP(x,__SC_TEST,__VA_ARGS__);				\
+		__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));	\
+		return ret;						\
+	}								\
+	static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+
+#endif
+
+#endif /* _LINUX_LIVEPATCH_PATCH_H_ */
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 76ffe29934d4..8ff917973254 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -226,7 +226,7 @@ static int klp_resolve_symbols(Elf_Shdr *sechdrs, const char *strtab,
 
 		/* Format: .klp.sym.sym_objname.sym_name,sympos */
 		cnt = sscanf(strtab + sym->st_name,
-			     ".klp.sym.%55[^.].%511[^,],%lu",
+			     KLP_SYM_PREFIX "%55[^.].%511[^,],%lu",
 			     sym_objname, sym_name, &sympos);
 		if (cnt != 3) {
 			pr_err("symbol %s has an incorrectly formatted name\n",
@@ -305,7 +305,7 @@ static int klp_write_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
 	 * See comment in klp_resolve_symbols() for an explanation
 	 * of the selected field width value.
 	 */
-	cnt = sscanf(shstrtab + sec->sh_name, ".klp.rela.%55[^.]",
+	cnt = sscanf(shstrtab + sec->sh_name, KLP_RELOC_SEC_PREFIX "%55[^.]",
 		     sec_objname);
 	if (cnt != 1) {
 		pr_err("section %s has an incorrectly formatted name\n",
diff --git a/scripts/livepatch/adjust-patch-lines b/scripts/livepatch/adjust-patch-lines
new file mode 100755
index 000000000000..b29592a57ed3
--- /dev/null
+++ b/scripts/livepatch/adjust-patch-lines
@@ -0,0 +1,181 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+#
+# Add #line statements to a patch so it doesn't affect __LINE__ usage
+
+SCRIPT="$(basename "$0")"
+
+set -o errexit
+set -o errtrace
+set -o pipefail
+
+warn() {
+	str="$1"
+	if [ -n "$str" ]; then
+		echo -e "$SCRIPT: error: $*" >&2
+	else
+		echo -e "$SCRIPT: error" >&2
+
+	fi
+}
+
+die() {
+	warn "$@"
+	if [ -n "$TMPFILE" ]; then
+		rm -f "$TMPFILE"
+	fi
+	exit 1
+}
+
+do_trap() {
+	die
+}
+
+trap do_trap ERR
+
+__usage() {
+	echo "Usage: $SCRIPT [options] patch_file [output_file]"
+	echo "Add #line statements to a patch so it doesn't affect __LINE__ usage"
+	echo "for the rest of the file.  This prevents false positive changes in the binary."
+	echo
+	echo "Options:"
+	echo "  -h, --help       Show this help message and exit"
+	echo
+	echo "Arguments:"
+	echo "  patch_file       Source .patch file"
+	echo "  output_file      Optional output file.  If not provided 'patch_file' will be"
+	echo "                   edited in place."
+}
+
+usage() {
+	__usage >&2
+}
+
+args=$(getopt -o "h" -l "help" -- "$@")
+eval set -- "$args"
+
+while true; do
+	case "$1" in
+		-h | --help)
+			usage
+			exit 0
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			usage
+			exit 1
+			;;
+	esac
+done
+
+if [ $# != 1 ] && [ $# != 2 ]; then
+	usage
+	die "unexpected # of args $#"
+fi
+
+patch="$1"
+
+if [ ! -e "$patch" ]; then
+	die "missing file: $patch"
+fi
+
+if [ $# = 2 ]; then
+	output="$2"
+else
+	output="$patch"
+fi
+
+TMPFILE="$(mktemp)"
+
+skip_file=false
+in_hunk=false
+needs_update=false
+while IFS= read -r line; do
+	if [[ "$line" =~ ^---\  ]]; then
+		filename="${line#--- */}"
+
+		if [[ ! "$filename" =~ \.[ch]$ ]]; then
+			skip_file=true
+			echo "$line" >> "$TMPFILE"
+			continue
+		fi
+
+		case "$filename" in
+			*vmlinux.lds.h)
+				skip_file=true
+				echo "$line" >> "$TMPFILE"
+				continue
+				;;
+		esac
+
+		skip_file=false
+		in_hunk=false
+		needs_update=false
+		echo "$line" >> "$TMPFILE"
+		continue
+	fi
+
+	if $skip_file; then
+		echo "$line" >> "$TMPFILE"
+		continue
+	fi
+
+	if [[ "$line" =~ ^@@ ]]; then
+
+		in_hunk=true
+		needs_update=false
+
+		cur="${line#*-}"
+		cur="${cur%%,*}"
+		((cur--))
+
+		last="${line#*,}"
+		last="${last%% *}"
+		last=$((cur + last))
+
+		echo "$line" >> "$TMPFILE"
+		continue
+	fi
+
+	if $in_hunk; then
+		if [[ "$line" =~ ^\+ ]]; then
+			needs_update=true
+			echo "$line" >> "$TMPFILE"
+			continue
+		fi
+
+		if [[ "$line" =~ ^- ]]; then
+			((cur++))
+			needs_update=true
+			echo "$line" >> "$TMPFILE"
+			continue
+		fi
+
+		if $needs_update; then
+			((cur++))
+			needs_update=false
+			echo "+#line $cur" >> "$TMPFILE"
+			echo "$line" >> "$TMPFILE"
+			continue
+		fi
+
+		((cur++))
+		echo "$line" >> "$TMPFILE"
+
+		if [ $cur = $last ]; then
+			in_hunk=false
+		fi
+
+		continue
+	fi
+
+	echo "$line" >> "$TMPFILE"
+
+done < "$patch"
+
+mv -f "$TMPFILE" "$output"
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
new file mode 100755
index 000000000000..e16584a4b697
--- /dev/null
+++ b/scripts/livepatch/klp-build
@@ -0,0 +1,355 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+
+set -o errexit
+set -o errtrace
+set -o pipefail
+
+SCRIPT="$(basename "$0")"
+SCRIPTDIR="$(readlink -f "$(dirname "$(type -p "$0")")")"
+
+SRC="$(pwd)"
+BUILD_DIR="$SRC"
+TMP_DIR="$BUILD_DIR/klp-tmp"
+ORIG_DIR="$TMP_DIR/orig"
+PATCHED_DIR="$TMP_DIR/patched"
+OUTPUT_DIR="$TMP_DIR/out"
+TMP_TMP_DIR="$TMP_DIR/tmp"
+
+shopt -o xtrace | grep -q 'on' && XTRACE=1
+
+status() {
+	echo -e "- $SCRIPT: $*"
+}
+
+warn() {
+	str="$1"
+	if [[ -n "$str" ]]; then
+		echo "$SCRIPT: error: $*" >&2
+	else
+		echo "$SCRIPT: error" >&2
+	fi
+}
+
+die() {
+	warn "$@"
+	revert_applied_patches "--recount"
+	[[ -z "$DEBUG" ]] && rm -rf "$TMP_DIR"
+	exit 1
+}
+
+__trap() {
+	die "line $1"
+}
+
+trap '__trap ${LINENO}' ERR INT
+
+__usage() {
+	echo "Usage: $SCRIPT [options] patch_file(s)"
+	echo "Generate a livepatch module"
+	echo
+	echo "Options:"
+	echo "  -h, --help       Show this help message and exit"
+	echo
+	echo "Arguments:"
+	echo "  patch_file(s)    One or more .patch files"
+}
+
+usage() {
+	__usage >&2
+}
+
+apply_patch() {
+	local patch="$1"
+	local extra_args="$2"
+
+	[[ -f "$patch" ]] || die "$patch doesn't exist"
+
+	( cd "$SRC" && git apply --quiet --check $extra_args "$patch" ) || die "$patch failed to apply"
+	( cd "$SRC" && git apply --quiet $extra_args "$patch" )         || die "$patch failed to apply"
+	APPLIED_PATCHES+=("$patch")
+}
+
+revert_patch() {
+	local patch="$1"
+	local extra_args="$2"
+
+	( cd "$SRC" && git apply --reverse --quiet $extra_args "$patch" ) || die "$patch failed to apply"
+	APPLIED_PATCHES=("${APPLIED_PATCHES[@]/$patch}")
+}
+
+revert_applied_patches() {
+	local patches=("${APPLIED_PATCHES[@]}")
+	local extra_args="$1"
+
+	for (( i=${#patches[@]}-1 ; i>=0 ; i-- )) ; do
+		local patch="${patches[$i]}"
+
+		# "deleted" entry can still exist as an empty string
+		[[ "$patch" ]] || continue
+
+		revert_patch "${patches[$i]}" "$extra_args"
+	done
+
+	APPLIED_PATCHES=()
+}
+
+apply_patches() {
+	for patch in "${PATCHES[@]}"; do
+		apply_patch "$patch"
+	done
+}
+
+validate_patches() {
+	apply_patches
+	revert_applied_patches
+}
+
+refresh_patch() {
+	local patch="$1"
+	local tmp="$TMP_TMP_DIR"
+
+	rm -rf "$tmp"
+	mkdir -p "$tmp"
+
+	while read -r file; do
+		local dest
+		dest="$tmp/a/$(dirname "$file")"
+		mkdir -p "$dest"
+		cp -f "$SRC/$file" "$dest"
+	done < <(grep -E '^(--- |\+\+\+ )' "$patch" | sed -E 's/(--- a\/|\+\+\+ b\/)//' | sort | uniq)
+
+	apply_patch "$patch" --recount
+
+	while read -r file; do
+		local dest
+		dest="$tmp/b/$(dirname "$file")"
+		mkdir -p "$dest"
+		cp -f "$SRC/$file" "$dest"
+	done < <(grep -E '^(--- |\+\+\+ )' "$patch" | sed -E 's/(--- a\/|\+\+\+ b\/)//' | sort | uniq)
+
+	revert_patch "$patch" --recount
+
+	(
+		cd "$tmp"
+		git diff --no-index --no-prefix a b > "$patch" || true
+	)
+}
+
+# Copy the patches to a temporary directory, fix their lines so as not to
+# affect the __LINE__ macro for otherwise unchanged functions, and update
+# $PATCHES to point to fixed patches.
+copy_and_fix_patches() {
+
+	idx=0001
+	for patch in "${PATCHES[@]}"; do
+		cp -f "$patch"  "$TMP_DIR/$idx-$(basename "$patch")"
+		idx=$(printf "%04d" $((10#$idx + 1)))
+	done
+
+	PATCHES=()
+	idx=0001
+	while true; do
+		patch="$(ls "$TMP_DIR"/"$idx"-*.patch 2> /dev/null || true)"
+		[[ -z "$patch" ]] && break
+
+		refresh_patch "$patch"
+		"$SCRIPTDIR/adjust-patch-lines" "$patch" || die "adjust-patch-lines failed"
+		refresh_patch "$patch"
+		apply_patch "$patch"
+
+		idx=$(printf "%04d" $((10#$idx + 1)))
+	done
+
+	revert_applied_patches
+
+	idx=0001
+	while true; do
+		patch="$(ls "$TMP_DIR"/"$idx"-*.patch 2> /dev/null || true)"
+		[[ -z "$patch" ]] && break
+
+		PATCHES+=("$patch")
+
+		idx=$(printf "%04d" $((10#$idx + 1)))
+	done
+}
+
+build_kernel() {
+	local options
+
+	if [[ -n "$VERBOSE" ]]; then
+		options="V=1"
+	else
+		options="-s"
+	fi
+
+	( cd "$SRC" && make -j"$(nproc)" "$options" vmlinux modules ) || die "kernel build failed"
+}
+
+copy_orig_objs() {
+	if [[ "$XTRACE" = 1 ]]; then
+		set +x
+	fi
+
+	while read -r _file; do
+		local file="${_file/.ko/.o}"
+		local rel_file="${file#"$BUILD_DIR"/}"
+		local dest_dir="$ORIG_DIR/$(dirname "$rel_file")"
+
+		# ignore any livepatch modules in pwd
+		if [[ "$_file" = *.ko ]] && [[ "$(dirname "$file")" -ef "$BUILD_DIR" ]]; then
+			continue
+		fi
+
+		[[ -f "$file" ]] || die "can't find $file"
+
+		mkdir -p "$dest_dir"
+		cp -f "$file" "$dest_dir" || die "cp -f $file $dest_dir failed"
+	done < <(find "$BUILD_DIR" -type f \( -name vmlinux.o -o -name "*.ko" \))
+
+	if [[ -n "$XTRACE" ]]; then
+		set -x
+	fi
+}
+
+diff_objects() {
+	local timestamp="$1"
+
+	while read -r _file; do
+		local file="${_file/.ko/.o}"
+		local rel_file="${file#"$BUILD_DIR"/}"
+		local orig="$ORIG_DIR/$rel_file"
+		local patched="$PATCHED_DIR/$rel_file"
+		local output="$OUTPUT_DIR/$rel_file"
+
+		[[ -f "$file" ]] || die "can't find $file"
+
+		mkdir -p "$(dirname "$patched")"
+		cp -f "$file" "$patched"
+
+		mkdir -p "$(dirname "$output")"
+
+		# status "diff: $rel_file"
+
+		"$SRC/tools/objtool/objtool" klp diff "$orig" "$patched" "$output" || die "objtool klp diff failed"
+	done < <(find "$BUILD_DIR" -type f \( -name vmlinux.o -o -name "*.ko" \) -newer "$timestamp")
+
+	local nr_objs="$(find "$OUTPUT_DIR" -type f \( -name vmlinux.o -o -name "*.ko" \) | wc -l)"
+
+	if [[ "$nr_objs" = 0 ]]; then
+		die "no changes detected"
+	fi
+}
+
+build_patch_module() {
+	local makefile="$OUTPUT_DIR/Kbuild"
+	local verbose
+	local replace
+
+	cp -f "$SRC/scripts/livepatch/module.c" "$OUTPUT_DIR"
+
+	echo "obj-m := $NAME.o" > "$makefile"
+	echo -n "$NAME-y := module.o" >> "$makefile"
+
+	while read -r file; do
+		cp -f "$file" "$file"_shipped
+		echo -n " ${file#"$OUTPUT_DIR"/}" >> "$makefile"
+	done < <(find "$OUTPUT_DIR" -type f -name "*.o" )
+	echo >> "$makefile"
+
+	if [[ -n "$VERBOSE" ]]; then
+		verbose="V=1"
+	else
+		verbose="-s"
+	fi
+
+	[[ $REPLACE == 1 ]] && replace="KCFLAGS=-DKLP_REPLACE"
+
+	make -C . M="$OUTPUT_DIR" "$verbose" $replace || die "module build failed"
+
+	cp -f "$OUTPUT_DIR/$NAME.ko" "$OUTPUT_DIR/$NAME.ko.prelink"
+
+	"$SRC/tools/objtool/objtool" klp link "$OUTPUT_DIR/$NAME.ko" || die "objtool klp link failed"
+
+	cp -f "$OUTPUT_DIR/$NAME.ko" .
+}
+
+args=$(getopt -o dhn:v -l "debug,help,name:,verbose,noreplace" -- "$@")
+eval set -- "$args"
+
+NAME="livepatch"
+REPLACE=1
+
+while true; do
+	case "$1" in
+		-d | --debug)
+			DEBUG=1
+			;;
+		-h | --help)
+			usage
+			exit 0
+			;;
+		-n | --name)
+			NAME="$2"
+			shift
+			;;
+		--noreplace)
+			REPLACE=0
+			;;
+		-v | --verbose)
+			VERBOSE=1
+			;;
+		--)
+			shift
+			break
+			;;
+		*)
+			usage
+			exit 1
+			;;
+	esac
+	shift
+done
+
+if [[ $# -eq 0 ]]; then
+	usage
+	exit 1
+fi
+
+# not yet smart enough to handle anything other than in-tree builds from pwd
+[[ "$PWD" -ef "$SCRIPTDIR/../.." ]] || die "please run from the kernel root directory"
+
+[[ -x "$SCRIPTDIR/adjust-patch-lines" ]] || die "can't find adjust-patch-lines script"
+
+validate_patches
+
+rm -rf "$TMP_DIR"
+mkdir -p "$TMP_DIR"
+APPLIED_PATCHES=()
+PATCHES=("$@")
+
+# this updates ${PATCHES} to point to the modified versions
+copy_and_fix_patches
+
+status "building original kernel"
+build_kernel
+copy_orig_objs
+
+touch "$TMP_DIR/timestamp"
+
+status "building patched kernel"
+apply_patches
+export KBUILD_MODPOST_WARN=1
+build_kernel
+revert_applied_patches
+
+status "diffing objects"
+diff_objects "$TMP_DIR/timestamp"
+
+status "building patch module"
+build_patch_module
+
+status  "success"
+[[ -z "$DEBUG" ]] && rm -rf "$TMP_DIR"
diff --git a/scripts/livepatch/module.c b/scripts/livepatch/module.c
new file mode 100644
index 000000000000..101cabf6b2f1
--- /dev/null
+++ b/scripts/livepatch/module.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Base module code for a livepatch kernel module
+ *
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/livepatch.h>
+
+// TODO livepatch could recognize these sections directly
+// TODO use function checksums instead of sympos
+
+extern char __start_klp_objects, __stop_klp_objects;
+
+/*
+ * Create weak versions of the linker-created symbols to prevent modpost from
+ * warning about unresolved symbols.
+ */
+__weak char __start_klp_objects = 0;
+__weak char __stop_klp_objects  = 0;
+struct klp_object_ext *__start_objs = (struct klp_object_ext *)&__start_klp_objects;
+struct klp_object_ext *__stop_objs  = (struct klp_object_ext *)&__stop_klp_objects;
+
+static struct klp_patch *patch;
+
+static int __init livepatch_mod_init(void)
+{
+	struct klp_object *objs;
+	unsigned int nr_objs;
+	int ret;
+
+	nr_objs = __stop_objs - __start_objs;
+
+	if (!__start_klp_objects || !nr_objs) {
+		pr_err("nothing to patch!\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	patch = kzalloc(sizeof(*patch), GFP_KERNEL);
+	if (!patch) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	objs = kzalloc(sizeof(struct klp_object) * (nr_objs + 1),  GFP_KERNEL);
+	if (!objs) {
+		ret = -ENOMEM;
+		goto err_free_patch;
+	}
+
+	for (int i = 0; i < nr_objs; i++) {
+		struct klp_object_ext *obj_ext = __start_objs + i;
+		struct klp_func_ext *funcs_ext = obj_ext->funcs;
+		unsigned int nr_funcs = obj_ext->nr_funcs;
+		struct klp_func *funcs = objs[i].funcs;
+		struct klp_object *obj = objs + i;
+
+		funcs = kzalloc(sizeof(struct klp_func) * (obj_ext->nr_funcs + 1), GFP_KERNEL);
+		if (!funcs) {
+			ret = -ENOMEM;
+			for (int j = 0; j < i; j++)
+				kfree(objs[i].funcs);
+			goto err_free_objs;
+		}
+
+		for (int j = 0; j < nr_funcs; j++) {
+			funcs[j].old_name   = funcs_ext[j].old_name;
+			funcs[j].new_func   = funcs_ext[j].new_func;
+			funcs[j].old_sympos = funcs_ext[j].sympos;
+		}
+
+		obj->name = obj_ext->name;
+		obj->funcs = funcs;
+
+		memcpy(&obj->callbacks, &obj_ext->callbacks, sizeof(struct klp_callbacks));
+	}
+
+	patch->mod = THIS_MODULE;
+	patch->objs = objs;
+
+	/* TODO patch->states */
+
+#ifdef KLP_REPLACE
+	patch->replace = true;
+#else
+	patch->replace = false;
+#endif
+
+	return klp_enable_patch(patch);
+
+err_free_objs:
+	kfree(objs);
+err_free_patch:
+	kfree(patch);
+err:
+	return ret;
+}
+
+static void __exit livepatch_mod_exit(void)
+{
+	unsigned int nr_objs;
+
+	nr_objs = __stop_objs - __start_objs;
+
+	for (int i = 0; i < nr_objs; i++)
+		kfree(patch->objs[i].funcs);
+
+	kfree(patch->objs);
+	kfree(patch);
+}
+
+module_init(livepatch_mod_init);
+module_exit(livepatch_mod_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index 5cbae820bca0..aec4b9f0ec95 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -32,8 +32,15 @@ SECTIONS {
 
 	__patchable_function_entries : { *(__patchable_function_entries) }
 
+	__klp_objects		0: ALIGN(8) {
+		__start_klp_objects = .;
+		KEEP(*(__klp_objects))
+		__stop_klp_objects = .;
+	}
+	__klp_funcs		0: ALIGN(8) { KEEP(*(__klp_funcs)) }
+
 #ifdef CONFIG_ARCH_USES_CFI_TRAPS
-	__kcfi_traps 		: { KEEP(*(.kcfi_traps)) }
+	__kcfi_traps		: { KEEP(*(.kcfi_traps)) }
 #endif
 
 #if defined(CONFIG_LTO_CLANG) || defined(CONFIG_LIVEPATCH)
diff --git a/tools/include/linux/livepatch_ext.h b/tools/include/linux/livepatch_ext.h
new file mode 100644
index 000000000000..4b71e72952d5
--- /dev/null
+++ b/tools/include/linux/livepatch_ext.h
@@ -0,0 +1,83 @@
+/* SPDX License-Identifier: GPL-2.0-or-later */
+/*
+ * External livepatch interfaces for patch creation tooling
+ *
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+
+#ifndef _LINUX_LIVEPATCH_EXT_H_
+#define _LINUX_LIVEPATCH_EXT_H_
+
+#include <linux/types.h>
+
+#define KLP_RELOC_SEC_PREFIX	".klp.rela."
+#define KLP_SYM_PREFIX		".klp.sym."
+
+#define KLP_CALLBACKS_SEC		".discard.klp_callbacks"
+
+#define __KLP_PRE_PATCH_PREFIX		__klp_pre_patch_callback_
+#define __KLP_POST_PATCH_PREFIX		__klp_post_patch_callback_
+#define __KLP_PRE_UNPATCH_PREFIX	__klp_pre_unpatch_callback_
+#define __KLP_POST_UNPATCH_PREFIX	__klp_post_unpatch_callback_
+
+#define KLP_PRE_PATCH_PREFIX		__stringify(__KLP_PRE_PATCH_PREFIX)
+#define KLP_POST_PATCH_PREFIX		__stringify(__KLP_POST_PATCH_PREFIX)
+#define KLP_PRE_UNPATCH_PREFIX		__stringify(__KLP_PRE_UNPATCH_PREFIX)
+#define KLP_POST_UNPATCH_PREFIX		__stringify(__KLP_POST_UNPATCH_PREFIX)
+
+struct klp_object;
+
+typedef int (*klp_pre_patch_t)(struct klp_object *obj);
+typedef void (*klp_post_patch_t)(struct klp_object *obj);
+typedef void (*klp_pre_unpatch_t)(struct klp_object *obj);
+typedef void (*klp_post_unpatch_t)(struct klp_object *obj);
+
+/**
+ * struct klp_callbacks - pre/post live-(un)patch callback structure
+ * @pre_patch:		executed before code patching
+ * @post_patch:		executed after code patching
+ * @pre_unpatch:	executed before code unpatching
+ * @post_unpatch:	executed after code unpatching
+ * @post_unpatch_enabled:	flag indicating if post-unpatch callback
+ *				should run
+ *
+ * All callbacks are optional.  Only the pre-patch callback, if provided,
+ * will be unconditionally executed.  If the parent klp_object fails to
+ * patch for any reason, including a non-zero error status returned from
+ * the pre-patch callback, no further callbacks will be executed.
+ */
+struct klp_callbacks {
+	klp_pre_patch_t		pre_patch;
+	klp_post_patch_t	post_patch;
+	klp_pre_unpatch_t	pre_unpatch;
+	klp_post_unpatch_t	post_unpatch;
+	bool post_unpatch_enabled;
+};
+
+/*
+ * 'struct klp_{func,object}_ext' are compact "external" representations of
+ * 'struct klp_{func,object}'.   They are used by objtool for livepatch
+ * generation.  The structs are then read by the livepatch module and converted
+ * to the real structs before calling klp_enable_patch().
+ *
+ * TODO make these the official API for klp_enable_patch().  That should
+ * simplify livepatch's interface as well as its data structure lifetime
+ * management.
+ *
+ * TODO possibly use struct_group_tagged() to declare these within the original
+ * structs.
+ */
+struct klp_func_ext {
+	const char *old_name;
+	void *new_func;
+	unsigned long sympos;
+};
+
+struct klp_object_ext {
+	const char *name;
+	struct klp_func_ext *funcs;
+	struct klp_callbacks callbacks;
+	unsigned int nr_funcs;
+};
+
+#endif /* _LINUX_LIVEPATCH_EXT_H_ */
diff --git a/tools/objtool/Build b/tools/objtool/Build
index a3cdf8af6635..f917e70f0fd0 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) += klp.o klp-diff.o klp-link.o
 
 objtool-y += libstring.o
 objtool-y += libctype.o
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 6833804ca419..cdae220abca0 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -2,6 +2,25 @@
 include ../scripts/Makefile.include
 include ../scripts/Makefile.arch
 
+BUILD_ORC := n
+BUILD_KLP := n
+
+ifeq ($(SRCARCH),x86)
+BUILD_ORC := y
+BUILD_KLP := y
+endif
+
+ifeq ($(SRCARCH),loongarch)
+BUILD_ORC := y
+endif
+
+export BUILD_ORC BUILD_KLP
+
+ifeq ($(BUILD_KLP),y)
+LIBXXHASH_FLAGS := $(shell $(HOSTPKG_CONFIG) libxxhash --cflags 2>/dev/null)
+LIBXXHASH_LIBS  := $(shell $(HOSTPKG_CONFIG) libxxhash --libs 2>/dev/null || echo -lxxhash)
+endif
+
 ifeq ($(srctree),)
 srctree := $(patsubst %/,%,$(dir $(CURDIR)))
 srctree := $(patsubst %/,%,$(dir $(srctree)))
@@ -21,9 +40,6 @@ OBJTOOL_IN := $(OBJTOOL)-in.o
 LIBELF_FLAGS := $(shell $(HOSTPKG_CONFIG) libelf --cflags 2>/dev/null)
 LIBELF_LIBS  := $(shell $(HOSTPKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
 
-LIBXXHASH_FLAGS := $(shell $(HOSTPKG_CONFIG) libxxhash --cflags 2>/dev/null)
-LIBXXHASH_LIBS  := $(shell $(HOSTPKG_CONFIG) libxxhash --libs 2>/dev/null || echo -lxxhash)
-
 all: $(OBJTOOL)
 
 INCLUDES := -I$(srctree)/tools/include \
@@ -54,17 +70,6 @@ else
   Q = @
 endif
 
-BUILD_ORC := n
-
-ifeq ($(SRCARCH),x86)
-	BUILD_ORC := y
-endif
-
-ifeq ($(SRCARCH),loongarch)
-	BUILD_ORC := y
-endif
-
-export BUILD_ORC
 export srctree OUTPUT CFLAGS SRCARCH AWK
 include $(srctree)/tools/build/Makefile.include
 
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index 5468fd15f380..bb24e963f371 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -94,6 +94,46 @@ s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
 	return phys_to_virt(addend);
 }
 
+static void scan_for_insn(struct section *sec, unsigned long offset,
+			  unsigned long *insn_off, unsigned int *insn_len)
+{
+	unsigned long o = 0;
+	struct insn insn;
+
+	while (1) {
+
+		insn_decode(&insn, sec->data->d_buf + o, sec_size(sec) - o,
+			    INSN_MODE_64);
+
+		if (o + insn.length > offset) {
+			*insn_off = o;
+			*insn_len = insn.length;
+			return;
+		}
+
+		o += insn.length;
+	}
+}
+
+u64 arch_adjusted_addend(struct reloc *reloc)
+{
+	unsigned int type = reloc_type(reloc);
+	s64 addend = reloc_addend(reloc);
+	unsigned long insn_off;
+	unsigned int insn_len;
+
+	if (type == R_X86_64_PLT32)
+		return addend + 4;
+
+	if (type != R_X86_64_PC32 || !is_text_section(reloc->sec->base))
+		return addend;
+
+	scan_for_insn(reloc->sec->base, reloc_offset(reloc),
+		      &insn_off, &insn_len);
+
+	return addend + insn_off + insn_len - reloc_offset(reloc);
+}
+
 unsigned long arch_jump_destination(struct instruction *insn)
 {
 	return insn->offset + insn->len + insn->immediate;
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 0e9e485cd3b6..f55dec2932de 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -647,6 +647,20 @@ static void create_fake_symbol(struct objtool_file *file, const char *name_pfx,
 	elf_create_symbol(file->elf, name, sec, STB_LOCAL, STT_OBJECT, offset, size);
 }
 
+static bool is_livepatch_module(struct objtool_file *file)
+{
+	struct section *sec;
+
+	if (!opts.module)
+		return false;
+
+	sec = find_section_by_name(file->elf, ".modinfo");
+	if (!sec)
+		return false;
+
+	return memmem(sec->data->d_buf, sec_size(sec), "livepatch=Y", 12);
+}
+
 static void create_static_call_sections(struct objtool_file *file)
 {
 	struct static_call_site *site;
@@ -659,7 +673,14 @@ static void create_static_call_sections(struct objtool_file *file)
 	sec = find_section_by_name(file->elf, ".static_call_sites");
 	if (sec) {
 		INIT_LIST_HEAD(&file->static_call_list);
-		WARN("file already has .static_call_sites section, skipping");
+
+		/*
+		 * Livepatch modules may have already extracted the static call
+		 * site entries.
+		 */
+		if (!file->klp)
+			WARN("file already has .static_call_sites section, skipping");
+
 		return;
 	}
 
@@ -696,7 +717,7 @@ static void create_static_call_sections(struct objtool_file *file)
 
 		key_sym = find_symbol_by_name(file->elf, tmp);
 		if (!key_sym) {
-			if (!opts.module)
+			if (!opts.module || file->klp)
 				ERROR("static_call: can't find static_call_key symbol: %s", tmp);
 
 			/*
@@ -2406,6 +2427,8 @@ static void mark_rodata(struct objtool_file *file)
 
 static void decode_sections(struct objtool_file *file)
 {
+	file->klp = is_livepatch_module(file);
+
 	mark_rodata(file);
 
 	init_pv_ops(file);
@@ -4006,7 +4029,7 @@ static void add_prefix_symbol(struct objtool_file *file, struct symbol *func)
 			continue;
 
 		sym_pfx = elf_create_prefix_symbol(file->elf, func, opts.prefix);
-		if (!sym_pfx)
+		if (!sym_pfx && !file->klp)
 			ERROR("duplicate prefix symbol for %s\n", func->name);
 
 		break;
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
index 022873bf7064..7960921996bd 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -21,6 +21,7 @@
 #include <linux/interval_tree_generic.h>
 #include <objtool/builtin.h>
 #include <objtool/elf.h>
+#include <objtool/klp.h>
 #include <objtool/warn.h>
 
 #define ALIGN_UP(x, align_to) (((x) + ((align_to)-1)) & ~((align_to)-1))
@@ -456,6 +457,8 @@ static void elf_add_symbol(struct elf *elf, struct symbol *sym)
 	else
 		entry = &sym->sec->symbol_list;
 	list_add(&sym->list, entry);
+
+	list_add_tail(&sym->global_list, &elf->symbols);
 	elf_hash_add(symbol, &sym->hash, sym->idx);
 	elf_hash_add(symbol_name, &sym->name_hash, str_hash(sym->name));
 
@@ -505,6 +508,8 @@ static void read_symbols(struct elf *elf)
 	elf->symbol_data = calloc(symbols_nr, sizeof(*sym));
 	ERROR_ON(!elf->symbol_data, "calloc");
 
+	INIT_LIST_HEAD(&elf->symbols);
+
 	for (i = 0; i < symbols_nr; i++) {
 		sym = &elf->symbol_data[i];
 
@@ -720,7 +725,7 @@ static void elf_update_symbol(struct elf *elf, struct section *symtab,
 static struct symbol *__elf_create_symbol(struct elf *elf, const char *name,
 					  struct section *sec, unsigned int bind,
 					  unsigned int type, unsigned long offset,
-					  size_t size)
+					  size_t size, bool klp)
 {
 	struct section *symtab, *symtab_shndx;
 	Elf32_Word first_non_local, new_idx;
@@ -735,6 +740,9 @@ static struct symbol *__elf_create_symbol(struct elf *elf, const char *name,
 			sym->sym.st_name = elf_add_string(elf, NULL, sym->name);
 	}
 
+	if (klp)
+		sym->sym.st_shndx = SHN_LIVEPATCH;
+
 	sym->sec = sec ? : find_section_by_index(elf, 0);
 
 	sym->sym.st_info  = GELF_ST_INFO(bind, type);
@@ -799,7 +807,7 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name,
 				   unsigned int type, unsigned long offset,
 				   size_t size)
 {
-	return __elf_create_symbol(elf, name, sec, bind, type, offset, size);
+	return __elf_create_symbol(elf, name, sec, bind, type, offset, size, false);
 }
 
 struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec)
@@ -812,6 +820,12 @@ struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec)
 	return sym;
 }
 
+struct symbol *elf_create_klp_symbol(struct elf *elf, const char *name,
+				     unsigned int bind, unsigned int type)
+{
+	return __elf_create_symbol(elf, name, NULL, bind, type, 0, 0, true);
+}
+
 struct symbol *
 elf_create_prefix_symbol(struct elf *elf, struct symbol *orig, size_t size)
 {
diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h
index 14911fdfdc8f..d9f019ef89a7 100644
--- a/tools/objtool/include/objtool/arch.h
+++ b/tools/objtool/include/objtool/arch.h
@@ -82,6 +82,7 @@ bool arch_callee_saved_reg(unsigned char reg);
 unsigned long arch_jump_destination(struct instruction *insn);
 
 s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc);
+u64 arch_adjusted_addend(struct reloc *reloc);
 
 const char *arch_nop_insn(int len);
 const char *arch_ret_insn(int len);
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index eab376169c1e..26bbf04afb24 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -46,5 +46,6 @@ extern struct opts opts;
 extern int cmd_parse_options(int argc, const char **argv, const char * const usage[]);
 
 extern int objtool_run(int argc, const char **argv);
+extern int cmd_klp(int argc, const char **argv);
 
 #endif /* _BUILTIN_H */
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index 1f14f33d279e..43839b3ac80f 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -16,6 +16,7 @@
 #include <xxhash.h>
 #include <arch/elf.h>
 
+#define SEC_NAME_LEN		512
 #define SYM_NAME_LEN		512
 
 #ifdef LIBELF_USE_DEPRECATED
@@ -53,10 +54,12 @@ struct section {
 	int idx;
 	bool _changed, text, rodata, noinstr, init, truncate;
 	struct reloc *relocs;
+	struct section *twin;
 };
 
 struct symbol {
 	struct list_head list;
+	struct list_head global_list;
 	struct rb_node node;
 	struct elf_hash_node hash;
 	struct elf_hash_node name_hash;
@@ -77,8 +80,11 @@ struct symbol {
 	u8 warned	     : 1;
 	u8 embedded_insn     : 1;
 	u8 local_label       : 1;
+	u8 changed	     : 1;
+	u8 added	     : 1;
 	struct list_head pv_target;
 	struct reloc *relocs;
+	struct symbol *twin, *clone;
 
 	XXH3_state_t *checksum_state;
 	XXH64_hash_t checksum;
@@ -99,6 +105,7 @@ struct elf {
 	const char *name, *tmp_name;
 	unsigned int num_files;
 	struct list_head sections;
+	struct list_head symbols;
 	unsigned long num_relocs;
 
 	int symbol_bits;
@@ -138,6 +145,8 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name,
 struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec);
 struct symbol *elf_create_prefix_symbol(struct elf *elf, struct symbol *orig,
 					size_t size);
+struct symbol *elf_create_klp_symbol(struct elf *elf, const char *name,
+				     unsigned int bind, unsigned int type);
 
 struct reloc *elf_create_reloc(struct elf *elf, struct section *sec,
 			       unsigned long offset, struct symbol *sym,
@@ -412,11 +421,14 @@ static inline void set_reloc_type(struct elf *elf, struct reloc *reloc, unsigned
 #define sec_for_each_sym_continue_reverse(sec, sym)			\
 	list_for_each_entry_continue_reverse(sym, &sec->symbol_list, list)
 
+#define sec_prev_sym(sec, sym)						\
+	sym->list.prev == &sec->symbol_list ? NULL : list_prev_entry(sym, list)
+
 #define for_each_sym(elf, sym)						\
-	for (struct section *__sec, *__fake = (struct section *)1;	\
-	     __fake; __fake = NULL)					\
-		for_each_sec(elf, __sec)				\
-			sec_for_each_sym(__sec, sym)
+	list_for_each_entry(sym, &elf->symbols, global_list)
+
+#define for_each_sym_continue(elf, sym)					\
+	list_for_each_entry_continue(sym, &elf->symbols, global_list)
 
 #define for_each_reloc(rsec, reloc)					\
 	for (int __i = 0, __fake = 1; __fake; __fake = 0)		\
diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h
new file mode 100644
index 000000000000..0df1dd273e1e
--- /dev/null
+++ b/tools/objtool/include/objtool/klp.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+#ifndef _OBJTOOL_KLP_H
+#define _OBJTOOL_KLP_H
+
+#define SHF_RELA_LIVEPATCH	0x00100000
+#define SHN_LIVEPATCH		0xff20
+
+#define KLP_RELOCS_SEC	".klp.relocs"
+#define KLP_OBJECTS_SEC	"__klp_objects"
+#define KLP_FUNCS_SEC	"__klp_funcs"
+#define KLP_STRINGS_SEC	".rodata.klp.str1.1"
+
+struct klp_reloc {
+	void *offset;
+	void *sym;
+	u32 type;
+};
+
+int cmd_klp_diff(int argc, const char **argv);
+int cmd_klp_link(int argc, const char **argv);
+
+#endif /* _OBJTOOL_KLP_H */
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index 3280abcce55e..f562b08bfbff 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -30,7 +30,7 @@ struct objtool_file {
 	struct list_head mcount_loc_list;
 	struct list_head endbr_list;
 	struct list_head call_list;
-	bool ignore_unreachables, hints, rodata;
+	bool ignore_unreachables, hints, rodata, klp;
 
 	unsigned int nr_endbr;
 	unsigned int nr_endbr_int;
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
new file mode 100644
index 000000000000..76296e38f9ff
--- /dev/null
+++ b/tools/objtool/klp-diff.c
@@ -0,0 +1,1112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+#include <libgen.h>
+#include <stdio.h>
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+#include <objtool/arch.h>
+#include <objtool/klp.h>
+#include <linux/livepatch_ext.h>
+#include <linux/stringify.h>
+#include <linux/string.h>
+#include <linux/jhash.h>
+
+#define ALIGN_DOWN(x, align_to) ((x) & ~((align_to)-1))
+#define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER))
+
+struct elfs {
+	struct elf *orig, *patched, *out;
+};
+
+struct export {
+	struct hlist_node hash;
+	char *mod, *sym;
+};
+
+static DEFINE_HASHTABLE(exports, 15);
+
+static inline u32 str_hash(const char *str)
+{
+	return jhash(str, strlen(str), 0);
+}
+
+static void read_exports(void)
+{
+	const char *symvers = "Module.symvers";
+	char line[1024];
+	FILE *file;
+
+	file = fopen(symvers, "r");
+	ERROR_ON(!file, "can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers);
+
+	while (fgets(line, 1024, file)) {
+		char *sym, *mod, *exp;
+		struct export *export;
+
+		sym = strchr(line, '\t');
+		ERROR_ON(!sym, "malformed Module.symvers");
+
+		*sym++ = '\0';
+
+		mod = strchr(sym, '\t');
+		ERROR_ON(!mod, "malformed Module.symvers");
+
+		*mod++ = '\0';
+
+		exp = strchr(mod, '\t');
+		ERROR_ON(!exp, "malformed Module.symvers");
+
+		*exp++ = '\0';
+
+		ERROR_ON(*sym == '\0' || *mod == '\0', "malformed Module.symvers");
+
+		export = calloc(1, sizeof(*export));
+		ERROR_ON(!export, "calloc");
+
+		export->mod = strdup(mod);
+		export->sym = strdup(sym);
+		hash_add(exports, &export->hash, str_hash(sym));
+	}
+}
+
+static int read_sym_checksums(struct elf *elf)
+{
+	struct section *sec;
+
+	sec = find_section_by_name(elf, SYM_CHECKSUM_SEC);
+	if (!sec)
+		return 0;
+
+	if (!sec->rsec)
+		ERROR("missing reloc section for " SYM_CHECKSUM_SEC);
+
+	if (sec_size(sec) % sizeof(struct sym_checksum))
+		ERROR("struct sym_checksum size mismatch");
+
+	for (int i = 0; i < sec_size(sec) / sizeof(struct sym_checksum); i++) {
+		struct sym_checksum *sym_checksum;
+		struct reloc *reloc;
+		struct symbol *sym;
+
+		sym_checksum = (struct sym_checksum *)sec->data->d_buf + i;
+
+		reloc = find_reloc_by_dest(elf, sec, i * sizeof(*sym_checksum));
+		if (!reloc)
+			ERROR("can't find reloc for sym_checksum[%d]", i);
+
+		sym = reloc->sym;
+
+		if (is_section_symbol(sym))
+			ERROR("not sure how to handle section %s", sym->name);
+
+		if (is_function_symbol(sym))
+			sym->checksum = sym_checksum->checksum;
+	}
+
+	return 0;
+}
+static struct symbol *first_file_symbol(struct elf *elf)
+{
+	struct symbol *sym;
+
+	for_each_sym(elf, sym)
+		if (is_file_symbol(sym))
+			return sym;
+
+	return NULL;
+}
+
+static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym)
+{
+	for_each_sym_continue(elf, sym)
+		if (is_file_symbol(sym))
+			return sym;
+
+	return NULL;
+}
+
+/*
+ * Certain static local variables should never be correlated.  They will be
+ * used in place rather than referencing the originals.
+ */
+static bool is_uncorrelated_static_local(struct symbol *sym)
+{
+	static const char * const vars[] = {
+		"__key.",
+		"__warned.",
+		"__already_done.",
+		"__func__.",
+		"_rs.",
+		"CSWTCH.",
+	};
+
+	if (!is_object_symbol(sym) || !is_local_symbol(sym))
+		return false;
+
+	if (!strcmp(sym->sec->name, ".data.once"))
+		return true;
+
+	for (int i = 0; i < ARRAY_SIZE(vars); i++) {
+		if (strstarts(sym->name, vars[i]))
+			return true;
+	}
+
+	return false;
+}
+
+static bool is_special_section(struct section *sec)
+{
+	static const char * const specials[] = {
+		".altinstructions",
+		".smp_locks",
+		"__bug_table",
+		"__ex_table",
+		"__jump_table",
+		"__mcount_loc",
+		/*
+		 * .static_call_sites is generated by "objtool --static-call"
+		 * which will run again later at livepatch module link time.
+		 * So one might be forgiven for thinking it doesn't need to be
+		 * extracted here.
+		 *
+		 * However, when run on modules, objtool blocks access to
+		 * unexported static call keys.
+		 *
+		 * So extract it here to inherit the non-module preferential
+		 * treatment.  The later static call processing will be skipped
+		 * when it sees this section already exists.
+		 */
+		".static_call_sites",
+	};
+
+	static const char * const non_special_discards[] = {
+		".discard.addressable",
+		SYM_CHECKSUM_SEC,
+	};
+
+	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++)
+		if (!strcmp(sec->name, non_special_discards[i]))
+			return false;
+	return strstarts(sec->name, ".discard.");
+}
+
+/*
+ * These sections are referenced by special sections but aren't considered
+ * special sections themselves.
+ */
+static bool is_special_section_aux(struct section *sec)
+{
+	static const char * const specials_aux[] = {
+		".altinstr_replacement",
+		".altinstr_aux",
+	};
+
+	for (int i = 0; i < ARRAY_SIZE(specials_aux); i++)
+		if (!strcmp(sec->name, specials_aux[i]))
+			return true;
+
+	return false;
+}
+
+/*
+ * These symbols should never be correlated, so their local patched versions
+ * are used instead of linking to the originals.
+ */
+static bool dont_correlate(struct symbol *sym)
+{
+	return is_file_symbol(sym) ||
+	       is_null_symbol(sym) ||
+	       is_section_symbol(sym) ||
+	       is_prefix_symbol(sym) ||
+	       is_uncorrelated_static_local(sym) ||
+	       is_string_section(sym->sec) ||
+	       is_special_section(sym->sec) ||
+	       is_special_section_aux(sym->sec) ||
+	       strstarts(sym->name, "__UNIQUE_ID") ||
+	       strstarts(sym->name, "__initcall__");
+}
+
+/*
+ * For each symbol in the original kernel, find its corresponding "twin" in the
+ * patched kernel.
+ */
+static void correlate_symbols(struct elf *elf1, struct elf *elf2)
+{
+	struct symbol *file1_sym, *file2_sym;
+	struct symbol *sym1, *sym2;
+
+	/* Correlate locals */
+	for (file1_sym = first_file_symbol(elf1),
+	     file2_sym = first_file_symbol(elf2); ;
+	     file1_sym = next_file_symbol(elf1, file1_sym),
+	     file2_sym = next_file_symbol(elf2, file2_sym)) {
+
+		if (!file1_sym && file2_sym)
+			ERROR("FILE symbol mismatch: NULL != %s", file2_sym->name);
+
+		if (file1_sym && !file2_sym)
+			ERROR("FILE symbol mismatch: %s != NULL", file1_sym->name);
+
+		if (!file1_sym)
+			break;
+
+		if (strcmp(file1_sym->name, file2_sym->name))
+			ERROR("FILE symbol mismatch: %s != %s",file1_sym->name, file2_sym->name);
+
+		file1_sym->twin = file2_sym;
+		file2_sym->twin = file1_sym;
+
+		sym1 = file1_sym;
+
+		for_each_sym_continue(elf1, sym1) {
+			if (is_file_symbol(sym1) || !is_local_symbol(sym1))
+				break;
+
+			if (dont_correlate(sym1))
+				continue;
+
+			sym2 = file2_sym;
+			for_each_sym_continue(elf2, sym2) {
+				if (is_file_symbol(sym2) || !is_local_symbol(sym2))
+					break;
+
+				if (sym2->twin || dont_correlate(sym2))
+					continue;
+
+				if (strcmp(sym1->demangled_name, sym2->demangled_name))
+					continue;
+
+				sym1->twin = sym2;
+				sym2->twin = sym1;
+				break;
+			}
+		}
+	}
+
+	/* Correlate globals */
+	for_each_sym(elf1, sym1) {
+		if (sym1->bind == STB_LOCAL)
+			continue;
+
+		sym2 = find_global_symbol_by_name(elf2, sym1->name);
+
+		if (sym2 && !sym2->twin && !strcmp(sym1->name, sym2->name)) {
+			sym1->twin = sym2;
+			sym2->twin = sym1;
+		}
+	}
+
+	for_each_sym(elf1, sym1) {
+		if (sym1->twin || dont_correlate(sym1))
+			continue;
+		WARN("no correlation: %s", sym1->name);
+	}
+}
+
+/* "sympos" is used by livepatch to disambiguate duplicate symbol names */
+static unsigned long find_sympos(struct elf *elf, struct symbol *sym)
+{
+	unsigned long sympos = 0, nr_matches = 0;
+	bool has_dup = false;
+	struct symbol *s;
+
+	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 (!sympos)
+		ERROR("can't find sympos for %s", sym->name);
+
+	return has_dup ? sympos : 0;
+}
+
+static void clone_sym_relocs(struct elfs *e, struct symbol *sym_patched);
+
+static struct symbol *__clone_symbol(struct elf *elf, struct symbol *sym_patched,
+				     bool data_too)
+{
+	struct section *sec = NULL;
+	unsigned long offset = 0;
+	struct symbol *sym;
+
+	if (data_too && sym_has_section(sym_patched)) {
+		struct section *sec_patched = sym_patched->sec;
+
+		sec = find_section_by_name(elf, sec_patched->name);
+		if (!sec)
+			sec = elf_create_section(elf, sec_patched->name, 0,
+						 sec_patched->sh.sh_entsize,
+						 sec_patched->sh.sh_type,
+						 sec_patched->sh.sh_addralign,
+						 sec_patched->sh.sh_flags);
+
+		if (is_string_section(sym_patched->sec)) {
+			sym = elf_create_section_symbol(elf, sec);
+			goto sym_created;
+		}
+
+		if (!is_section_symbol(sym_patched))
+			offset = sec_size(sec);
+
+		if (sym_patched->len || is_section_symbol(sym_patched)) {
+			void *data = NULL;
+			size_t size;
+
+			// bss doesn't have data
+			if (sym_patched->sec->data->d_buf)
+				data = sym_patched->sec->data->d_buf + sym_patched->offset;
+
+			if (is_section_symbol(sym_patched))
+				size = sec_size(sym_patched->sec);
+			else
+				size = sym_patched->len;
+
+			elf_add_data(elf, sec, data, size);
+		}
+	}
+
+	sym = elf_create_symbol(elf, sym_patched->name, sec, sym_patched->bind,
+				sym_patched->type, offset, sym_patched->len);
+
+sym_created:
+	sym_patched->clone = sym;
+	sym->clone = sym_patched;
+
+	return sym;
+}
+
+/*
+ * Copy a symbol to the output object, optionally including its data and
+ * relocations.
+ */
+static struct symbol *clone_symbol(struct elfs *e, struct symbol *sym_patched,
+				   bool data_too)
+{
+	if (sym_patched->clone)
+		return sym_patched->clone;
+
+	/* Clone prefix symbol */
+	if (data_too && is_function_symbol(sym_patched) && sym_patched->offset) {
+		struct symbol *pfx_patched;
+
+		pfx_patched = sec_prev_sym(sym_patched->sec, sym_patched);
+
+		if (pfx_patched && is_prefix_symbol(pfx_patched))
+			__clone_symbol(e->out, pfx_patched, true);
+	}
+
+	__clone_symbol(e->out, sym_patched, data_too);
+
+	if (data_too)
+		clone_sym_relocs(e, sym_patched);
+
+	return sym_patched->clone;
+}
+
+/*
+ * Copy all changed functions (and their dependencies) from the patched object
+ * to the output object.
+ */
+static void clone_changed_functions(struct elfs *e)
+{
+	struct symbol *sym_orig, *sym_patched;
+
+	/* Find changed functions */
+	for_each_sym(e->orig, sym_orig) {
+		if (!is_function_symbol(sym_orig) || is_prefix_symbol(sym_orig))
+			continue;
+
+		if (!sym_orig->twin)
+			continue;
+
+		if (sym_orig->checksum != sym_orig->twin->checksum) {
+			printf("%s: changed: %s\n", Objname, sym_orig->name);
+			sym_orig->twin->changed = 1;
+		}
+	}
+
+	/* Find added functions */
+	for_each_sym(e->patched, sym_patched) {
+		if (!is_function_symbol(sym_patched) || is_prefix_symbol(sym_patched))
+			continue;
+
+		if (!sym_patched->twin) {
+			sym_patched->added = 1;
+			printf("%s: added: %s\n", Objname, sym_patched->name);
+		}
+	}
+
+	/* Clone changed and added functions */
+	for_each_sym(e->patched, sym_patched) {
+		if (sym_patched->changed || sym_patched->added)
+			clone_symbol(e, sym_patched, true);
+	}
+}
+
+/*
+ * Determine whether a relocation should reference the section rather than the
+ * underlying symbol.
+ */
+static bool section_reference_needed(struct section *sec)
+{
+	/*
+	 * String symbols are zero-length and uncorrelated.  It's easier to
+	 * deal with them as section symbols.
+	 */
+	if (is_string_section(sec))
+		return true;
+
+	/*
+	 * .rodata has mostly anonymous data so there's no way to determine the
+	 * length of a needed reference.  just copy the whole section.
+	 */
+	if (!strcmp(sec->name, ".rodata"))
+		return true;
+
+	/* UBSAN anonymous data */
+	if (strstarts(sec->name, ".data..Lubsan"))
+		return true;
+
+	return false;
+}
+
+static bool is_reloc_allowed(struct reloc *reloc)
+{
+	return section_reference_needed(reloc->sym->sec) ==
+	       is_section_symbol(reloc->sym);
+}
+
+static struct export *find_export(struct elf *elf, struct symbol *sym)
+{
+	struct export *export;
+
+	hash_for_each_possible(exports, export, hash, str_hash(sym->name)) {
+		if (!strcmp(export->sym, sym->name))
+			return export;
+	}
+
+	return NULL;
+}
+
+static const char *__find_modname(struct elfs *e)
+{
+	struct section  *sec;
+	char *name;
+
+	sec = find_section_by_name(e->orig, ".modinfo");
+	if (!sec)
+		ERROR("missing .modinfo section");
+
+	name = memmem(sec->data, sec_size(sec), "\0name=", 6);
+	if (name)
+		return name + 6;
+
+	name = strdup(e->orig->name);
+	ERROR_ON(!name, "strdup");
+
+	for (char *c = name; *c; c++) {
+		if (*c == '/')
+			name = c + 1;
+		else if (*c == '-')
+			*c = '_';
+		else if (*c == '.') {
+			*c = '\0';
+			break;
+		}
+	}
+
+	return name;
+}
+
+/* Get the object's module name as defined by the kernel (and klp_object) */
+static const char *find_modname(struct elfs *e)
+{
+	static const char *modname;
+
+	if (modname)
+		return modname;
+
+	modname = __find_modname(e);
+	return modname;
+}
+
+static bool klp_reloc_needed(struct elf *elf_patched, struct reloc *reloc_patched)
+{
+	struct symbol *sym_patched = reloc_patched->sym;
+	struct export *export;
+
+	/* no external symbol to reference */
+	if (dont_correlate(sym_patched))
+		return false;
+
+	/* For cloned functions, a regular reloc will do. */
+	if (sym_patched->changed || sym_patched->added)
+		return false;
+
+	/*
+	 * 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
+	 * allowed to be loaded before any modules it depends on.
+	 *
+	 * If exported by vmlinux, a normal reloc will work.
+	 */
+	export = find_export(elf_patched, sym_patched);
+	if (export)
+		return strcmp(export->mod, "vmlinux");
+
+	if (!sym_patched->twin) {
+		/*
+		 * Presumably the symbol and its reference were added by the
+		 * patch.  The symbol could be defined in this .o or in another
+		 * .o in the patch module.
+		 *
+		 * This should be checked *after* the exports, for the case
+		 * where the patch adds a reference to an exported symbol.
+		 */
+		return false;
+	}
+
+	/* Unexported symbol which lives in the original vmlinux or module. */
+	return true;
+}
+
+static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc)
+{
+	struct symbol *sym = reloc->sym;
+	struct section *sec = sym->sec;
+
+	if (!sec->sym)
+		elf_create_section_symbol(elf, sec);
+
+	reloc->sym = sec->sym;
+	set_reloc_sym(elf, reloc, sym->idx);
+	set_reloc_addend(elf, reloc, sym->offset + reloc_addend(reloc));
+	return 0;
+}
+
+static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc)
+{
+	struct symbol *sym = reloc->sym;
+	struct section *sec = sym->sec;
+
+	/* If the symbol has a dedicated section, it's easy to find */
+	sym = find_symbol_by_offset(sec, 0);
+	if (sym && sym->len == sec_size(sec))
+		goto found_sym;
+
+	/* No dedicated section; find the symbol manually */
+	sym = find_symbol_containing(sec, arch_adjusted_addend(reloc));
+	if (!sym) {
+		/*
+		 * This can happen for special section references to weak code
+		 * whose symbol has been stripped by the linker.
+		 */
+		return -1;
+	}
+
+found_sym:
+	reloc->sym = sym;
+	set_reloc_sym(elf, reloc, sym->idx);
+	set_reloc_addend(elf, reloc, reloc_addend(reloc) - sym->offset);
+	return 0;
+}
+
+/*
+ * Convert a relocation symbol reference to the needed format: either a section
+ * symbol or the underlying symbol itself.
+ */
+static int convert_reloc_sym(struct elf *elf, struct reloc *reloc)
+{
+	if (is_reloc_allowed(reloc))
+		return 0;
+
+	if (section_reference_needed(reloc->sym->sec))
+		return convert_reloc_sym_to_secsym(elf, reloc);
+	else
+		return convert_reloc_secsym_to_sym(elf, reloc);
+}
+
+/*
+ * Convert a regular relocation to a klp relocation (sort of).
+ */
+static void clone_reloc_klp(struct elfs *e, struct reloc *reloc_patched,
+			    struct section *sec, unsigned long offset,
+			    struct export *export)
+{
+	struct symbol *sym_patched = reloc_patched->sym;
+	s64 addend = reloc_addend(reloc_patched);
+	const char *sym_modname, *sym_orig_name;
+	static struct section *klp_relocs;
+	struct symbol *sym, *klp_sym;
+	unsigned long klp_reloc_off;
+	char sym_name[SYM_NAME_LEN];
+	struct klp_reloc klp_reloc;
+	unsigned long sympos;
+
+	if (!sym_patched->twin)
+		ERROR("unexpected klp reloc for new symbol %s", sym_patched->name);
+
+	/*
+	 * First, copy the original reloc and symbol as-is so objtool will be
+	 * able to process the code correctly during module link time.  This
+	 * relocation will be disabled later by cmd_klp_link().
+	 */
+
+	sym = sym_patched->clone;
+	if (!sym) {
+		sym = elf_create_klp_symbol(e->out, sym_patched->name,
+					    sym_patched->bind,
+					    sym_patched->type);
+		sym_patched->clone = sym;
+		sym->clone = sym_patched;
+	}
+
+	elf_create_reloc(e->out, sec, offset, sym, addend, reloc_type(reloc_patched));
+
+	/*
+	 * Second, create the klp symbol.
+	 */
+
+	if (export) {
+		sym_modname = export->mod;
+		sym_orig_name = export->sym;
+		sympos = 0;
+	} else {
+		sym_modname = find_modname(e);
+		sym_orig_name = sym_patched->twin->name;
+		sympos = find_sympos(e->orig, sym_patched->twin);
+	}
+
+	/* 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);
+
+	klp_sym = find_symbol_by_name(e->out, sym_name);
+	if (!klp_sym)
+		klp_sym = elf_create_klp_symbol(e->out, sym_name,
+						sym_patched->bind,
+						sym_patched->type);
+
+	/*
+	 * Third, create the .klp_relocs entry, which will be converted to an
+	 * actual klp rela by cmd_klp_link().
+	 *
+	 * This intermediate step is necessary to prevent corruption by the
+	 * linker, which doesn't know how to properly handle two rela sections
+	 * applying to the same base section.
+	 */
+
+	if (!klp_relocs)
+		klp_relocs = elf_create_section(e->out, KLP_RELOCS_SEC, 0,
+						0, SHT_PROGBITS, 8, SHF_ALLOC);
+
+	klp_reloc_off = sec_size(klp_relocs);
+	memset(&klp_reloc, 0, sizeof(klp_reloc));
+
+	klp_reloc.type = reloc_type(reloc_patched);
+	elf_add_data(e->out, klp_relocs, &klp_reloc, sizeof(klp_reloc));
+
+	/* klp_reloc.offset */
+	if (!sec->sym)
+		elf_create_section_symbol(e->out, sec);
+	elf_create_reloc(e->out, klp_relocs,
+			 klp_reloc_off + offsetof(struct klp_reloc, offset),
+			 sec->sym, offset, R_ABS64);
+
+	/* klp_reloc.sym */
+	elf_create_reloc(e->out, klp_relocs,
+			 klp_reloc_off + offsetof(struct klp_reloc, sym),
+			 klp_sym, addend, R_ABS64);
+
+}
+
+/* Copy a reloc and its symbol to the output object */
+static void clone_reloc(struct elfs *e, struct reloc *reloc_patched,
+			struct section *sec, unsigned long offset)
+{
+	struct symbol *sym_patched = reloc_patched->sym;
+	struct export *export = find_export(e->out, sym_patched);
+	s64 addend = reloc_addend(reloc_patched);
+	struct symbol *sym;
+
+	if (!is_reloc_allowed(reloc_patched))
+		ERROR_FUNC(reloc_patched->sec->base, reloc_offset(reloc_patched),
+			   "missing symbol for reference to %s+0x%lx",
+			   sym_patched->name, addend);
+
+	if (klp_reloc_needed(e->patched, reloc_patched)) {
+		clone_reloc_klp(e, reloc_patched, sec, offset, export);
+		return;
+	}
+
+	sym = clone_symbol(e, sym_patched, !export);
+
+	if (is_string_section(sym_patched->sec))
+		addend = elf_add_string(e->out, sym->sec,
+					reloc_patched->sym->sec->data->d_buf + addend);
+
+	elf_create_reloc(e->out, sec, offset, sym, addend, reloc_type(reloc_patched));
+}
+
+/* Copy all relocs needed for a symbol's contents */
+static void clone_sym_relocs(struct elfs *e, struct symbol *sym_patched)
+{
+	struct section *rsec_patched = sym_patched->sec->rsec;
+	struct reloc *reloc_patched;
+	unsigned long start, end;
+	struct symbol *sym;
+
+	sym = sym_patched->clone;
+	if (!sym)
+		ERROR("no clone for %s", sym_patched->name);
+
+	if (!rsec_patched)
+		return;
+
+	if (!is_section_symbol(sym_patched) && !sym_patched->len)
+		return;
+
+	if (is_string_section(sym_patched->sec))
+		return;
+
+	if (is_section_symbol(sym_patched)) {
+		start = 0;
+		end = sec_size(sym_patched->sec);
+	} else {
+		start = sym_patched->offset;
+		end = start + sym_patched->len;
+	}
+
+	for_each_reloc(rsec_patched, reloc_patched) {
+		unsigned long offset;
+
+		if (reloc_offset(reloc_patched) < start ||
+		    reloc_offset(reloc_patched) >= end)
+			continue;
+
+		convert_reloc_sym(e->patched, reloc_patched);
+
+		offset = sym->offset + (reloc_offset(reloc_patched) - sym_patched->offset);
+
+		clone_reloc(e, reloc_patched, sym->sec, offset);
+	}
+}
+
+/* Keep a special section entry if it references an included function */
+static bool should_keep_special_sym(struct elf *elf, struct symbol *sym)
+{
+	struct reloc *reloc;
+
+	if (is_section_symbol(sym))
+		return false;
+
+	for_each_reloc(sym->sec->rsec, reloc) {
+		unsigned long reloc_off = reloc_offset(reloc);
+
+		if (convert_reloc_sym(elf, reloc))
+			continue;
+
+		if (reloc_off >= sym->offset && reloc_off < sym->offset + sym->len &&
+		    is_function_symbol(reloc->sym) &&
+		    (reloc->sym->changed || reloc->sym->added))
+			return true;
+	}
+
+	return false;
+}
+
+static unsigned int reloc_size(struct reloc *reloc)
+{
+	switch (reloc_type(reloc)) {
+
+	case R_X86_64_PC32:
+	case R_X86_64_32:
+		return 4;
+
+	case R_X86_64_64:
+	case R_X86_64_PC64:
+		return 8;
+
+	default:
+		ERROR("unknown reloc type");
+	}
+}
+
+static void clone_special_section(struct elfs *e, struct section *sec_patched)
+{
+	unsigned long off_patched, off_out;
+	struct reloc *reloc_patched;
+	struct symbol *sym_patched;
+	bool has_syms = false;
+	struct section *sec;
+
+	/*
+	 * Some special sections are composed of an array of structs, where
+	 * each array entry has its own fake symbol denoting its location and
+	 * size.  Copy all entries (both data and relocs) referencing an
+	 * included function.
+	 */
+	off_patched = 0;
+	sec_for_each_sym(sec_patched, sym_patched) {
+		if (!is_object_symbol(sym_patched))
+			continue;
+		has_syms = true;
+
+		if (off_patched != sym_patched->offset)
+			ERROR_FUNC(sec_patched, off_patched, "special section symbol gap");
+
+		off_patched = sym_patched->offset + sym_patched->len;
+
+		if (!should_keep_special_sym(e->patched, sym_patched))
+			continue;
+
+		clone_symbol(e, sym_patched, true);
+	}
+
+	if (has_syms)
+		return;
+
+	/*
+	 * Some special sections are just a simple array of pointers.  Copy all
+	 * relocs referencing included functions.
+	 */
+	off_patched = 0;
+	off_out = 0;
+	sec = NULL;
+	for_each_reloc(sec_patched->rsec, reloc_patched) {
+		unsigned int rel_size = reloc_size(reloc_patched);
+
+		if (off_patched != reloc_offset(reloc_patched))
+			ERROR("special section reloc gap - does it need fake symbols?");
+
+		off_patched += rel_size;
+
+		if (convert_reloc_sym(e->patched, reloc_patched))
+			continue;
+
+		if (!reloc_patched->sym->changed && !reloc_patched->sym->added)
+			continue;
+
+		if (!sec) {
+			sec = find_section_by_name(e->out, sec_patched->name);
+			if (sec)
+				ERROR("why does %s already exist?", sec_patched->name);
+
+			sec = elf_create_section(e->out, sec_patched->name, 0,
+						 sec_patched->sh.sh_entsize,
+						 sec_patched->sh.sh_type,
+						 sec_patched->sh.sh_addralign,
+						 sec_patched->sh.sh_flags);
+		}
+
+		elf_add_data(e->out, sec, NULL, rel_size);
+		clone_reloc(e, reloc_patched, sec, off_out);
+		off_out += rel_size;
+	}
+}
+
+/* Extract only the needed bits from special sections */
+static void clone_special_sections(struct elfs *e)
+{
+	struct section *sec_patched;
+
+	for_each_sec(e->patched, sec_patched) {
+		if (is_special_section(sec_patched))
+			clone_special_section(e, sec_patched);
+	}
+}
+
+/*
+ * Create __klp_objects and __klp_funcs sections which are intermediate
+ * sections provided as input to the patch module's init code (module.c) for
+ * building the 'struct klp_patch' needed for the livepatch API.
+ */
+static void create_klp_sections(struct elfs *e)
+{
+	size_t obj_size  = sizeof(struct klp_object_ext);
+	size_t func_size = sizeof(struct klp_func_ext);
+	struct section *obj_sec, *funcs_sec, *str_sec;
+	struct symbol *funcs_sym, *str_sym, *sym;
+	const char *modname = find_modname(e);
+	char sym_name[SYM_NAME_LEN];
+	unsigned int nr_funcs = 0;
+	void *obj_data;
+	s64 addend;
+
+	obj_sec  = elf_create_section_pair(e->out, KLP_OBJECTS_SEC, obj_size, 0, 0);
+
+	funcs_sec = elf_create_section_pair(e->out, KLP_FUNCS_SEC, func_size, 0, 0);
+	funcs_sym = elf_create_section_symbol(e->out, funcs_sec);
+
+	str_sec = elf_create_section(e->out, KLP_STRINGS_SEC, 0, 0,
+				     SHT_PROGBITS, 1,
+				     SHF_ALLOC | SHF_STRINGS | SHF_MERGE);
+	elf_add_string(e->out, str_sec, "");
+	str_sym = elf_create_section_symbol(e->out, str_sec);
+
+	/* allocate klp_object_ext */
+	obj_data = elf_add_data(e->out, obj_sec, NULL, obj_size);
+
+	/* klp_object_ext.name */
+	if (strcmp(modname, "vmlinux")) {
+		addend = elf_add_string(e->out, str_sec, modname);
+		elf_create_reloc(e->out, obj_sec,
+				 offsetof(struct klp_object_ext, name),
+				 str_sym, addend, R_ABS64);
+	}
+
+	/* klp_object_ext.funcs */
+	elf_create_reloc(e->out, obj_sec, offsetof(struct klp_object_ext, funcs),
+			 funcs_sym, 0, R_ABS64);
+
+	for_each_sym(e->out, sym) {
+		unsigned long offset = nr_funcs * func_size;
+		unsigned long sympos;
+		void *func_data;
+
+		if (!sym->clone || !sym->clone->changed || !is_function_symbol(sym))
+			continue;
+
+		/* allocate klp_func_ext */
+		func_data = elf_add_data(e->out, funcs_sec, NULL, func_size);
+
+		/* klp_func_ext.old_name */
+		addend = elf_add_string(e->out, str_sec, sym->clone->twin->name);
+		elf_create_reloc(e->out, funcs_sec,
+				 offset + offsetof(struct klp_func_ext, old_name),
+				 str_sym, addend, R_ABS64);
+
+		/* klp_func_ext.new_func */
+		elf_create_reloc(e->out, funcs_sec,
+				 offset + offsetof(struct klp_func_ext, new_func),
+				 sym, 0, R_ABS64);
+
+		/* klp_func_ext.sympos */
+		BUILD_BUG_ON(sizeof(sympos) != sizeof_field(struct klp_func_ext, sympos));
+		sympos = find_sympos(e->orig, sym->clone->twin);
+		memcpy(func_data + offsetof(struct klp_func_ext, sympos), &sympos,
+		       sizeof_field(struct klp_func_ext, sympos));
+
+		nr_funcs++;
+	}
+
+	/* klp_object_ext.nr_funcs */
+	BUILD_BUG_ON(sizeof(nr_funcs) != sizeof_field(struct klp_object_ext, nr_funcs));
+	memcpy(obj_data + offsetof(struct klp_object_ext, nr_funcs), &nr_funcs,
+	       sizeof_field(struct klp_object_ext, nr_funcs));
+
+	/*
+	 * Find callback pointers created by KLP_PRE_PATCH_CALLBACK() and
+	 * friends, and add them to the klp object.
+	 */
+
+	snprintf(sym_name, SYM_NAME_LEN, KLP_PRE_PATCH_PREFIX "%s", modname);
+	sym = find_symbol_by_name(e->out, sym_name);
+	if (sym) {
+		struct reloc *reloc;
+
+		reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset);
+
+		elf_create_reloc(e->out, obj_sec,
+				 offsetof(struct klp_object_ext, callbacks) +
+				 offsetof(struct klp_callbacks, pre_patch),
+				 reloc->sym, reloc_addend(reloc), R_ABS64);
+	}
+
+	snprintf(sym_name, SYM_NAME_LEN, KLP_POST_PATCH_PREFIX "%s", modname);
+	sym = find_symbol_by_name(e->out, sym_name);
+	if (sym) {
+		struct reloc *reloc;
+
+		reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset);
+
+		elf_create_reloc(e->out, obj_sec,
+				 offsetof(struct klp_object_ext, callbacks) +
+				 offsetof(struct klp_callbacks, post_patch),
+				 reloc->sym, reloc_addend(reloc), R_ABS64);
+	}
+
+	snprintf(sym_name, SYM_NAME_LEN, KLP_PRE_UNPATCH_PREFIX "%s", modname);
+	sym = find_symbol_by_name(e->out, sym_name);
+	if (sym) {
+		struct reloc *reloc;
+
+		reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset);
+
+		elf_create_reloc(e->out, obj_sec,
+				 offsetof(struct klp_object_ext, callbacks) +
+				 offsetof(struct klp_callbacks, pre_unpatch),
+				 reloc->sym, reloc_addend(reloc), R_ABS64);
+	}
+
+	snprintf(sym_name, SYM_NAME_LEN, KLP_POST_UNPATCH_PREFIX "%s", modname);
+	sym = find_symbol_by_name(e->out, sym_name);
+	if (sym) {
+		struct reloc *reloc;
+
+		reloc = find_reloc_by_dest(e->out, sym->sec, sym->offset);
+
+		elf_create_reloc(e->out, obj_sec,
+				 offsetof(struct klp_object_ext, callbacks) +
+				 offsetof(struct klp_callbacks, post_unpatch),
+				 reloc->sym, reloc_addend(reloc), R_ABS64);
+	}
+}
+
+int cmd_klp_diff(int argc, const char **argv)
+{
+	struct elf *elf_orig, *elf_patched, *elf_out;
+	struct elfs e;
+
+	argc--;
+	argv++;
+
+	if (argc != 3) {
+		fprintf(stderr, "usage: objtool diff <in1.o> <in2.o> <out.o>\n");
+		return -1;
+	}
+
+	elf_orig = elf_open_read(argv[0], O_RDONLY);
+	elf_patched = elf_open_read(argv[1], O_RDONLY);
+
+	Objname = basename(Objname);
+
+	read_exports();
+
+	read_sym_checksums(elf_orig);
+	read_sym_checksums(elf_patched);
+
+	correlate_symbols(elf_orig, elf_patched);
+
+	elf_out = elf_create_file(&elf_orig->ehdr, argv[2]);
+
+	e.orig		= elf_orig;
+	e.patched	= elf_patched;
+	e.out		= elf_out;
+
+	clone_changed_functions(&e);
+
+	clone_special_sections(&e);
+
+	create_klp_sections(&e);
+
+	elf_write(elf_out);
+	return 0;
+}
+
diff --git a/tools/objtool/klp-link.c b/tools/objtool/klp-link.c
new file mode 100644
index 000000000000..0b42a79c8215
--- /dev/null
+++ b/tools/objtool/klp-link.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+#include <fcntl.h>
+#include <gelf.h>
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+#include <objtool/klp.h>
+#include <linux/livepatch_ext.h>
+
+/*
+ * 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.
+ */
+int cmd_klp_link(int argc, const char **argv)
+{
+	struct section *symtab, *klp_relocs;
+	struct elf *elf;
+
+	argc--;
+	argv++;
+
+	if (argc != 1) {
+		fprintf(stderr, "%d\n", argc);
+		fprintf(stderr, "usage: objtool link <file.ko>\n");
+		return -1;
+	}
+
+	elf = elf_open_read(argv[0], O_RDWR);
+
+	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");
+
+	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 entry:
+		 */
+
+		/* klp_reloc.sec_offset */
+		reloc = find_reloc_by_dest(elf, klp_relocs,
+					   klp_reloc_off + offsetof(struct klp_reloc, offset));
+		ERROR_ON(!reloc, "malformed " KLP_RELOCS_SEC " section");
+
+		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));
+		ERROR_ON(!reloc, "malformed " KLP_RELOCS_SEC " section");
+
+		klp_sym = reloc->sym;
+		addend = reloc_addend(reloc);
+
+		/* symbol format: .klp.sym.modname.sym_name,sympos */
+		sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname);
+
+		/*
+		 * Create klp reloc:
+		 */
+
+		/* section format: .klp.rela.sec_objname.section_name */
+		snprintf(rsec_name, SEC_NAME_LEN, KLP_RELOC_SEC_PREFIX "%s.%s",
+			 sym_modname, sec->name);
+		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);
+
+			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;
+		elf_create_reloc(elf, sec, offset, klp_sym, addend, klp_reloc->type);
+		sec->rsec = tmp;
+
+		klp_sym->sym.st_shndx = SHN_LIVEPATCH;
+		gelf_update_sym(symtab->data, klp_sym->idx, &klp_sym->sym);
+
+		/*
+		 * Disable 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);
+		gelf_update_sym(symtab->data, sym->idx, &sym->sym);
+	}
+
+	elf_write(elf);
+
+	return 0;
+}
diff --git a/tools/objtool/klp.c b/tools/objtool/klp.c
new file mode 100644
index 000000000000..fc871108060e
--- /dev/null
+++ b/tools/objtool/klp.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 Josh Poimboeuf <jpoimboe@...nel.org>
+ */
+
+#include <subcmd/parse-options.h>
+#include <string.h>
+#include <stdlib.h>
+#include <objtool/builtin.h>
+#include <objtool/objtool.h>
+#include <objtool/klp.h>
+
+struct subcmd {
+	const char *name;
+	const char *description;
+	int (*fn)(int, const char **);
+};
+
+static struct subcmd subcmds[] = {
+	{ "diff",		"Generate binary diff of two object files",			cmd_klp_diff, },
+	{ "link",		"Finalize klp symbols/relocations after module linking",	cmd_klp_link, },
+};
+
+static void cmd_klp_usage(void)
+{
+	fprintf(stderr, "usage: objtool klp <subcommand> [<options>]\n\n");
+	fprintf(stderr, "Subcommands:\n");
+
+	for (int i = 0; i < ARRAY_SIZE(subcmds); i++) {
+		struct subcmd *cmd = &subcmds[i];
+
+		fprintf(stderr,"  %s\t%s\n", cmd->name, cmd->description);
+	}
+
+	exit(1);
+}
+
+int cmd_klp(int argc, const char **argv)
+{
+	argc--;
+	argv++;
+
+	if (!argc)
+		cmd_klp_usage();
+
+	if (argc) {
+		for (int i = 0; i < ARRAY_SIZE(subcmds); i++) {
+			struct subcmd *cmd = &subcmds[i];
+
+			if (!strcmp(cmd->name, argv[0]))
+				return cmd->fn(argc, argv);
+		}
+	}
+
+	cmd_klp_usage();
+	return 0;
+}
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
index 06f7e518b8a7..75ff32ab0368 100644
--- a/tools/objtool/objtool.c
+++ b/tools/objtool/objtool.c
@@ -122,5 +122,11 @@ int main(int argc, const char **argv)
 	exec_cmd_init("objtool", UNUSED, UNUSED, UNUSED);
 	pager_init(UNUSED);
 
+	if (argc > 1 && !strcmp(argv[1], "klp")) {
+		argc--;
+		argv++;
+		return cmd_klp(argc, argv);
+	}
+
 	return objtool_run(argc, argv);
 }
diff --git a/tools/objtool/sync-check.sh b/tools/objtool/sync-check.sh
index 81d120d05442..873ce93f9993 100755
--- a/tools/objtool/sync-check.sh
+++ b/tools/objtool/sync-check.sh
@@ -16,6 +16,7 @@ arch/x86/include/asm/orc_types.h
 arch/x86/include/asm/emulate_prefix.h
 arch/x86/lib/x86-opcode-map.txt
 arch/x86/tools/gen-insn-attr-x86.awk
+include/linux/livepatch_ext.h
 include/linux/static_call_types.h
 "
 
diff --git a/tools/objtool/weak.c b/tools/objtool/weak.c
index 426fdf0b7548..7e1858885fd6 100644
--- a/tools/objtool/weak.c
+++ b/tools/objtool/weak.c
@@ -8,6 +8,8 @@
 #include <stdbool.h>
 #include <errno.h>
 #include <objtool/objtool.h>
+#include <objtool/arch.h>
+#include <objtool/builtin.h>
 
 #define UNSUPPORTED(name)						\
 ({									\
@@ -24,3 +26,8 @@ int __weak orc_create(struct objtool_file *file)
 {
 	UNSUPPORTED("ORC");
 }
+
+int __weak cmd_klp(int argc, const char **argv)
+{
+	UNSUPPORTED("klp");
+}
-- 
2.45.2


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ