Skip to content

Commit

Permalink
kpatch-build: support CONFIG_LTO_CLANG_THIN
Browse files Browse the repository at this point in the history
Support CONFIG_LTO_CLANG_THIN with ld.lld --lto-obj-path option.

With CONFIG_LTO_CLANG_THIN, .o files are LLVM IR binary, so CDO doesn't
work on .o file. To solve this issue, we CDO the thinlto files generated
by the --lto-obj-path option. Clang LTO generates the thinlto files
after cross file inline, so they are good candidates for CDO. See [1] for
more discussions about this.

To achieve this, we need:

  1. kpatch-build to update kernel Makefile(s) so it generates thinlto
     files;
  2. kpatch-build and kpatch-cc to save the thinlto file properly;
  3. kpatch-build to feed these thinlto files to CDO;
  4. The user need to supply vmlinux.o, from which we generate the symtab
     file. We need this because GLOBAL symbols may be marked as LOCAL in
     LTO vmlinux;
  4. A small workaround in CDO that ignores changes in init.text for
     vmlinux.o.thinlto.*

[1] dynup#1320

Signed-off-by: Song Liu <[email protected]>
  • Loading branch information
swine committed Jul 12, 2023
1 parent dc4b66a commit 22727a9
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 5 deletions.
15 changes: 15 additions & 0 deletions kpatch-build/create-diff-object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2942,6 +2942,21 @@ static void kpatch_mark_ignored_sections(struct kpatch_elf *kelf)
!strcmp(sec->name, "__patchable_function_entries"))
sec->ignore = 1;
}

/*
* With CONFIG_LTO_CLANG, we see some weird new functions,
* such as:
* __initstub__kmod_syscall__728_5326_bpf_syscall_sysctl_init7
* while the original function is very similar, like:
* __initstub__kmod_syscall__728_5324_bpf_syscall_sysctl_init7
*
* We don't have very good solution for it yet. To workaround
* the issue, it seems safe to ignore .init.text changes in
* vmlinux.o. This will skip such new functions.
*/
if (!strncmp(childobj, "vmlinux.o", 9) &&
!strncmp(sec->name, ".init.text", 10))
sec->ignore = 1;
}

sec = find_section_by_name(&kelf->sections, ".kpatch.ignore.sections");
Expand Down
79 changes: 74 additions & 5 deletions kpatch-build/kpatch-build
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ cleanup() {
[[ -e "$TEMPDIR/Makefile.modfinal" ]] && mv -f "$TEMPDIR/Makefile.modfinal" "$KERNEL_SRCDIR/scripts"
[[ -e "$TEMPDIR/setlocalversion" ]] && mv -f "$TEMPDIR/setlocalversion" "$KERNEL_SRCDIR/scripts"

# restore original Makefile.build if we updated it for the build
[[ -e "$TEMPDIR/Makefile.build" ]] && mv -f "$TEMPDIR/Makefile.build" "$KERNEL_SRCDIR/scripts"

# restore Makefile if we updated it for the build
[[ -e "$TEMPDIR/Makefile" ]] && mv -f "$TEMPDIR/Makefile" "$KERNEL_SRCDIR"

[[ "$DEBUG" -eq 0 ]] && rm -rf "$TEMPDIR"
rm -rf "$RPMTOPDIR"
unset KCFLAGS
Expand Down Expand Up @@ -1175,6 +1181,35 @@ if [[ -n "$CONFIG_DEBUG_INFO_BTF" ]]; then
fi
fi

if [[ -n "$CONFIG_LTO_CLANG" ]]; then
[[ -n "$CONFIG_LTO_CLANG_THIN" ]] || die "Only thin lto is supported. Try enable CONFIG_LTO_CLANG_THIN."

# With CONFIG_LTO_CLANG, vmlinux has LOCAL symbols for some GLOBAL
# functions. For example:
#
# readelf -s vmlinux | grep perf_event_bpf_event
#
# non-LTO:
# 124946: ffffffff81287070 858 FUNC GLOBAL DEFAULT 1 perf_event_bpf_event
#
# LTO:
# 122028: ffffffff81298cd0 861 FUNC LOCAL HIDDEN 1 perf_event_bpf_event
#
# To work around this, use vmlinux.o instead.
[[ -e "$VMLINUX" ]] || die "For kernel with CONFIG_LTO_CLANG, please supply vmlinux.o via -v|--vmlinux option."

# This is a heuristic: use -x to check vmlinux vs. vmlinux.o
[[ -x "$VMLINUX" ]] && die "For kernel with CONFIG_LTO_CLANG, please supply vmlinux.o instead of vmlinux via -v|--vmlinux option."

cp -f "$KERNEL_SRCDIR/Makefile" "$TEMPDIR/Makefile" || die
sed -i "s/--thinlto-cache-dir=\$(extmod_prefix).thinlto-cache/--lto-obj-path=vmlinux.o.thinlto.o/g" "$KERNEL_SRCDIR"/Makefile

cp -f "$KERNEL_SRCDIR/scripts/Makefile.build" "$TEMPDIR/Makefile.build" || die
sed -i "s/\$(ld_flags)/\$(ld_flags) --lto-obj-path=\$@.thinlto.o/g" "$KERNEL_SRCDIR"/scripts/Makefile.build

export KPATCH_CC_HANDLE_LTO=1
fi

if [[ -n "$CONFIG_CC_IS_CLANG" ]]; then
echo "WARNING: Clang support is experimental"
fi
Expand Down Expand Up @@ -1228,6 +1263,7 @@ unset KPATCH_GCC_TEMPDIR
if [[ -n "$CONFIG_CC_IS_CLANG" ]]; then
MAKEVARS+=("CC=${KPATCH_CC_PREFIX}${CLANG}")
MAKEVARS+=("HOSTCC=${HOSTCC:-${CLANG}}")
MAKEVARS+=("LLVM=1")
else
MAKEVARS+=("CC=${KPATCH_CC_PREFIX}${GCC}")
MAKEVARS+=("HOSTCC=${HOSTCC:-${GCC}}")
Expand Down Expand Up @@ -1297,9 +1333,14 @@ if [[ -n "$CONFIG_MODVERSIONS" ]]; then
trace_on
fi

if [[ -n "$CONFIG_LTO_CLANG" ]]; then
DIFF_OBJS="$TEMPDIR/thinlto_objs"
else
DIFF_OBJS="$TEMPDIR/changed_objs"
fi
# Read as words, no quotes.
# shellcheck disable=SC2013
for i in $(cat "$TEMPDIR/changed_objs")
for i in $(cat "$DIFF_OBJS")
do
mkdir -p "$TEMPDIR/patched/$(dirname "$i")" || die
cp -f "$BUILDDIR/$i" "$TEMPDIR/patched/$i" || die
Expand Down Expand Up @@ -1328,7 +1369,8 @@ if [[ -z "$MODNAME" ]] ; then

MODNAME="$(module_name_string "$MODNAME")"
fi
FILES="$(cat "$TEMPDIR/changed_objs")"
FILES="$(cat "$DIFF_OBJS")"

cd "$TEMPDIR" || die
mkdir output
declare -a objnames
Expand All @@ -1352,7 +1394,11 @@ for i in $FILES; do

mkdir -p "output/$(dirname "$i")"
cd "$BUILDDIR" || die
find_kobj "$i"
if [[ -z "$CONFIG_LTO_CLANG" ]] ; then
find_kobj "$i"
else
KOBJFILE=${i/.o.thinlto.o*/}
fi
cd "$TEMPDIR" || die
if [[ -e "orig/$i" ]]; then
if [[ -n $OOT_MODULE ]]; then
Expand All @@ -1361,6 +1407,19 @@ for i in $FILES; do
KOBJFILE_PATH="$OOT_MODULE"
SYMTAB="${TEMPDIR}/module/${KOBJFILE_NAME}.symtab"
SYMVERS_FILE="$TEMPDIR/Module.symvers"
elif [[ -n "$CONFIG_LTO_CLANG" ]] ; then
if [[ $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//-/_}"
KOBJFILE_PATH="${TEMPDIR}/module/$KOBJFILE.ko"
SYMTAB="${KOBJFILE_PATH}.symtab"
SYMVERS_FILE="$BUILDDIR/Module.symvers"
fi
elif [[ "$(basename "$KOBJFILE")" = vmlinux ]]; then
KOBJFILE_NAME=vmlinux
KOBJFILE_PATH="$VMLINUX"
Expand All @@ -1374,11 +1433,21 @@ for i in $FILES; do
SYMVERS_FILE="$BUILDDIR/Module.symvers"
fi

"$READELF" -s --wide "$KOBJFILE_PATH" > "$SYMTAB"
# With CONFIG_LTO_CLANG, multiple .thinlto files share a
# symtab file. Only generate the symtab file once.
[[ -e "$SYMTAB" ]] || "$READELF" -s --wide "$KOBJFILE_PATH" > "$SYMTAB"
if [[ "$ARCH" = "ppc64le" ]]; then
sed -ri 's/\s+\[<localentry>: 8\]//' "$SYMTAB"
fi

if [[ -n "$CONFIG_LTO_CLANG" ]] ; then
# skip .thinlto file that didn't change at all
diff "orig/$i" "patched/$i" 2> /dev/null && continue
# skip .thinlto file without any functions
num_func=$("$READELF" --symbols "orig/$i" | grep -c FUNC)
[[ $num_func -eq 0 ]] && continue
fi

# create-diff-object orig.o patched.o parent-name parent-symtab
# Module.symvers patch-mod-name output.o
"$TOOLSDIR"/create-diff-object $CDO_FLAGS "orig/$i" "patched/$i" "$KOBJFILE_NAME" \
Expand Down Expand Up @@ -1431,7 +1500,7 @@ fi
cd "$TEMPDIR/output" || die
# $KPATCH_LDFLAGS and result of find used as list, no quotes.
# shellcheck disable=SC2086,SC2046
"$LD" -r $KPATCH_LDFLAGS -o ../patch/tmp_output.o $(find . -name "*.o") 2>&1 | logger || die
"$LD" -r $KPATCH_LDFLAGS -o ../patch/tmp_output.o $(find . -name "*.o*") 2>&1 | logger || die

if [[ "$USE_KLP" -eq 1 ]]; then
cp -f "$TEMPDIR"/patch/tmp_output.o "$TEMPDIR"/patch/output.o || die
Expand Down
18 changes: 18 additions & 0 deletions kpatch-build/kpatch-cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ if [[ "$TOOLCHAINCMD" =~ ^(.*[-/])?(gcc|clang)$ ]] ; then
args+=(--warn-unresolved-symbols)
break
;;
*/.tmp_*.o)
# .tmp_*.o is used for single file modules.
# See "cmd_ld_single_m" in scripts/Makefile.build.
if [[ $KPATCH_CC_HANDLE_LTO -ne 0 ]] ; then
mkdir -p "$KPATCH_GCC_TEMPDIR/orig/$(dirname "$relobj")"
cp "${obj/.tmp_/}".thinlto.o* "$KPATCH_GCC_TEMPDIR/orig/$(dirname "$relobj")"
echo "${obj/.tmp_/}".thinlto.o* >> "$KPATCH_GCC_TEMPDIR/thinlto_objs"
fi
break
;;
*.o)
if [[ $KPATCH_CC_HANDLE_LTO -ne 0 ]] ; then
mkdir -p "$KPATCH_GCC_TEMPDIR/orig/$(dirname "$relobj")"
cp "$obj".thinlto* "$KPATCH_GCC_TEMPDIR/orig/$(dirname "$relobj")"
echo "$obj".thinlto.o* >> "$KPATCH_GCC_TEMPDIR/thinlto_objs"
fi
break
;;
*)
break
;;
Expand Down

0 comments on commit 22727a9

Please sign in to comment.