All of lore.kernel.org
 help / color / mirror / Atom feed
From: Joe Lawrence <joe.lawrence@redhat.com>
To: live-patching@vger.kernel.org
Cc: Josh Poimboeuf <jpoimboe@kernel.org>, Song Liu <song@kernel.org>,
	Miroslav Benes <mbenes@suse.cz>, Petr Mladek <pmladek@suse.com>
Subject: [RFC 4/4] livepatch/klp-build: add basic out-of-tree module patching support
Date: Tue, 12 May 2026 18:11:02 -0400	[thread overview]
Message-ID: <20260512221102.2720763-5-joe.lawrence@redhat.com> (raw)
In-Reply-To: <20260512221102.2720763-1-joe.lawrence@redhat.com>

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


  parent reply	other threads:[~2026-05-12 22:11 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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-14 20:47   ` sashiko-bot
2026-05-12 22:11 ` [RFC 2/4] objtool/klp: allow special section entry size overrides Joe Lawrence
2026-05-14 20:58   ` sashiko-bot
2026-05-12 22:11 ` [RFC 3/4] objtool/klp: add --arch option to display target architecture Joe Lawrence
2026-05-14 21:10   ` sashiko-bot
2026-05-12 22:11 ` Joe Lawrence [this message]
2026-05-14 21:20   ` [RFC 4/4] livepatch/klp-build: add basic out-of-tree module patching support sashiko-bot
2026-05-12 23:30 ` [RFC 0/4] klp-build: simple OOT module support Song Liu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260512221102.2720763-5-joe.lawrence@redhat.com \
    --to=joe.lawrence@redhat.com \
    --cc=jpoimboe@kernel.org \
    --cc=live-patching@vger.kernel.org \
    --cc=mbenes@suse.cz \
    --cc=pmladek@suse.com \
    --cc=song@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.