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 <jpoimboe@redhat.com>
This commit is contained in:
Josh Poimboeuf 2022-08-17 12:21:04 -07:00
parent 7861240f48
commit 1d7e8a74bb
1 changed files with 190 additions and 57 deletions

View File

@ -434,82 +434,215 @@ find_special_section_data() {
return return
} }
filter_parent_obj() # path of file, relative to dir
{ # adapted from https://stackoverflow.com/a/24848739
local dir="${1}" relpath() {
local file="${2}" 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() { find_parent_obj() {
dir="$(dirname "$1")" local file="$1"
absdir="$(readlink -f "$dir")"
pwddir="$(readlink -f .)" # common case: look in same directory
pdir="${absdir#$pwddir/}" find_parent_obj_in_dir "$file" "$(dirname "$file")"
file="$(basename "$1")" [[ -n $PARENT ]] && return
grepname="${1%.o}"
grepname="$grepname\\.o" # if we previously had a successful deep find, try that dir first
if [[ "$DEEP_FIND" -eq 1 ]]; then if [[ -n "$LAST_DEEP_FIND_DIR" ]]; then
num=0 find_parent_obj_in_dir "$file" "$LAST_DEEP_FIND_DIR"
if [[ -n "$last_deep_find" ]]; then [[ -n "$PARENT" ]] && return
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)"
fi fi
[[ "$num" -eq 0 ]] && PARENT="" && return # prevent known deep finds
[[ "$num" -gt 1 ]] && ERROR_IF_DIFF="two parent matches for $1" 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")" # check higher-level dirs
PARENT="$(basename "$parent")" local dir
PARENT="${PARENT#.}" dir="$(dirname "$file")"
PARENT="${PARENT%.cmd}" while [[ ! $dir -ef . ]]; do
[[ $dir != "." ]] && PARENT="$dir/$PARENT" dir="$(dirname "$dir")"
[[ ! -e "$PARENT" ]] && die "ERROR: can't find parent $PARENT for $1" 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() { find_kobj() {
arg="$1" local file="$1"
if [[ -n $OOT_MODULE ]]; then if [[ -n $OOT_MODULE ]]; then
KOBJFILE="$OOT_MODULE" KOBJFILE="$OOT_MODULE"
return return
fi fi
KOBJFILE="$arg" KOBJFILE="$file"
DEEP_FIND=0
ERROR_IF_DIFF= ERROR_IF_DIFF=
while true; do 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" find_parent_obj "$KOBJFILE"
[[ -n "$PARENT" ]] && DEEP_FIND=0 [[ -z "$PARENT" ]] && die "invalid ancestor $KOBJFILE for $file"
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
KOBJFILE="$PARENT" KOBJFILE="$PARENT"
done done
} }