Live Patching
 help / color / mirror / Atom feed
* [RFC 0/4] klp-build: simple OOT module support
@ 2026-05-12 22:10 Joe Lawrence
  2026-05-12 22:10 ` [RFC 1/4] objtool/klp: add --symvers option to klp diff Joe Lawrence
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Joe Lawrence @ 2026-05-12 22:10 UTC (permalink / raw)
  To: live-patching; +Cc: Josh Poimboeuf, Song Liu, Miroslav Benes, Petr Mladek

This patchset introduces support for patching basic out-of-tree (OOT)
modules.  The primary motivation is to streamline testing for objtool klp
diff by providing a flexible and stable environment.

While much of the objtool klp diff logic can be validated using standard
in-tree modules (avoiding the overhead of full kernel rebuilds), OOT
support provides significant value for reproducing and fixing specific
kernel code patterns.  Standard in-tree drivers are subject to frequent
refactoring and API updates, making them unreliable for producing the
consistent binary patterns required for stable testing.  While dedicated
test modules could be introduced into the kernel tree to ensure
stability, starting with OOT modules allows for faster iteration before
committing to a permanent in-tree landing spot.

For an example, I have inlined a module below used to verify Josh's
latest commit ("objtool/klp: Rewrite symbol correlation algorithm")
[1].  This specific test case confirms that his patch resolves the
thinLTO ambiguity issue originally reported by Song Liu in February [2].

  [1] https://lore.kernel.org/live-patching/cq5uytz6edj75w53f2eubypvqm66hgh4eag7ec2vgqjefzzqts@lcnvt7fcrtmd/T/#mbfaf71b314b6600424f9c5504c415a2e3a87ade3
  [2] https://lore.kernel.org/live-patching/20260226005436.379303-9-song@kernel.org

To make full use of the example OOT module, I have additional test-
harness code that automates fetching pre-built object files, integrating
with kselftests, etc.  I chose to spin out this basic OOT support
first since it mostly stands alone and may have other helpful contexts.

-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8--

==> Kbuild <==
obj-m := klp_test_ambig.o
klp_test_ambig-y := klp_test_ambig_a.o klp_test_ambig_b.o klp_test_ambig_c.o

==> klp_test_ambig_a.c <==
#include <linux/module.h>
#include <linux/kernel.h>
#include "klp_test_ambig.h"

static noinline int __helper(int x, int len)
{
	int i, sum = x;

	for (i = 0; i < len; i++)
		sum += i + 5;
	if (sum > 1000)
		sum = 0;
	return sum;
}

static int value_a;

int klp_test_ambig_func_a(int x)
{
	value_a = __helper(value_a, x);
	return value_a;
}
EXPORT_SYMBOL_GPL(klp_test_ambig_func_a);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ThinLTO demangled ambiguity test module");

==> klp_test_ambig_b.c <==
#include <linux/module.h>
#include <linux/kernel.h>
#include "klp_test_ambig.h"

static noinline int __helper(int x, int len)
{
	int i, sum = x;

	for (i = 0; i < len; i++)
		sum += i + 10;
	if (sum > 1000)
		sum = 0;
	return sum;
}

static int value_b;

int klp_test_ambig_func_b(int x)
{
	value_b = __helper(value_b, x);
	return value_b;
}
EXPORT_SYMBOL_GPL(klp_test_ambig_func_b);

==> klp_test_ambig_c.c <==
#include <linux/module.h>
#include <linux/kernel.h>
#include "klp_test_ambig.h"

static int value_c;

int klp_test_ambig_func_c(int x)
{
	value_c = klp_test_ambig_func_a(x) + klp_test_ambig_func_b(x);
	return value_c;
}
EXPORT_SYMBOL_GPL(klp_test_ambig_func_c);

==> klp_test_ambig.h <==
#ifndef _KLP_TEST_AMBIG_H
#define _KLP_TEST_AMBIG_H

int klp_test_ambig_func_a(int x);
int klp_test_ambig_func_b(int x);
int klp_test_ambig_func_c(int x);

#endif


==> 0001-thin-lto-demangled-ambiguity.patch <==
From 2d208e686739b1ccccfe385e837b5a4a04a9526f Mon Sep 17 00:00:00 2001
From: klp-build-test <test@example.com>
Date: Sat, 28 Mar 2026 13:55:56 +0000
Subject: [PATCH] thin-lto-demangled-ambiguity

ThinLTO two __helper.llvm.* symbols with disjoint hashes; objtool correlates

---
 klp_test_ambig_a.c | 2 +-
 klp_test_ambig_b.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/klp_test_ambig_a.c b/klp_test_ambig_a.c
index cc4db70..4ee26a5 100644
--- a/klp_test_ambig_a.c
+++ b/klp_test_ambig_a.c
@@ -7,7 +7,7 @@ static noinline int __helper(int x, int len)
 	int i, sum = x;
 
 	for (i = 0; i < len; i++)
-		sum += i + 5;
+		sum += i * 2 + 5;
 	if (sum > 1000)
 		sum = 0;
 	return sum;
diff --git a/klp_test_ambig_b.c b/klp_test_ambig_b.c
index ca114b0..1b7ce1a 100644
--- a/klp_test_ambig_b.c
+++ b/klp_test_ambig_b.c
@@ -7,7 +7,7 @@ static noinline int __helper(int x, int len)
 	int i, sum = x;
 
 	for (i = 0; i < len; i++)
-		sum += i + 10;
+		sum += i * 2 + 10;
 	if (sum > 1000)
 		sum = 0;
 	return sum;
-- 
2.53.0

-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8--

Applies on top of:
tree:   git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git
branch: klp-build-arm64
commit: fffce0ac08e0 ("klp-build: Add arm64 syscall patching macro")

Joe Lawrence (4):
  objtool/klp: add --symvers option to klp diff
  objtool/klp: allow special section entry size overrides
  objtool/klp: add --arch option to display target architecture
  livepatch/klp-build: add basic out-of-tree module patching support

 scripts/livepatch/klp-build | 90 ++++++++++++++++++++++++++++---------
 tools/objtool/Makefile      |  3 +-
 tools/objtool/klp-diff.c    | 49 +++++++++++++++++---
 3 files changed, 115 insertions(+), 27 deletions(-)

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 1/4] objtool/klp: add --symvers option to klp diff
  2026-05-12 22:10 [RFC 0/4] klp-build: simple OOT module support Joe Lawrence
@ 2026-05-12 22:10 ` Joe Lawrence
  2026-05-12 22:11 ` [RFC 2/4] objtool/klp: allow special section entry size overrides Joe Lawrence
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Joe Lawrence @ 2026-05-12 22:10 UTC (permalink / raw)
  To: live-patching; +Cc: Josh Poimboeuf, Song Liu, Miroslav Benes, Petr Mladek

Add a --symvers (-s) option that accepts a path to Module.symvers.  When
provided, it replaces the default "Module.symvers" filename used by the
auto-detection logic.

This decoupling helps to enable scenarios like out-of-tree module
patching and unit testing of klp-diff, where object files /
Module.symvers live outside the kernel tree.

The existing auto-detection behavior (try cwd, then top_level_dir()
fallback) is preserved regardless of whether --symvers is specified.

Assisted-by: Cursor:claude-4.6-opus
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
---
 tools/objtool/klp-diff.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index b9624bd9439b..bd8d64f2f3f6 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -34,6 +34,7 @@ struct export {
 	char *mod, *sym;
 };
 
+static const char *symvers_path = "Module.symvers";
 bool debug, debug_correlate, debug_clone;
 int indent;
 
@@ -47,6 +48,7 @@ static const struct option klp_diff_options[] = {
 	OPT_BOOLEAN('d', "debug", &debug, "enable all debug output"),
 	OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"),
 	OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"),
+	OPT_STRING('s', "symvers", &symvers_path, "path", "path to Module.symvers (default: Module.symvers)"),
 	OPT_END(),
 };
 
@@ -86,16 +88,15 @@ static char *escape_str(const char *orig)
 
 static int read_exports(void)
 {
-	const char *symvers = "Module.symvers";
 	char line[1024], *path = NULL;
 	unsigned int line_num = 1;
 	FILE *file;
 
-	file = fopen(symvers, "r");
+	file = fopen(symvers_path, "r");
 	if (!file) {
-		path = top_level_dir(symvers);
+		path = top_level_dir(symvers_path);
 		if (!path) {
-			ERROR("can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers);
+			ERROR("can't open '%s', \"objtool diff\" should be run from the kernel tree", symvers_path);
 			return -1;
 		}
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 2/4] objtool/klp: allow special section entry size overrides
  2026-05-12 22:10 [RFC 0/4] klp-build: simple OOT module support Joe Lawrence
  2026-05-12 22:10 ` [RFC 1/4] objtool/klp: add --symvers option to klp diff Joe Lawrence
@ 2026-05-12 22:11 ` Joe Lawrence
  2026-05-12 22:11 ` [RFC 3/4] objtool/klp: add --arch option to display target architecture Joe Lawrence
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Joe Lawrence @ 2026-05-12 22:11 UTC (permalink / raw)
  To: live-patching; +Cc: Josh Poimboeuf, Song Liu, Miroslav Benes, Petr Mladek

Special section entry sizes (ALT_ENTRY_SIZE, JUMP_ENTRY_SIZE,
EX_ENTRY_SIZE) are built into objtool from arch-specific headers.
When processing cached unit test objects that were built from a
different kernel version, these compiled-in sizes may not match the
objects' actual entry sizes, causing create_fake_symbols() to
incorrectly parse special sections.

Allow the user to override the compiled-in defaults via environment
variables of the same name.  When unset, behavior is unchanged.  This
will enable a klp-diff unit test runner to pass the correct entry sizes
from test metadata.

Assisted-by: Cursor:claude-4.6-opus
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
---
 tools/objtool/klp-diff.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index bd8d64f2f3f6..ebe4a2a087ca 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -1749,6 +1749,22 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym)
 
 }
 
+static unsigned int entry_size_from_env(const char *name, unsigned int def)
+{
+	const char *str = getenv(name);
+	char *end;
+	unsigned long val;
+
+	if (!str)
+		return def;
+
+	val = strtoul(str, &end, 10);
+	if (*end || !val)
+		return def;
+
+	return val;
+}
+
 static int create_fake_symbol(struct elf *elf, struct section *sec,
 			      unsigned long offset, size_t size)
 {
@@ -1871,6 +1887,21 @@ static int create_fake_symbols(struct elf *elf)
 		}
 
 		entry_size = sec->sh.sh_entsize;
+
+		/*
+		 * Some special sections have multiple relocs per entry,
+		 * so the reloc-based heuristic below doesn't work.  Use
+		 * the arch-defined entry sizes for known special sections.
+		 */
+		if (!entry_size) {
+			if (!strcmp(sec->name, ".altinstructions"))
+				entry_size = entry_size_from_env("ALT_ENTRY_SIZE", ALT_ENTRY_SIZE);
+			else if (!strcmp(sec->name, "__jump_table"))
+				entry_size = entry_size_from_env("JUMP_ENTRY_SIZE", JUMP_ENTRY_SIZE);
+			else if (!strcmp(sec->name, "__ex_table"))
+				entry_size = entry_size_from_env("EX_ENTRY_SIZE", EX_ENTRY_SIZE);
+		}
+
 		if (!entry_size) {
 			entry_size = arch_reloc_size(sec->rsec->relocs);
 			if (sec_size(sec) != entry_size * sec_num_entries(sec->rsec)) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 3/4] objtool/klp: add --arch option to display target architecture
  2026-05-12 22:10 [RFC 0/4] klp-build: simple OOT module support Joe Lawrence
  2026-05-12 22:10 ` [RFC 1/4] objtool/klp: add --symvers option to klp diff Joe Lawrence
  2026-05-12 22:11 ` [RFC 2/4] objtool/klp: allow special section entry size overrides Joe Lawrence
@ 2026-05-12 22:11 ` Joe Lawrence
  2026-05-12 22:11 ` [RFC 4/4] livepatch/klp-build: add basic out-of-tree module patching support Joe Lawrence
  2026-05-12 23:30 ` [RFC 0/4] klp-build: simple OOT module support Song Liu
  4 siblings, 0 replies; 6+ messages in thread
From: Joe Lawrence @ 2026-05-12 22:11 UTC (permalink / raw)
  To: live-patching; +Cc: Josh Poimboeuf, Song Liu, Miroslav Benes, Petr Mladek

objtool is built for a specific architecture determined by SRCARCH at
compile time.  However, the binary currently provides no way to report
this target architecture, making it difficult for scripts or test suites
to verify compatibility before processing object files.

Pass SRCARCH as a preprocessor define and add a --arch (-a) option to
klp diff that prints the target architecture and exits:

  $ objtool klp diff --arch
  x86

This is mutually exclusive with normal operation: when --arch is given,
all other options and arguments are ignored.

Assisted-by: Cursor:claude-4.6-opus
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
---
 tools/objtool/Makefile   | 3 ++-
 tools/objtool/klp-diff.c | 9 ++++++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 94aabeee9736..79694444053c 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -65,7 +65,8 @@ INCLUDES := -I$(srctree)/tools/include \
 	    -I$(srctree)/tools/lib
 
 OBJTOOL_CFLAGS  := -std=gnu11 -fomit-frame-pointer -O2 -g $(WARNINGS)	\
-		   $(INCLUDES) $(LIBELF_FLAGS) $(LIBXXHASH_CFLAGS) $(HOSTCFLAGS)
+		   $(INCLUDES) $(LIBELF_FLAGS) $(LIBXXHASH_CFLAGS) $(HOSTCFLAGS) \
+		   -DOBJTOOL_ARCH=$(SRCARCH)
 
 OBJTOOL_LDFLAGS := $(LIBSUBCMD) $(LIBELF_LIBS) $(LIBXXHASH_LIBS) $(HOSTLDFLAGS)
 
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index ebe4a2a087ca..8710ae8708df 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -35,7 +35,7 @@ struct export {
 };
 
 static const char *symvers_path = "Module.symvers";
-bool debug, debug_correlate, debug_clone;
+bool show_arch, debug, debug_correlate, debug_clone;
 int indent;
 
 static const char * const klp_diff_usage[] = {
@@ -45,6 +45,7 @@ static const char * const klp_diff_usage[] = {
 
 static const struct option klp_diff_options[] = {
 	OPT_GROUP("Options:"),
+	OPT_BOOLEAN('a', "arch", &show_arch, "display target architecture"),
 	OPT_BOOLEAN('d', "debug", &debug, "enable all debug output"),
 	OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"),
 	OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"),
@@ -2366,6 +2367,12 @@ int cmd_klp_diff(int argc, const char **argv)
 	int ret;
 
 	argc = parse_options(argc, argv, klp_diff_options, klp_diff_usage, 0);
+
+	if (show_arch) {
+		printf("%s\n", __stringify(OBJTOOL_ARCH));
+		return 0;
+	}
+
 	if (argc != 3)
 		usage_with_options(klp_diff_usage, klp_diff_options);
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC 4/4] livepatch/klp-build: add basic out-of-tree module patching support
  2026-05-12 22:10 [RFC 0/4] klp-build: simple OOT module support Joe Lawrence
                   ` (2 preceding siblings ...)
  2026-05-12 22:11 ` [RFC 3/4] objtool/klp: add --arch option to display target architecture Joe Lawrence
@ 2026-05-12 22:11 ` Joe Lawrence
  2026-05-12 23:30 ` [RFC 0/4] klp-build: simple OOT module support Song Liu
  4 siblings, 0 replies; 6+ messages in thread
From: Joe Lawrence @ 2026-05-12 22:11 UTC (permalink / raw)
  To: live-patching; +Cc: Josh Poimboeuf, Song Liu, Miroslav Benes, Petr Mladek

klp-build is currently limited to patching in-tree kernel modules.
Introduce a -M/--module-dir option to enable livepatch generation for
basic out-of-tree (OOT) modules.  This requires the associated kernel
tree to be pre-configured (e.g., 'make modules_prepare').

The OOT workflow is as follows:

  cd /path/to/kernel
  ./scripts/livepatch/klp-build -M /path/to/mymodule my-fix.patch

With this option, klp-build performs two builds (original and patched)
of the OOT module via 'make M=...' instead of a full kernel rebuild.
The resulting objects are then processed and diffed to produce the
final livepatch .ko.

While this enhancement does not yet cover every OOT scenario, it
provides a functional baseline and enables OOT unit-testing for
the 'objtool klp diff' command.

Current limitations include:
 - Separate build directories (make O=) are not yet supported.
 - No passthrough for extra Kbuild variables (e.g., EXTRA_CFLAGS or
   specific CONFIG_* overrides like DKMS supports).
 - OOT module source must be contained within a single directory.
   Multi-directory layouts are not handled.

Assisted-by: Cursor:claude-4.6-opus
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
---
 scripts/livepatch/klp-build | 90 ++++++++++++++++++++++++++++---------
 1 file changed, 69 insertions(+), 21 deletions(-)

diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 10145b1dd089..db4da64f2b9f 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -21,6 +21,7 @@ shopt -s lastpipe
 
 unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE
 
+MODULE_DIR=""
 REPLACE=1
 SHORT_CIRCUIT=0
 JOBS="$(getconf _NPROCESSORS_ONLN)"
@@ -137,6 +138,7 @@ Options:
 
 Advanced Options:
    -d, --debug			Show symbol/reloc cloning decisions
+   -M, --module-dir=<DIR>	Out-of-tree module source directory
    -S, --short-circuit=STEP	Start at build step (requires prior --keep-tmp)
 				   1|orig		Build original kernel (default)
 				   2|patched		Build patched kernel
@@ -159,8 +161,8 @@ process_args() {
 	local args
 	local patch
 
-	short="hfj:o:vdS:T"
-	long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+	short="hfj:M:o:vdS:T"
+	long="help,show-first-changed,jobs:,module-dir:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
 
 	args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
 		echo; usage; exit
@@ -202,6 +204,10 @@ process_args() {
 				keep_tmp=1
 				shift
 				;;
+			-M | --module-dir)
+				MODULE_DIR="$2"
+				shift 2
+				;;
 			-S | --short-circuit)
 				[[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir"
 				keep_tmp=1
@@ -361,11 +367,21 @@ check_unsupported_patches() {
 		get_patch_files "$patch" | mapfile -t files
 
 		for file in "${files[@]}"; do
+			# In and out-of-tree paths to reject
 			case "$file" in
-				lib/*|*/vdso/*|*/realmode/rm/*|*.S)
+				*.S)
 					die "${patch}: unsupported patch to $file"
 					;;
 			esac
+
+			# In-tree paths to reject (based on naming convention)
+			if [[ -z "$MODULE_DIR" ]]; then
+				case "$file" in
+					lib/*|*/vdso/*|*/realmode/rm/*)
+						die "${patch}: unsupported patch to $file"
+						;;
+				esac
+			fi
 		done
 	done
 }
@@ -374,13 +390,14 @@ apply_patch() {
 	local patch="$1"
 	shift
 	local extra_args=("$@")
+	local patch_target="${MODULE_DIR:-$PWD}"
 	local drift_regex="with fuzz|offset [0-9]+ line"
 	local output
 	local status
 
 	[[ ! -f "$patch" ]] && die "$patch doesn't exist"
 	status=0
-	output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$?
+	output=$(patch -d "$patch_target" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$?
 	if [[ "$status" -ne 0 ]]; then
 		echo "$output" >&2
 		die "$patch did not apply"
@@ -390,14 +407,15 @@ apply_patch() {
 	fi
 
 	APPLIED_PATCHES+=("$patch")
-	patch -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch"
+	patch -d "$patch_target" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch"
 }
 
 revert_patch() {
 	local patch="$1"
+	local patch_target="${MODULE_DIR:-$PWD}"
 	local tmp=()
 
-	patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true
+	patch -d "$patch_target" -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true
 
 	for p in "${APPLIED_PATCHES[@]}"; do
 		[[ "$p" == "$patch" ]] && continue
@@ -452,8 +470,6 @@ cross_compile_init() {
 }
 
 do_init() {
-	# We're not yet smart enough to handle anything other than in-tree
-	# builds in pwd.
 	[[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory"
 
 	if (( SHORT_CIRCUIT >= 2 )); then
@@ -470,6 +486,15 @@ do_init() {
 		[[ -f "$DIFF_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $DIFF_DIR"
 	fi
 
+	if [[ -n "$MODULE_DIR" ]]; then
+		[[ -d "$MODULE_DIR" ]] || die "module directory not found: $MODULE_DIR"
+		MODULE_DIR="$(realpath "$MODULE_DIR")"
+		[[ -f "$MODULE_DIR/Kbuild" || -f "$MODULE_DIR/Makefile" ]] ||
+			die "no Kbuild or Makefile in $MODULE_DIR"
+		[[ -f "$PWD/Module.symvers" ]] ||
+			die "kernel must be built first (no Module.symvers in $PWD)"
+	fi
+
 	(( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR"
 	mkdir -p "$TMP_DIR"
 
@@ -488,6 +513,7 @@ do_init() {
 refresh_patch() {
 	local patch="$1"
 	local tmpdir="$PATCH_TMP_DIR"
+	local patch_target="${MODULE_DIR:-$PWD}"
 	local input_files=()
 	local output_files=()
 
@@ -500,11 +526,11 @@ refresh_patch() {
 	get_patch_output_files "$patch" | mapfile -t output_files
 
 	# Copy orig source files to 'a'
-	echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a"
+	( cd "$patch_target" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" )
 
 	# Copy patched source files to 'b'
 	apply_patch "$patch" "--silent"
-	echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b"
+	( cd "$patch_target" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" )
 	revert_patch "$patch"
 
 	# Diff 'a' and 'b' to make a clean patch
@@ -546,6 +572,9 @@ clean_kernel() {
 	cmd=("make")
 	cmd+=("--silent")
 	cmd+=("-j$JOBS")
+	if [[ -n "$MODULE_DIR" ]]; then
+		cmd+=("M=$MODULE_DIR")
+	fi
 	cmd+=("clean")
 
 	"${cmd[@]}"
@@ -582,7 +611,11 @@ build_kernel() {
 	fi
 	cmd+=("-j$JOBS")
 	cmd+=("KCFLAGS=-ffunction-sections -fdata-sections")
-	cmd+=("vmlinux")
+	if [[ -n "$MODULE_DIR" ]]; then
+		cmd+=("M=$MODULE_DIR")
+	else
+		cmd+=("vmlinux")
+	fi
 	cmd+=("modules")
 
 	"${cmd[@]}"							\
@@ -594,12 +627,19 @@ build_kernel() {
 find_objects() {
 	local opts=("$@")
 
-	# Find root-level vmlinux.o and non-root-level .ko files,
-	# excluding klp-tmp/ and .git/
-	find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \
-		    -type f "${opts[@]}"				\
-		    \( -name "*.ko" -o -path "$PWD/vmlinux.o" \)	\
-		    -printf '%P\n'
+	if [[ -n "$MODULE_DIR" ]]; then
+		# OOT: find .ko at any depth under the module dir
+		find "$MODULE_DIR" -path "$MODULE_DIR/.git" -prune -o \
+			-type f "${opts[@]}" \
+			-name "*.ko" -printf '%P\n'
+	else
+		# In-tree: find root-level vmlinux.o and non-root-level .ko files,
+		# excluding klp-tmp/ and .git/
+		find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o	-regex "$PWD/[^/][^/]*\.ko" \) -prune -o \
+			    -type f "${opts[@]}"				\
+			    \( -name "*.ko" -o -path "$PWD/vmlinux.o" \)	\
+			    -printf '%P\n'
+	fi
 }
 
 # Copy all .o archives to $ORIG_DIR
@@ -611,10 +651,11 @@ copy_orig_objects() {
 
 	find_objects | mapfile -t files
 
+	local obj_root="${MODULE_DIR:-$PWD}"
 	xtrace_save "copying original objects"
 	for _file in "${files[@]}"; do
 		local rel_file="${_file/.ko/.o}"
-		local file="$PWD/$rel_file"
+		local file="$obj_root/$rel_file"
 		local orig_file="$ORIG_DIR/$rel_file"
 		local orig_dir="$(dirname "$orig_file")"
 
@@ -645,10 +686,11 @@ copy_patched_objects() {
 
 	find_objects "${opts[@]}" | mapfile -t files
 
+	local obj_root="${MODULE_DIR:-$PWD}"
 	xtrace_save "copying changed objects"
 	for _file in "${files[@]}"; do
 		local rel_file="${_file/.ko/.o}"
-		local file="$PWD/$rel_file"
+		local file="$obj_root/$rel_file"
 		local orig_file="$ORIG_DIR/$rel_file"
 		local patched_file="$PATCHED_DIR/$rel_file"
 		local patched_dir="$(dirname "$patched_file")"
@@ -727,6 +769,9 @@ diff_objects() {
 		cmd+=("klp")
 		cmd+=("diff")
 		(( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}")
+
+		[[ -n "$MODULE_DIR" ]] && cmd+=("--symvers" "$PWD/Module.symvers")
+
 		cmd+=("$orig_file")
 		cmd+=("$patched_file")
 		cmd+=("$out_file")
@@ -905,13 +950,16 @@ build_patch_module() {
 process_args "$@"
 do_init
 
+BUILD_TARGET="kernel"
+[[ -n "$MODULE_DIR" ]] && BUILD_TARGET="module ${MODULE_DIR##*/}"
+
 if (( SHORT_CIRCUIT <= 2 )); then
 	status "Validating patch(es)"
 	validate_patches
 fi
 
 if (( SHORT_CIRCUIT <= 1 )); then
-	status "Building original kernel"
+	status "Building original $BUILD_TARGET"
 	clean_kernel
 	build_kernel "original"
 	status "Copying original object files"
@@ -922,7 +970,7 @@ if (( SHORT_CIRCUIT <= 2 )); then
 	status "Fixing patch(es)"
 	fix_patches
 	apply_patches "--silent"
-	status "Building patched kernel"
+	status "Building patched $BUILD_TARGET"
 	build_kernel "patched"
 	revert_patches
 	status "Copying patched object files"
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC 0/4] klp-build: simple OOT module support
  2026-05-12 22:10 [RFC 0/4] klp-build: simple OOT module support Joe Lawrence
                   ` (3 preceding siblings ...)
  2026-05-12 22:11 ` [RFC 4/4] livepatch/klp-build: add basic out-of-tree module patching support Joe Lawrence
@ 2026-05-12 23:30 ` Song Liu
  4 siblings, 0 replies; 6+ messages in thread
From: Song Liu @ 2026-05-12 23:30 UTC (permalink / raw)
  To: Joe Lawrence; +Cc: live-patching, Josh Poimboeuf, Miroslav Benes, Petr Mladek

Hi Joe,

On Tue, May 12, 2026 at 3:11 PM Joe Lawrence <joe.lawrence@redhat.com> wrote:
>
> This patchset introduces support for patching basic out-of-tree (OOT)
> modules.  The primary motivation is to streamline testing for objtool klp
> diff by providing a flexible and stable environment.

Thanks for this work!

I would like to add to the motivation here:

It is actually useful to livepatch OOT modules in production.
When using kpatch-build, we (Meta) generally do not patch OOT
modules. But there was one case where we had to do it, and it
required quite some manual work.

If we can make building livepatch for OOT easy, we are likely to
use it regularly in the future.

Thanks,
Song

[...]

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-05-12 23:31 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-12 22:10 [RFC 0/4] klp-build: simple OOT module support Joe Lawrence
2026-05-12 22:10 ` [RFC 1/4] objtool/klp: add --symvers option to klp diff Joe Lawrence
2026-05-12 22:11 ` [RFC 2/4] objtool/klp: allow special section entry size overrides Joe Lawrence
2026-05-12 22:11 ` [RFC 3/4] objtool/klp: add --arch option to display target architecture Joe Lawrence
2026-05-12 22:11 ` [RFC 4/4] livepatch/klp-build: add basic out-of-tree module patching support Joe Lawrence
2026-05-12 23:30 ` [RFC 0/4] klp-build: simple OOT module support Song Liu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox