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.
Showing the changes to scripts/livepatch (1 of 1 file). Show all
Unified · Split
| @@ -20,7 +20,7 @@ set -o nounset · 19 unmodified lines | ||
| 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,6 +114,7 @@ Usage: $SCRIPT [OPTIONS] PATCH_FILE(s) · 87 unmodified lines | ||
| 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 |
| @@ -141,8 +142,8 @@ process_args() { · 21 unmodified lines | ||
| 141 | 142 | local long |
| 142 | 143 | local args |
| 143 | 144 | |
| 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" | |
| 146 | 147 | |
| 147 | 148 | args=$(getopt --options "$short" --longoptions "$long" -- "$@") || { |
| 148 | 149 | echo; usage; exit |
| @@ -155,6 +156,10 @@ process_args() { · 6 unmodified lines | ||
| 155 | 156 | usage |
| 156 | 157 | exit 0 |
| 157 | 158 | ;; |
| 159 | + -f | --show-first-changed) | |
| 160 | + DIFF_CHECKSUM=1 | |
| 161 | + shift | |
| 162 | + ;; | |
| 158 | 163 | -j | --jobs) |
| 159 | 164 | JOBS="$2" |
| 160 | 165 | shift 2 |
| @@ -618,6 +623,7 @@ diff_objects() { · 457 unmodified lines | ||
| 618 | 623 | local orig_file="$rel_file" |
| 619 | 624 | local patched_file="$PATCHED_DIR/$rel_file" |
| 620 | 625 | local out_file="$DIFF_DIR/$rel_file" |
| 626 | + local filter=() | |
| 621 | 627 | local cmd=() |
| 622 | 628 | |
| 623 | 629 | mkdir -p "$(dirname "$out_file")" |
| @@ -630,16 +636,80 @@ diff_objects() { · 6 unmodified lines | ||
| 630 | 636 | cmd+=("$patched_file") |
| 631 | 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 | 648 | cd "$ORIG_DIR" |
| 635 | 649 | "${cmd[@]}" \ |
| 636 | 650 | 1> >(tee -a "$log") \ |
| 637 | − 2> >(tee -a "$log" >&2) || \ | |
| 651 | + 2> >(tee -a "$log" | "${filter[@]}" >&2) || \ | |
| 638 | 652 | die "objtool klp diff failed" |
| 639 | 653 | ) |
| 640 | 654 | 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. | |
| 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 | + | |
| 643 | 713 | # Build and post-process livepatch module in $KMOD_DIR |
| 644 | 714 | build_patch_module() { |
| 645 | 715 | local makefile="$KMOD_DIR/Kbuild" |
| @@ -743,6 +813,10 @@ fi · 97 unmodified lines | ||
| 743 | 813 | if (( SHORT_CIRCUIT <= 3 )); then |
| 744 | 814 | status "Diffing objects" |
| 745 | 815 | diff_objects |
| 816 | + if [[ -v DIFF_CHECKSUM ]]; then | |
| 817 | + status "Finding first changed instructions" | |
| 818 | + diff_checksums | |
| 819 | + fi | |
| 746 | 820 | fi |
| 747 | 821 | |
| 748 | 822 | if (( SHORT_CIRCUIT <= 4 )); then |