From 1ead10d2b214ec5545ee2cdb25d5b0cdb3b4f735 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 17 Aug 2022 12:10:39 -0700 Subject: [PATCH 1/3] kpatch-cc: Add more file ignores These files aren't in the kernel proper, and can be ignored. Signed-off-by: Josh Poimboeuf --- kpatch-build/kpatch-cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/kpatch-build/kpatch-cc b/kpatch-build/kpatch-cc index 6f0063e..d2bccdd 100755 --- a/kpatch-build/kpatch-cc +++ b/kpatch-build/kpatch-cc @@ -31,22 +31,22 @@ if [[ "$TOOLCHAINCMD" =~ ^(.*-)?gcc$ || "$TOOLCHAINCMD" =~ ^(.*-)?clang$ ]] ; th vmlinux.o|\ .tmp_kallsyms1.o|\ .tmp_kallsyms2.o|\ - init/version.o|\ - arch/x86/boot/version.o|\ - arch/x86/boot/compressed/eboot.o|\ - arch/x86/boot/header.o|\ - arch/x86/boot/compressed/efi_stub_64.o|\ - arch/x86/boot/compressed/piggy.o|\ - kernel/system_certificates.o|\ - arch/x86/vdso/*|\ + arch/x86/boot/*|\ arch/x86/entry/vdso/*|\ - drivers/firmware/efi/libstub/*|\ + arch/x86/purgatory/*|\ + arch/x86/realmode/*|\ + arch/x86/tools/*|\ + arch/x86/vdso/*|\ arch/powerpc/kernel/prom_init.o|\ arch/powerpc/kernel/vdso64/*|\ arch/s390/boot/*|\ arch/s390/purgatory/*|\ arch/s390/kernel/vdso64/*|\ + drivers/firmware/efi/libstub/*|\ + init/version.o|\ + kernel/system_certificates.o|\ lib/*|\ + tools/*|\ .*.o|\ */.lib_exports.o) break From 7861240f482aa41bfad41fbb57ac1990cd6b6960 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 17 Aug 2022 12:17:19 -0700 Subject: [PATCH 2/3] kpatch-build: Add find_kobj() short-circuit for OOT modules When patching an OOT module, the parent object is always the OOT module. Hard-code that to prevent the need for any further special casing in find_kobj() (e.g., commit 9143e88f16a1 ("kpatch-build: fix find_parent_obj")). Signed-off-by: Josh Poimboeuf --- kpatch-build/kpatch-build | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index 296fa48..5435a19 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -479,6 +479,12 @@ find_parent_obj() { find_kobj() { arg="$1" + + if [[ -n $OOT_MODULE ]]; then + KOBJFILE="$OOT_MODULE" + return + fi + KOBJFILE="$arg" DEEP_FIND=0 ERROR_IF_DIFF= @@ -1093,17 +1099,17 @@ for i in $FILES; do find_kobj "$i" cd "$TEMPDIR" || die if [[ -e "orig/$i" ]]; then - if [[ "$(basename "$KOBJFILE")" = vmlinux ]]; then - KOBJFILE_NAME=vmlinux - KOBJFILE_PATH="$VMLINUX" - SYMTAB="${TEMPDIR}/${KOBJFILE_NAME}.symtab" - SYMVERS_FILE="$BUILDDIR/Module.symvers" - elif [[ "$(basename "$KOBJFILE")" = "$(basename "$OOT_MODULE")" ]]; then + if [[ -n $OOT_MODULE ]]; then KOBJFILE_NAME="$(basename --suffix=.ko "$OOT_MODULE")" KOBJFILE_NAME="${KOBJFILE_NAME//-/_}" KOBJFILE_PATH="$OOT_MODULE" SYMTAB="${TEMPDIR}/module/${KOBJFILE_NAME}.symtab" SYMVERS_FILE="$TEMPDIR/Module.symvers" + elif [[ "$(basename "$KOBJFILE")" = vmlinux ]]; then + KOBJFILE_NAME=vmlinux + KOBJFILE_PATH="$VMLINUX" + SYMTAB="${TEMPDIR}/${KOBJFILE_NAME}.symtab" + SYMVERS_FILE="$BUILDDIR/Module.symvers" else KOBJFILE_NAME=$(basename "${KOBJFILE%.ko}") KOBJFILE_NAME="${KOBJFILE_NAME//-/_}" From 1d7e8a74bb6c282ebe6c9193e4bd0218ec689e46 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 17 Aug 2022 12:21:04 -0700 Subject: [PATCH 3/3] kpatch-build: support Linux 5.19 .cmd file format Rewrite kobj_find() to deal with Linux 5.19, where the .cmd files use object file paths relative to the .cmd file rather than relative to the root of the kernel tree. While at it, add several performance enhancements to prevent all currently known deep finds. This is all quite fiddly. But it works. Fixes #1277. Signed-off-by: Josh Poimboeuf --- kpatch-build/kpatch-build | 247 +++++++++++++++++++++++++++++--------- 1 file changed, 190 insertions(+), 57 deletions(-) diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index 5435a19..bec8010 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -434,82 +434,215 @@ find_special_section_data() { return } -filter_parent_obj() -{ - local dir="${1}" - local file="${2}" +# path of file, relative to dir +# adapted from https://stackoverflow.com/a/24848739 +relpath() { + local file="$1" + local dir="$2" - grep -v "\.mod\.cmd$" | grep -Fv "${dir}/.${file}.cmd" + local filedir + local common + local result + + filedir="$(dirname "$(readlink -f "$file")")" + common="$(readlink -f "$dir")" + + if [[ "$filedir" = "$common" ]]; then + basename "$file" + return + fi + + while [[ "${filedir#$common/}" = "$filedir" ]]; do + common="$(dirname "$common")" + result="../$result" + done + + result="${result}${filedir#$common/}" + echo "${result}/$(basename "$file")" +} + +cmd_file_to_o_file() { + local parent="$1" + + # convert cmd file name to corresponding .o + parent_dir="$(dirname "$parent")" + parent_dir="${parent_dir#./}" + parent="$(basename "$parent")" + parent="${parent#.}" + parent="${parent%.cmd}" + parent="$parent_dir/$parent" + + [[ -f $parent ]] || die "can't find $parent associated with $1" + + echo "$parent" +} + +get_parent_from_parents() { + local parents=("$@") + + [[ ${#parents[@]} -eq 0 ]] && PARENT="" && return + [[ ${#parents[@]} -eq 1 ]] && PARENT="${parents[0]}" && return + + # multiple parents: + local parent + local mod_name="${parents[0]%.*}" + local mod_file + for parent in "${parents[@]}"; do + # for modules, there can be multiple matches. Some + # combination of foo.o, foo.mod, and foo.ko, depending + # on kernel version and whether the module is single or + # multi-object. Make sure a .mod and/or .ko exists, and no + # more than one .mod/.ko exists. + + [[ $parent = *.o ]] && continue + + if [[ ${parent%.*} != "$mod_name" ]]; then + mod_file="" + break + fi + + if [[ $parent = *.mod || $parent = *.ko ]]; then + mod_file=$parent + continue + fi + + mod_file="" + break + done + + if [[ -n $mod_file ]]; then + PARENT="$mod_file" + return + fi + + ERROR_IF_DIFF="multiple parent matches for $file: ${parents[*]}" + PARENT="${parents[0]}" +} + +__find_parent_obj_in_dir() { + local file="$1" + local dir="$2" + + declare -a parents + + while IFS='' read -r parent; do + parent="$(cmd_file_to_o_file "$parent")" + [[ $parent -ef $file ]] && continue + parents+=("$parent") + done < <(grep -El "[ ]${file/./\\.}([ \)]|$)" "$dir"/.*.cmd) + + get_parent_from_parents "${parents[@]}" +} + +find_parent_obj_in_dir() { + local file="$1" + local dir="$2" + + # make sure the dir has .cmd files + if ! compgen -G "$dir"/.*.cmd > /dev/null; then + PARENT="" + return + fi + + # 5.19+: ../acp/acp_hw.o + __find_parent_obj_in_dir "$(relpath "$file" "$dir")" "$dir" + [[ -n $PARENT ]] && return + + # pre-5.19 (and 5.19+ single-object modules): + if [[ $file == $dir* ]]; then + # arch/x86/kernel/smp.o + __find_parent_obj_in_dir "$file" "$dir" + else + # drivers/gpu/drm/amd/amdgpu/../acp/acp_hw.o + __find_parent_obj_in_dir "$dir"/"$(relpath "$file" "$dir")" "$dir" + fi } find_parent_obj() { - dir="$(dirname "$1")" - absdir="$(readlink -f "$dir")" - pwddir="$(readlink -f .)" - pdir="${absdir#$pwddir/}" - file="$(basename "$1")" - grepname="${1%.o}" - grepname="$grepname\\.o" - if [[ "$DEEP_FIND" -eq 1 ]]; then - num=0 - if [[ -n "$last_deep_find" ]]; then - parent="$(grep -lw "$grepname" "$last_deep_find"/.*.cmd | filter_parent_obj "${pdir}" "${file}" | head -n1)" - num="$(grep -lw "$grepname" "$last_deep_find"/.*.cmd | filter_parent_obj "${pdir}" "${file}" | wc -l)" - fi - if [[ "$num" -eq 0 ]]; then - parent="$(find . -name ".*.cmd" -print0 | xargs -0 grep -lw "$grepname" | filter_parent_obj "${pdir}" "${file}" | cut -c3- | head -n1)" - num="$(find . -name ".*.cmd" -print0 | xargs -0 grep -lw "$grepname" | filter_parent_obj "${pdir}" "${file}" | wc -l)" - [[ "$num" -eq 1 ]] && last_deep_find="$(dirname "$parent")" - fi - else - parent="$(grep -lw "$grepname" "$dir"/.*.cmd | filter_parent_obj "${dir}" "${file}" | head -n1)" - num="$(grep -lw "$grepname" "$dir"/.*.cmd | filter_parent_obj "${dir}" "${file}" | wc -l)" + local file="$1" + + # common case: look in same directory + find_parent_obj_in_dir "$file" "$(dirname "$file")" + [[ -n $PARENT ]] && return + + # if we previously had a successful deep find, try that dir first + if [[ -n "$LAST_DEEP_FIND_DIR" ]]; then + find_parent_obj_in_dir "$file" "$LAST_DEEP_FIND_DIR" + [[ -n "$PARENT" ]] && return fi - [[ "$num" -eq 0 ]] && PARENT="" && return - [[ "$num" -gt 1 ]] && ERROR_IF_DIFF="two parent matches for $1" + # prevent known deep finds + if [[ $file = drivers/gpu/drm/amd/* ]]; then + find_parent_obj_in_dir "$file" "drivers/gpu/drm/amd/amdgpu" + [[ -n "$PARENT" ]] && return + fi + if [[ $file = virt/kvm/* ]]; then + find_parent_obj_in_dir "$file" "arch/x86/kvm" + [[ -n "$PARENT" ]] && return + fi + if [[ $file = drivers/oprofile/* ]]; then + find_parent_obj_in_dir "$file" "arch/x86/oprofile" + [[ -n "$PARENT" ]] && return + fi - dir="$(dirname "$parent")" - PARENT="$(basename "$parent")" - PARENT="${PARENT#.}" - PARENT="${PARENT%.cmd}" - [[ $dir != "." ]] && PARENT="$dir/$PARENT" - [[ ! -e "$PARENT" ]] && die "ERROR: can't find parent $PARENT for $1" + # check higher-level dirs + local dir + dir="$(dirname "$file")" + while [[ ! $dir -ef . ]]; do + dir="$(dirname "$dir")" + find_parent_obj_in_dir "$file" "$dir" + [[ -n $PARENT ]] && return + done + + # slow path: search the entire tree ("deep find") + echo 'doing "deep find" for parent object' + declare -a parents + while IFS= read -r -d '' dir; do + find_parent_obj_in_dir "$file" "$dir" + if [[ -n $PARENT ]]; then + parents+=("$PARENT") + LAST_DEEP_FIND_DIR="$dir" + fi + done < <(find . -type d -print0) + + get_parent_from_parents "${parents[@]}" } +# find vmlinux or .ko associated with a .o file find_kobj() { - arg="$1" + local file="$1" if [[ -n $OOT_MODULE ]]; then KOBJFILE="$OOT_MODULE" return fi - KOBJFILE="$arg" - DEEP_FIND=0 + KOBJFILE="$file" ERROR_IF_DIFF= while true; do + case "$KOBJFILE" in + *.mod) + KOBJFILE=${PARENT/.mod/.ko} + [[ -e $KOBJFILE ]] || die "can't find .ko for $PARENT" + return + ;; + *.ko) + return + ;; + */built-in.o|\ + */built-in.a|\ + arch/x86/kernel/ebda.o|\ + arch/x86/kernel/head*.o|\ + arch/x86/kernel/platform-quirks.o|\ + arch/x86/lib/lib.a|\ + lib/lib.a) + KOBJFILE=vmlinux + return + ;; + esac + find_parent_obj "$KOBJFILE" - [[ -n "$PARENT" ]] && DEEP_FIND=0 - if [[ -z "$PARENT" ]]; then - [[ "$KOBJFILE" = *.ko ]] && return - case "$KOBJFILE" in - */built-in.o|\ - */built-in.a|\ - arch/x86/lib/lib.a|\ - arch/x86/kernel/head*.o|\ - arch/x86/kernel/ebda.o|\ - arch/x86/kernel/platform-quirks.o|\ - lib/lib.a) - KOBJFILE=vmlinux - return - esac - if [[ "$DEEP_FIND" -eq 0 ]]; then - DEEP_FIND=1 - continue; - fi - die "invalid ancestor $KOBJFILE for $arg" - fi + [[ -z "$PARENT" ]] && die "invalid ancestor $KOBJFILE for $file" KOBJFILE="$PARENT" done }