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
next prev parent reply other threads:[~2026-05-12 22:11 UTC|newest]
Thread overview: 6+ 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-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 ` Joe Lawrence [this message]
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox