Commit 78be9fac authored by Josh Poimboeuf's avatar Josh Poimboeuf
Browse files

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: default avatarPetr Mladek <pmladek@suse.com>
Tested-by: default avatarJoe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: default avatarJosh Poimboeuf <jpoimboe@kernel.org>
parent 2c2f0b86
Loading
Loading
Loading
Loading
+78 −4
Original line number Diff line number Diff line
@@ -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