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: 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.