livepatch/klp-build: Add --show-first-changed option to show function divergence

Add a --show-first-changed option to identify where changed functions
begin to diverge:

  - Parse 'objtool klp diff' output to find changed functions.

  - Run objtool again on each object with --debug-checksum=<funcs>.

  - Diff the per-instruction checksum debug output to locate the first
    differing instruction.

This can be useful for quickly determining where and why a function
changed.

Acked-by: Petr Mladek <pmladek@suse.com>
Tested-by: Joe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
This commit is contained in:
Josh Poimboeuf
2025-09-17 09:04:10 -07:00
parent 2c2f0b8626
commit 78be9facfb

View File

@@ -20,7 +20,7 @@ set -o nounset
# This helps keep execution in pipes so pipefail+errexit can catch errors.
shopt -s lastpipe
unset DEBUG_CLONE SKIP_CLEANUP XTRACE
unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
REPLACE=1
SHORT_CIRCUIT=0
@@ -114,6 +114,7 @@ Usage: $SCRIPT [OPTIONS] PATCH_FILE(s)
Generate a livepatch module.
Options:
-f, --show-first-changed Show address of first changed instruction
-j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]
-o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]
--no-replace Disable livepatch atomic replace
@@ -141,8 +142,8 @@ process_args() {
local long
local args
short="hj:o:vdS:T"
long="help,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
short="hfj:o:vdS:T"
long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
echo; usage; exit
@@ -155,6 +156,10 @@ process_args() {
usage
exit 0
;;
-f | --show-first-changed)
DIFF_CHECKSUM=1
shift
;;
-j | --jobs)
JOBS="$2"
shift 2
@@ -618,6 +623,7 @@ diff_objects() {
local orig_file="$rel_file"
local patched_file="$PATCHED_DIR/$rel_file"
local out_file="$DIFF_DIR/$rel_file"
local filter=()
local cmd=()
mkdir -p "$(dirname "$out_file")"
@@ -630,16 +636,80 @@ diff_objects() {
cmd+=("$patched_file")
cmd+=("$out_file")
if [[ -v DIFF_CHECKSUM ]]; then
filter=("grep0")
filter+=("-Ev")
filter+=("DEBUG: .*checksum: ")
else
filter=("cat")
fi
(
cd "$ORIG_DIR"
"${cmd[@]}" \
1> >(tee -a "$log") \
2> >(tee -a "$log" >&2) || \
2> >(tee -a "$log" | "${filter[@]}" >&2) || \
die "objtool klp diff failed"
)
done
}
# For each changed object, run objtool with --debug-checksum to get the
# per-instruction checksums, and then diff those to find the first changed
# instruction for each function.
diff_checksums() {
local orig_log="$ORIG_DIR/checksum.log"
local patched_log="$PATCHED_DIR/checksum.log"
local -A funcs
local cmd=()
local line
local file
local func
gawk '/\.o: changed function: / {
sub(/:$/, "", $1)
print $1, $NF
}' "$KLP_DIFF_LOG" | mapfile -t lines
for line in "${lines[@]}"; do
read -r file func <<< "$line"
if [[ ! -v funcs["$file"] ]]; then
funcs["$file"]="$func"
else
funcs["$file"]+=" $func"
fi
done
cmd=("$SRC/tools/objtool/objtool")
cmd+=("--checksum")
cmd+=("--link")
cmd+=("--dry-run")
for file in "${!funcs[@]}"; do
local opt="--debug-checksum=${funcs[$file]// /,}"
(
cd "$ORIG_DIR"
"${cmd[@]}" "$opt" "$file" &> "$orig_log" || \
( cat "$orig_log" >&2; die "objtool --debug-checksum failed" )
cd "$PATCHED_DIR"
"${cmd[@]}" "$opt" "$file" &> "$patched_log" || \
( cat "$patched_log" >&2; die "objtool --debug-checksum failed" )
)
for func in ${funcs[$file]}; do
diff <( grep0 -E "^DEBUG: .*checksum: $func " "$orig_log" | sed "s|$ORIG_DIR/||") \
<( grep0 -E "^DEBUG: .*checksum: $func " "$patched_log" | sed "s|$PATCHED_DIR/||") \
| gawk '/^< DEBUG: / {
gsub(/:/, "")
printf "%s: %s: %s\n", $3, $5, $6
exit
}' || true
done
done
}
# Build and post-process livepatch module in $KMOD_DIR
build_patch_module() {
local makefile="$KMOD_DIR/Kbuild"
@@ -743,6 +813,10 @@ fi
if (( SHORT_CIRCUIT <= 3 )); then
status "Diffing objects"
diff_objects
if [[ -v DIFF_CHECKSUM ]]; then
status "Finding first changed instructions"
diff_checksums
fi
fi
if (( SHORT_CIRCUIT <= 4 )); then