History

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.

Josh Poimboeuf committed 1 year ago

Commit 78be9fa1 parent 2c2f0b81 file · +78 −4Browse files

Showing the changes to scripts/livepatch (1 of 1 file). Show all

Unified · Split

scripts/livepatch/klp-build+78−4
@@ -20,7 +20,7 @@ set -o nounset · 19 unmodified lines
20# This helps keep execution in pipes so pipefail+errexit can catch errors.20# This helps keep execution in pipes so pipefail+errexit can catch errors.
21shopt -s lastpipe21shopt -s lastpipe
22 22
23unset DEBUG_CLONE SKIP_CLEANUP XTRACE23unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
24 24
25REPLACE=125REPLACE=1
26SHORT_CIRCUIT=026SHORT_CIRCUIT=0
@@ -114,6 +114,7 @@ Usage: $SCRIPT [OPTIONS] PATCH_FILE(s) · 87 unmodified lines
114Generate a livepatch module.114Generate a livepatch module.
115 115
116Options:116Options:
117 -f, --show-first-changed Show address of first changed instruction
117 -j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]118 -j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]
118 -o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]119 -o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]
119 --no-replace Disable livepatch atomic replace120 --no-replace Disable livepatch atomic replace
@@ -141,8 +142,8 @@ process_args() { · 21 unmodified lines
141 local long142 local long
142 local args143 local args
143 144
144 short="hj:o:vdS:T"145 short="hfj:o:vdS:T"
145 long="help,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"146 long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
146 147
147 args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {148 args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
148 echo; usage; exit149 echo; usage; exit
@@ -155,6 +156,10 @@ process_args() { · 6 unmodified lines
155 usage156 usage
156 exit 0157 exit 0
157 ;;158 ;;
159 -f | --show-first-changed)
160 DIFF_CHECKSUM=1
161 shift
162 ;;
158 -j | --jobs)163 -j | --jobs)
159 JOBS="$2"164 JOBS="$2"
160 shift 2165 shift 2
@@ -618,6 +623,7 @@ diff_objects() { · 457 unmodified lines
618 local orig_file="$rel_file"623 local orig_file="$rel_file"
619 local patched_file="$PATCHED_DIR/$rel_file"624 local patched_file="$PATCHED_DIR/$rel_file"
620 local out_file="$DIFF_DIR/$rel_file"625 local out_file="$DIFF_DIR/$rel_file"
626 local filter=()
621 local cmd=()627 local cmd=()
622 628
623 mkdir -p "$(dirname "$out_file")"629 mkdir -p "$(dirname "$out_file")"
@@ -630,16 +636,80 @@ diff_objects() { · 6 unmodified lines
630 cmd+=("$patched_file")636 cmd+=("$patched_file")
631 cmd+=("$out_file")637 cmd+=("$out_file")
632 638
639 if [[ -v DIFF_CHECKSUM ]]; then
640 filter=("grep0")
641 filter+=("-Ev")
642 filter+=("DEBUG: .*checksum: ")
643 else
644 filter=("cat")
645 fi
646
633 (647 (
634 cd "$ORIG_DIR"648 cd "$ORIG_DIR"
635 "${cmd[@]}" \649 "${cmd[@]}" \
636 1> >(tee -a "$log") \650 1> >(tee -a "$log") \
637 2> >(tee -a "$log" >&2) || \651 2> >(tee -a "$log" | "${filter[@]}" >&2) || \
638 die "objtool klp diff failed"652 die "objtool klp diff failed"
639 )653 )
640 done654 done
641}655}
642 656
657# For each changed object, run objtool with --debug-checksum to get the
658# per-instruction checksums, and then diff those to find the first changed
659# instruction for each function.
660diff_checksums() {
661 local orig_log="$ORIG_DIR/checksum.log"
662 local patched_log="$PATCHED_DIR/checksum.log"
663 local -A funcs
664 local cmd=()
665 local line
666 local file
667 local func
668
669 gawk '/\.o: changed function: / {
670 sub(/:$/, "", $1)
671 print $1, $NF
672 }' "$KLP_DIFF_LOG" | mapfile -t lines
673
674 for line in "${lines[@]}"; do
675 read -r file func <<< "$line"
676 if [[ ! -v funcs["$file"] ]]; then
677 funcs["$file"]="$func"
678 else
679 funcs["$file"]+=" $func"
680 fi
681 done
682
683 cmd=("$SRC/tools/objtool/objtool")
684 cmd+=("--checksum")
685 cmd+=("--link")
686 cmd+=("--dry-run")
687
688 for file in "${!funcs[@]}"; do
689 local opt="--debug-checksum=${funcs[$file]// /,}"
690
691 (
692 cd "$ORIG_DIR"
693 "${cmd[@]}" "$opt" "$file" &> "$orig_log" || \
694 ( cat "$orig_log" >&2; die "objtool --debug-checksum failed" )
695
696 cd "$PATCHED_DIR"
697 "${cmd[@]}" "$opt" "$file" &> "$patched_log" || \
698 ( cat "$patched_log" >&2; die "objtool --debug-checksum failed" )
699 )
700
701 for func in ${funcs[$file]}; do
702 diff <( grep0 -E "^DEBUG: .*checksum: $func " "$orig_log" | sed "s|$ORIG_DIR/||") \
703 <( grep0 -E "^DEBUG: .*checksum: $func " "$patched_log" | sed "s|$PATCHED_DIR/||") \
704 | gawk '/^< DEBUG: / {
705 gsub(/:/, "")
706 printf "%s: %s: %s\n", $3, $5, $6
707 exit
708 }' || true
709 done
710 done
711}
712
643# Build and post-process livepatch module in $KMOD_DIR713# Build and post-process livepatch module in $KMOD_DIR
644build_patch_module() {714build_patch_module() {
645 local makefile="$KMOD_DIR/Kbuild"715 local makefile="$KMOD_DIR/Kbuild"
@@ -743,6 +813,10 @@ fi · 97 unmodified lines
743if (( SHORT_CIRCUIT <= 3 )); then813if (( SHORT_CIRCUIT <= 3 )); then
744 status "Diffing objects"814 status "Diffing objects"
745 diff_objects815 diff_objects
816 if [[ -v DIFF_CHECKSUM ]]; then
817 status "Finding first changed instructions"
818 diff_checksums
819 fi
746fi820fi
747 821
748if (( SHORT_CIRCUIT <= 4 )); then822if (( SHORT_CIRCUIT <= 4 )); then