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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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 <jpoim...@kernel.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


Reply via email to