[RFC PATCH v2 6/7] livepatch/klp-build: add pre-built object support for advanced OOT workflows
From: Joe Lawrence <joe.lawrence@redhat.com>
Date: 2026-08-26 19:50:19
Subsystem:
live patching, the rest · Maintainers:
Josh Poimboeuf, Jiri Kosina, Miroslav Benes, Petr Mladek, Linus Torvalds
Most real-world out-of-tree drivers use complicated, non-standard build
systems (DKMS, cmake, autoconf, proprietary toolchains) that cannot be
driven by klp-build's internal build stages. Rather than attempting to
accommodate every third-party build system, let the user handle the
build process and supply the object pairs directly.
Enhance klp-build with --orig-dir and --patched-dir options that accept
directories of pre-built .o files. The user is responsible for building
original and patched objects externally. The only hard requirement is
that both builds use -ffunction-sections and -fdata-sections so that
objtool can identify changed functions at the object level.
From there, klp-build performs its binary comparison, symbol extraction,
and livepatch module assembly pipeline used for in-tree patches, only
with the build steps factored out. External symbol ownership is
determined from the target kernel's Module.symvers (--symvers,
defaulting to $PWD/Module.symvers).
The new options are mutually exclusive with --oot-dir (which drives the
build itself) and --short-circuit (which is specific to the standard
build pipeline).
The workflow is as follows:
# 1. Build OOT objects with -ffunction-sections and -fdata-sections
mkdir -p /tmp/orig && cp $OOT/my_module.o /tmp/orig/
# 2. (Optional) leverage the kernel's fix-patch-lines script to "undo"
# the effects of line number shift by the patch
$KDIR/scripts/livepatch/fix-patch-lines $OOT/fix-overflow.patch \
| recountdiff > /tmp/fixed.patch
# 3. Apply patch, rebuild
mkdir -p /tmp/patched && cp $OOT/my_module.o /tmp/patched/
# 4. Generate the livepatch
cd $KDIR
./scripts/livepatch/klp-build \
--orig-dir /tmp/orig \
--patched-dir /tmp/patched \
--symvers $KDIR/Module.symvers \
-o livepatch-my_module.ko
NOTE: klp-build does not verify that user-supplied objects were built
against the correct kernel headers or with compatible compiler options.
The resulting livepatch module is only as correct as the inputs
provided.
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
---
scripts/livepatch/klp-build | 110 +++++++++++++++++++++++++++++++++---
1 file changed, 102 insertions(+), 8 deletions(-)
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index b60f5a5da31e..e118f133e923 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build@@ -22,6 +22,9 @@ shopt -s lastpipe unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE OOT_DIR="" +USER_ORIG_DIR="" +USER_PATCHED_DIR="" +SYMVERS_PATH="" REPLACE=1 SHORT_CIRCUIT=0 JOBS="$(getconf _NPROCESSORS_ONLN)"
@@ -127,6 +130,7 @@ trap trap_err ERR __usage() { cat <<EOF Usage: $SCRIPT [OPTIONS] PATCH_FILE(s) + $SCRIPT --orig-dir=<DIR> --patched-dir=<DIR> [OPTIONS] Generate a livepatch module. Options:
@@ -139,6 +143,9 @@ Options: Advanced Options: -d, --debug Show symbol/reloc cloning decisions --oot-dir=<DIR> Out-of-tree module source directory + --orig-dir=<DIR> Directory of pre-built original .o files + --patched-dir=<DIR> Directory of pre-built patched .o files + --symvers=<FILE> Path to Module.symvers [default: \$PWD/Module.symvers] -S, --short-circuit=STEP Start at build step (requires prior --keep-tmp) 1|orig Build original kernel (default) 2|patched Build patched kernel
@@ -162,7 +169,7 @@ process_args() { local patch short="hfj:o:vdS:T" - long="help,show-first-changed,jobs:,oot-dir:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" + long="help,show-first-changed,jobs:,oot-dir:,orig-dir:,patched-dir:,symvers:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" args=$(getopt --options "$short" --longoptions "$long" -- "$@") || { echo; usage; exit
@@ -208,6 +215,18 @@ process_args() { OOT_DIR="$2" shift 2 ;; + --orig-dir) + USER_ORIG_DIR="$2" + shift 2 + ;; + --patched-dir) + USER_PATCHED_DIR="$2" + shift 2 + ;; + --symvers) + SYMVERS_PATH="$2" + shift 2 + ;; -S | --short-circuit) [[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir" keep_tmp=1
@@ -236,7 +255,18 @@ process_args() { esac done - if [[ $# -eq 0 ]] && (( SHORT_CIRCUIT <= 2 )); then + if [[ -n "$USER_ORIG_DIR" || -n "$USER_PATCHED_DIR" ]]; then + [[ -n "$USER_ORIG_DIR" && -n "$USER_PATCHED_DIR" ]] || + die "--orig-dir and --patched-dir must both be specified" + [[ -v NAME ]] || + die "--orig-dir/--patched-dir requires -o <output.ko>" + [[ -n "$OOT_DIR" ]] && + die "--orig-dir/--patched-dir and --oot-dir are mutually exclusive" + (( SHORT_CIRCUIT > 0 )) && + die "--short-circuit is not used with --orig-dir/--patched-dir" + [[ $# -gt 0 ]] && + die "patch files are not used with --orig-dir/--patched-dir" + elif [[ $# -eq 0 ]] && (( SHORT_CIRCUIT <= 2 )); then usage exit 1 fi
@@ -456,7 +486,19 @@ validate_patches() { do_init() { [[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" - if [[ -n "$OOT_DIR" ]]; then + if [[ -n "$USER_ORIG_DIR" ]]; then + [[ -d "$USER_ORIG_DIR" ]] || die "directory not found: $USER_ORIG_DIR" + [[ -d "$USER_PATCHED_DIR" ]] || die "directory not found: $USER_PATCHED_DIR" + USER_ORIG_DIR="$(realpath "$USER_ORIG_DIR")" + USER_PATCHED_DIR="$(realpath "$USER_PATCHED_DIR")" + if [[ -n "$SYMVERS_PATH" ]]; then + SYMVERS_PATH="$(realpath "$SYMVERS_PATH")" + [[ -f "$SYMVERS_PATH" ]] || die "Module.symvers not found: $SYMVERS_PATH" + else + [[ -f "$PWD/Module.symvers" ]] || + die "no Module.symvers in $PWD; use --symvers to specify" + fi + elif [[ -n "$OOT_DIR" ]]; then [[ -d "$OOT_DIR" ]] || die "module directory not found: $OOT_DIR" OOT_DIR="$(realpath "$OOT_DIR")" [[ -f "$OOT_DIR/Kbuild" || -f "$OOT_DIR/Makefile" ]] ||
@@ -482,14 +524,19 @@ do_init() { (( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR" mkdir -p "$TMP_DIR" + validate_config + set_module_name + set_kernelversion + + # Pre-built OOT init complete + if [[ -n "$USER_ORIG_DIR" ]]; then + return 0 + fi + APPLIED_PATCHES=() [[ -x "$FIX_PATCH_LINES" ]] || die "can't find fix-patch-lines" command -v recountdiff &>/dev/null || die "recountdiff not found (install patchutils)" - - validate_config - set_module_name - set_kernelversion } # Refresh the patch hunk headers, specifically the line numbers and counts.
@@ -757,7 +804,11 @@ diff_objects() { cmd+=("klp") cmd+=("diff") (( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}") - [[ -n "$OOT_DIR" ]] && cmd+=("--symvers" "$PWD/Module.symvers") + if [[ -n "$SYMVERS_PATH" ]]; then + cmd+=("--symvers" "$SYMVERS_PATH") + elif [[ -n "$OOT_DIR" || -n "$USER_ORIG_DIR" ]]; then + cmd+=("--symvers" "$PWD/Module.symvers") + fi cmd+=("$orig_file") cmd+=("$patched_file") cmd+=("$out_file")
@@ -931,11 +982,54 @@ build_patch_module() { } +setup_oot() { + local files=() + local rel + + mkdir -p "$ORIG_DIR" "$PATCHED_DIR" + + find "$USER_ORIG_DIR" -type f -name "*.o" -printf '%P\n' | mapfile -t files + [[ ${#files[@]} -gt 0 ]] || die "no .o files found in $USER_ORIG_DIR" + for rel in "${files[@]}"; do + [[ -f "$USER_PATCHED_DIR/$rel" ]] || + die "$rel found in orig dir but missing from patched dir" + mkdir -p "$ORIG_DIR/$(dirname "$rel")" + mkdir -p "$PATCHED_DIR/$(dirname "$rel")" + cp -f "$USER_ORIG_DIR/$rel" "$ORIG_DIR/$rel" + cp -f "$USER_PATCHED_DIR/$rel" "$PATCHED_DIR/$rel" + done + + touch "$ORIG_DIR/.complete" + touch "$PATCHED_DIR/.complete" +} + + ################################################################################ process_args "$@" do_init +# User provided OOT original and patched object files can jump straight +# to checksum + diff + build steps +if [[ -n "$USER_ORIG_DIR" ]]; then + status "Setting up OOT objects" + setup_oot + + status "Generating original checksums" + generate_checksums "$ORIG_DIR" "$ORIG_CSUM_DIR" "$PATCHED_DIR" + status "Generating patched checksums" + generate_checksums "$PATCHED_DIR" "$PATCHED_CSUM_DIR" + + status "Diffing objects" + diff_objects + + status "Building patch module: $OUTFILE" + build_patch_module + + status "SUCCESS" + exit 0 +fi + BUILD_TARGET="kernel" [[ -n "$OOT_DIR" ]] && BUILD_TARGET="module ${OOT_DIR##*/}"
--
2.55.0