Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

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