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
2020 # This helps keep execution in pipes so pipefail+errexit can catch errors.
2121 shopt -s lastpipe
2222
23−unset DEBUG_CLONE SKIP_CLEANUP XTRACE
23+unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
2424
2525 REPLACE=1
2626 SHORT_CIRCUIT=0
@@ -114,6 +114,7 @@ Usage: $SCRIPT [OPTIONS] PATCH_FILE(s) · 87 unmodified lines
114114 Generate a livepatch module.
115115
116116 Options:
117+ -f, --show-first-changed Show address of first changed instruction
117118 -j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]
118119 -o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]
119120 --no-replace Disable livepatch atomic replace
@@ -141,8 +142,8 @@ process_args() { · 21 unmodified lines
141142 local long
142143 local args
143144
144− short="hj:o:vdS:T"
145− long="help,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
145+ short="hfj:o:vdS:T"
146+ long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
146147
147148 args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
148149 echo; usage; exit
@@ -155,6 +156,10 @@ process_args() { · 6 unmodified lines
155156 usage
156157 exit 0
157158 ;;
159+ -f | --show-first-changed)
160+ DIFF_CHECKSUM=1
161+ shift
162+ ;;
158163 -j | --jobs)
159164 JOBS="$2"
160165 shift 2
@@ -618,6 +623,7 @@ diff_objects() { · 457 unmodified lines
618623 local orig_file="$rel_file"
619624 local patched_file="$PATCHED_DIR/$rel_file"
620625 local out_file="$DIFF_DIR/$rel_file"
626+ local filter=()
621627 local cmd=()
622628
623629 mkdir -p "$(dirname "$out_file")"
@@ -630,16 +636,80 @@ diff_objects() { · 6 unmodified lines
630636 cmd+=("$patched_file")
631637 cmd+=("$out_file")
632638
639+ if [[ -v DIFF_CHECKSUM ]]; then
640+ filter=("grep0")
641+ filter+=("-Ev")
642+ filter+=("DEBUG: .*checksum: ")
643+ else
644+ filter=("cat")
645+ fi
646+
633647 (
634648 cd "$ORIG_DIR"
635649 "${cmd[@]}" \
636650 1> >(tee -a "$log") \
637− 2> >(tee -a "$log" >&2) || \
651+ 2> >(tee -a "$log" | "${filter[@]}" >&2) || \
638652 die "objtool klp diff failed"
639653 )
640654 done
641655 }
642656
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.
660+diff_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+
643713 # Build and post-process livepatch module in $KMOD_DIR
644714 build_patch_module() {
645715 local makefile="$KMOD_DIR/Kbuild"
@@ -743,6 +813,10 @@ fi · 97 unmodified lines
743813 if (( SHORT_CIRCUIT <= 3 )); then
744814 status "Diffing objects"
745815 diff_objects
816+ if [[ -v DIFF_CHECKSUM ]]; then
817+ status "Finding first changed instructions"
818+ diff_checksums
819+ fi
746820 fi
747821
748822 if (( SHORT_CIRCUIT <= 4 )); then