kpatch/kpatch-build/kpatch-build
Joe Lawrence a26b2af2a3 kpatch-build: set EXTRAVERSION and not localversion for RH kernels
There are some Red Hat kernel NVR combinations like
"kernel-5.13.0-0.rc4.33.el9.x86_64" that don't work well with our srpm
localversion strategy and end up botching the utsrelease.h file... which
allows for kpatch builds, but the module loader rightly rejects the
vermagic mismatch.

An ordinary rpmbuild sets up the kernel Makefile with:

  # make sure EXTRAVERSION says what we want it to say
  # Trim the release if this is a CI build, since KERNELVERSION is limited to 64 characters
  ShortRel=$(perl -e "print \"%{release}\" =~ s/\.pr\.[0-9A-Fa-f]{32}//r")
  perl -p -i -e "s/^EXTRAVERSION.*/EXTRAVERSION = -${ShortRel}.%{_target_cpu}${Variant:++${Variant}}/" Makefile

The simplest fix is just adding the version string to the kernel
Makefile EXTRAVERSION as rpmbuild would do (minus the perl voodoo).

Fixes: #1196
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
2021-07-02 16:34:27 -04:00

1223 lines
38 KiB
Bash
Executable File

#!/bin/bash
#
# kpatch build script
#
# Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
# Copyright (C) 2013,2014 Josh Poimboeuf <jpoimboe@redhat.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
# 02110-1301, USA.
# This script takes a patch based on the version of the kernel
# currently running and creates a kernel module that will
# replace modified functions in the kernel such that the
# patched code takes effect.
# This script:
# - Either uses a specified kernel source directory or downloads the kernel
# source package for the currently running kernel
# - Unpacks and prepares the source package for building if necessary
# - Builds the base kernel or module
# - Builds the patched kernel/module and monitors changed objects
# - Builds the patched objects with gcc flags -f[function|data]-sections
# - Runs kpatch tools to create and link the patch kernel module
set -o pipefail
BASE="$PWD"
SCRIPTDIR="$(readlink -f "$(dirname "$(type -p "$0")")")"
ARCH="$(uname -m)"
CPUS="$(getconf _NPROCESSORS_ONLN)"
CACHEDIR="${CACHEDIR:-$HOME/.kpatch}"
SRCDIR="$CACHEDIR/src"
RPMTOPDIR="$CACHEDIR/buildroot"
VERSIONFILE="$CACHEDIR/version"
TEMPDIR="$CACHEDIR/tmp"
LOGFILE="$CACHEDIR/build.log"
RELEASE_FILE=/etc/os-release
DEBUG=0
SKIPCLEANUP=0
SKIPCOMPILERCHECK=0
ARCH_KCFLAGS=""
DEBUG_KCFLAGS=""
declare -a PATCH_LIST
APPLIED_PATCHES=0
OOT_MODULE=
KLP_REPLACE=1
warn() {
echo "ERROR: $1" >&2
}
die() {
if [[ -z "$1" ]]; then
msg="kpatch build failed"
else
msg="$1"
fi
if [[ -e "$LOGFILE" ]]; then
warn "$msg. Check $LOGFILE for more details."
else
warn "$msg."
fi
exit 1
}
logger() {
local to_stdout=${1:-0}
if [[ $DEBUG -ge 2 ]] || [[ "$to_stdout" -eq 1 ]]; then
# Log to both stdout and the logfile
tee -a "$LOGFILE"
else
# Log only to the logfile
cat >> "$LOGFILE"
fi
}
verify_patch_files() {
local path
local dir
local ret=0
for patch in "${PATCH_LIST[@]}"; do
for path in $(lsdiff "$patch" 2>/dev/null); do
dir=$(dirname "$path")
ext="${path##*.}"
if [[ "$dir" =~ ^lib$ ]] || [[ "$dir" =~ ^lib/ ]] ; then
warn "$patch: unsupported patch to lib/: $path"
ret=1
fi
if [[ "$ext" == "S" ]] ; then
warn "$patch: unsupported patch to assembly: $path"
ret=1
fi
done
done
[[ $ret == 1 ]] && die "Unsupported changes detected"
}
apply_patches() {
local patch
for patch in "${PATCH_LIST[@]}"; do
patch -N -p1 --dry-run < "$patch" 2>&1 | logger || die "$patch file failed to apply"
patch -N -p1 < "$patch" 2>&1 | logger || die "$patch file failed to apply"
(( APPLIED_PATCHES++ ))
done
}
remove_patches() {
local patch
local idx
for (( ; APPLIED_PATCHES>0; APPLIED_PATCHES-- )); do
idx=$(( APPLIED_PATCHES - 1))
patch="${PATCH_LIST[$idx]}"
patch -p1 -R -d "$SRCDIR" < "$patch" &> /dev/null
done
# If $SRCDIR was a git repo, make sure git actually sees that
# we've reverted our patch(es).
[[ -d "$SRCDIR/.git" ]] && (cd "$SRCDIR" && git update-index -q --refresh)
}
cleanup() {
rm -f "$SRCDIR/.scmversion"
remove_patches
# restore original vmlinux if it was overwritten by sourcedir build
[[ -e "$TEMPDIR/vmlinux" ]] && mv -f "$TEMPDIR/vmlinux" "$SRCDIR/"
# restore original link-vmlinux.sh if we updated it for the build
[[ -e "$TEMPDIR/link-vmlinux.sh" ]] && mv -f "$TEMPDIR/link-vmlinux.sh" "$SRCDIR/scripts"
# restore original Makefile.modfinal if we updated it for the build
[[ -e "$TEMPDIR/Makefile.modfinal" ]] && mv -f "$TEMPDIR/Makefile.modfinal" "$SRCDIR/scripts"
[[ "$DEBUG" -eq 0 ]] && rm -rf "$TEMPDIR"
rm -rf "$RPMTOPDIR"
unset KCFLAGS
unset KCPPFLAGS
}
clean_cache() {
rm -rf "${CACHEDIR:?}"/*
mkdir -p "$TEMPDIR" || die "Couldn't create $TEMPDIR"
}
check_pipe_status() {
rc="${PIPESTATUS[0]}"
if [[ "$rc" = 139 ]]; then
# There doesn't seem to be a consistent/portable way of
# accessing the last executed command in bash, so just
# pass in the script name for now..
warn "$1 SIGSEGV"
if ls core* &> /dev/null; then
cp core* /tmp
die "core file at /tmp/$(ls core*)"
fi
die "There was a SIGSEGV, but no core dump was found in the current directory. Depending on your distro you might find it in /var/lib/systemd/coredump or /var/crash."
fi
}
kernel_version_gte() {
[ "${ARCHVERSION//-*/}" = "$(echo -e "${ARCHVERSION//-*}\\n$1" | sort -rV | head -n1)" ]
}
kernel_is_rhel() {
[[ "$ARCHVERSION" =~ \.el[789] ]]
}
rhel_kernel_version_gte() {
[ "${ARCHVERSION}" = "$(echo -e "${ARCHVERSION}\\n$1" | sort -rV | head -n1)" ]
}
# klp.arch relocations were supported prior to v5.8
# and prior to 4.18.0-240.el8
use_klp_arch()
{
if kernel_is_rhel; then
! rhel_kernel_version_gte 4.18.0-240.el8
else
! kernel_version_gte 5.8.0
fi
}
rhel_kernel_version_gte() {
[ "${ARCHVERSION}" = "$(echo -e "${ARCHVERSION}\\n$1" | sort -rV | head -n1)" ]
}
# klp.arch relocations were supported prior to v5.8
# and prior to 4.18.0-284.el8
use_klp_arch()
{
if kernel_is_rhel; then
! rhel_kernel_version_gte 4.18.0-284.el8
else
! kernel_version_gte 5.8.0
fi
}
support_klp_replace()
{
if kernel_is_rhel; then
rhel_kernel_version_gte 4.18.0-193.el8
else
kernel_version_gte 5.1.0
fi
}
find_dirs() {
if [[ -e "$SCRIPTDIR/create-diff-object" ]]; then
# git repo
TOOLSDIR="$SCRIPTDIR"
DATADIR="$(readlink -f "$SCRIPTDIR/../kmod")"
PLUGINDIR="$(readlink -f "$SCRIPTDIR/gcc-plugins")"
elif [[ -e "$SCRIPTDIR/../libexec/kpatch/create-diff-object" ]]; then
# installation path
TOOLSDIR="$(readlink -f "$SCRIPTDIR/../libexec/kpatch")"
DATADIR="$(readlink -f "$SCRIPTDIR/../share/kpatch")"
PLUGINDIR="$TOOLSDIR"
else
return 1
fi
}
find_core_symvers() {
SYMVERSFILE=""
if [[ -e "$SCRIPTDIR/create-diff-object" ]]; then
# git repo
SYMVERSFILE="$DATADIR/core/Module.symvers"
elif [[ -e "$SCRIPTDIR/../libexec/kpatch/create-diff-object" ]]; then
# installation path
if [[ -e "$SCRIPTDIR/../lib/kpatch/$ARCHVERSION/Module.symvers" ]]; then
SYMVERSFILE="$(readlink -f "$SCRIPTDIR/../lib/kpatch/$ARCHVERSION/Module.symvers")"
elif [[ -e /lib/modules/$ARCHVERSION/extra/kpatch/Module.symvers ]]; then
SYMVERSFILE="$(readlink -f "/lib/modules/$ARCHVERSION/extra/kpatch/Module.symvers")"
fi
fi
[[ -e "$SYMVERSFILE" ]]
}
gcc_version_from_file() {
readelf -p .comment "$1" | grep -m 1 -o 'GCC:.*'
}
gcc_version_check() {
local target="$1"
local c="$TEMPDIR/test.c" o="$TEMPDIR/test.o"
local out gccver kgccver
# gcc --version varies between distributions therefore extract version
# by compiling a test file and compare it to vmlinux's version.
echo 'void main(void) {}' > "$c"
out="$(gcc -c -pg -ffunction-sections -o "$o" "$c" 2>&1)"
gccver="$(gcc_version_from_file "$o")"
kgccver="$(gcc_version_from_file "$target")"
if [[ -n "$out" ]]; then
warn "gcc >= 4.8 required for -pg -ffunction-settings"
echo "gcc output: $out"
return 1
fi
out="$(gcc -c -gz=none -o "$o" "$c" 2>&1)"
if [[ -z "$out" ]]; then
DEBUG_KCFLAGS="-gz=none"
fi
rm -f "$c" "$o"
# ensure gcc version matches that used to build the kernel
if [[ "$gccver" != "$kgccver" ]]; then
warn "gcc/kernel version mismatch"
echo "gcc version: $gccver"
echo "kernel version: $kgccver"
echo "Install the matching gcc version (recommended) or use --skip-compiler-check"
echo "to skip the version matching enforcement (not recommended)"
return 1
fi
return
}
clang_version_from_file() {
readelf -p .comment "$1" | grep -m 1 -Eo 'clang version [0-9.]+'
}
clang_version_check() {
local target="$1"
local clangver kclangver
clangver=$(clang --version | grep -m 1 -Eo 'clang version [0-9.]+')
kclangver="$(clang_version_from_file "$target")"
# ensure clang version matches that used to build the kernel
if [[ "$clangver" != "$kclangver" ]]; then
warn "clang/kernel version mismatch"
echo "clang version: $clangver"
echo "kernel version: $kclangver"
echo "Install the matching clang version (recommended) or use --skip-compiler-check"
echo "to skip the version matching enforcement (not recommended)"
return 1
fi
return
}
find_special_section_data_ppc64le() {
[[ "$CONFIG_JUMP_LABEL" -eq 0 ]] && AWK_OPTIONS="-vskip_j=1"
SPECIAL_VARS="$(readelf -wi "$VMLINUX" |
gawk --non-decimal-data '
BEGIN { f = b = e = j = 0 }
# Set state if name matches
f == 0 && /DW_AT_name.* fixup_entry[[:space:]]*$/ {f = 1; next}
b == 0 && /DW_AT_name.* bug_entry[[:space:]]*$/ {b = 1; next}
e == 0 && /DW_AT_name.* exception_table_entry[[:space:]]*$/ {e = 1; next}
j == 0 && /DW_AT_name.* jump_entry[[:space:]]*$/ {j = 1; next}
# Reset state unless this abbrev describes the struct size
f == 1 && !/DW_AT_byte_size/ { f = 0; next }
b == 1 && !/DW_AT_byte_size/ { b = 0; next }
e == 1 && !/DW_AT_byte_size/ { e = 0; next }
j == 1 && !/DW_AT_byte_size/ { j = 0; next }
# Now that we know the size, stop parsing for it
f == 1 {printf("export FIXUP_STRUCT_SIZE=%d\n", $4); f = 2}
b == 1 {printf("export BUG_STRUCT_SIZE=%d\n", $4); b = 2}
e == 1 {printf("export EX_STRUCT_SIZE=%d\n", $4); e = 2}
j == 1 {printf("export JUMP_STRUCT_SIZE=%d\n", $4); j = 2}
# Bail out once we have everything
f == 2 && b == 2 && e == 2 && (j == 2 || skip_j) {exit}')"
[[ -n "$SPECIAL_VARS" ]] && eval "$SPECIAL_VARS"
[[ -z "$FIXUP_STRUCT_SIZE" ]] && die "can't find special struct fixup_entry size"
[[ -z "$BUG_STRUCT_SIZE" ]] && die "can't find special struct bug_entry size"
[[ -z "$EX_STRUCT_SIZE" ]] && die "can't find special struct exception_table_entry size"
[[ -z "$JUMP_STRUCT_SIZE" && "$CONFIG_JUMP_LABEL" -ne 0 ]] && die "can't find special struct jump_entry size"
return
}
find_special_section_data() {
if [[ "$ARCH" = "ppc64le" ]]; then
find_special_section_data_ppc64le
return
fi
[[ "$CONFIG_PARAVIRT" -eq 0 ]] && AWK_OPTIONS="-vskip_p=1"
[[ "$CONFIG_UNWINDER_ORC" -eq 0 ]] && AWK_OPTIONS="$AWK_OPTIONS -vskip_o=1"
[[ "$CONFIG_JUMP_LABEL" -eq 0 ]] && AWK_OPTIONS="$AWK_OPTIONS -vskip_j=1"
! kernel_version_gte 5.10.0 && AWK_OPTIONS="$AWK_OPTIONS -vskip_s=1"
# If $AWK_OPTIONS are blank gawk would treat "" as a blank script
# shellcheck disable=SC2086
SPECIAL_VARS="$(readelf -wi "$VMLINUX" |
gawk --non-decimal-data $AWK_OPTIONS '
BEGIN { a = b = p = e = o = j = s = 0 }
# Set state if name matches
a == 0 && /DW_AT_name.* alt_instr[[:space:]]*$/ {a = 1; next}
b == 0 && /DW_AT_name.* bug_entry[[:space:]]*$/ {b = 1; next}
p == 0 && /DW_AT_name.* paravirt_patch_site[[:space:]]*$/ {p = 1; next}
e == 0 && /DW_AT_name.* exception_table_entry[[:space:]]*$/ {e = 1; next}
o == 0 && /DW_AT_name.* orc_entry[[:space:]]*$/ {o = 1; next}
j == 0 && /DW_AT_name.* jump_entry[[:space:]]*$/ {j = 1; next}
s == 0 && /DW_AT_name.* static_call_site[[:space:]]*$/ {s = 1; next}
# Reset state unless this abbrev describes the struct size
a == 1 && !/DW_AT_byte_size/ { a = 0; next }
b == 1 && !/DW_AT_byte_size/ { b = 0; next }
p == 1 && !/DW_AT_byte_size/ { p = 0; next }
e == 1 && !/DW_AT_byte_size/ { e = 0; next }
o == 1 && !/DW_AT_byte_size/ { o = 0; next }
j == 1 && !/DW_AT_byte_size/ { j = 0; next }
s == 1 && !/DW_AT_byte_size/ { s = 0; next }
# Now that we know the size, stop parsing for it
a == 1 {printf("export ALT_STRUCT_SIZE=%d\n", $4); a = 2}
b == 1 {printf("export BUG_STRUCT_SIZE=%d\n", $4); b = 2}
p == 1 {printf("export PARA_STRUCT_SIZE=%d\n", $4); p = 2}
e == 1 {printf("export EX_STRUCT_SIZE=%d\n", $4); e = 2}
o == 1 {printf("export ORC_STRUCT_SIZE=%d\n", $4); o = 2}
j == 1 {printf("export JUMP_STRUCT_SIZE=%d\n", $4); j = 2}
s == 1 {printf("export STATIC_CALL_STRUCT_SIZE=%d\n", $4); s = 2}
# Bail out once we have everything
a == 2 && b == 2 && (p == 2 || skip_p) && e == 2 && (o == 2 || skip_o) && (j == 2 || skip_j) && (s == 2 || skip_s) {exit}')"
[[ -n "$SPECIAL_VARS" ]] && eval "$SPECIAL_VARS"
[[ -z "$ALT_STRUCT_SIZE" ]] && die "can't find special struct alt_instr size"
[[ -z "$BUG_STRUCT_SIZE" ]] && die "can't find special struct bug_entry size"
[[ -z "$EX_STRUCT_SIZE" ]] && die "can't find special struct paravirt_patch_site size"
[[ -z "$PARA_STRUCT_SIZE" && "$CONFIG_PARAVIRT" -ne 0 ]] && die "can't find special struct paravirt_patch_site size"
[[ -z "$ORC_STRUCT_SIZE" && "$CONFIG_UNWINDER_ORC" -ne 0 ]] && die "can't find special struct orc_entry size"
[[ -z "$JUMP_STRUCT_SIZE" && "$CONFIG_JUMP_LABEL" -ne 0 ]] && die "can't find special struct jump_entry size"
[[ -z "$STATIC_CALL_STRUCT_SIZE" ]] && kernel_version_gte 5.10.0 && die "can't find special struct static_call_site size"
return
}
filter_parent_obj()
{
local dir="${1}"
local file="${2}"
grep -v "\.mod\.cmd$" | grep -Fv "${dir}/.${file}.cmd"
}
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)"
fi
[[ "$num" -eq 0 ]] && PARENT="" && return
[[ "$num" -gt 1 ]] && ERROR_IF_DIFF="two parent matches for $1"
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"
}
find_kobj() {
arg="$1"
KOBJFILE="$arg"
DEEP_FIND=0
ERROR_IF_DIFF=
while true; do
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
KOBJFILE="$PARENT"
done
}
# Only allow alphanumerics and '_' and '-' in the module name. Everything else
# is replaced with '-'. Also truncate to 48 chars so the full name fits in the
# kernel's 56-byte module name array.
module_name_string() {
echo "${1//[^a-zA-Z0-9_-]/-}" | cut -c 1-48
}
usage() {
echo "usage: $(basename "$0") [options] <patch1 ... patchN>" >&2
echo " patchN Input patchfile(s)" >&2
echo " -h, --help Show this help message" >&2
echo " -a, --archversion Specify the kernel arch version" >&2
echo " -r, --sourcerpm Specify kernel source RPM" >&2
echo " -s, --sourcedir Specify kernel source directory" >&2
echo " -c, --config Specify kernel config file" >&2
echo " -v, --vmlinux Specify original vmlinux" >&2
echo " -j, --jobs Specify the number of make jobs" >&2
echo " -t, --target Specify custom kernel build targets" >&2
echo " -n, --name Specify the name of the kpatch module" >&2
echo " -o, --output Specify output folder" >&2
echo " -d, --debug Enable 'xtrace' and keep scratch files" >&2
echo " in <CACHEDIR>/tmp" >&2
echo " (can be specified multiple times)" >&2
echo " -e, --oot-module Enable patching out-of-tree module," >&2
echo " specify current version of module" >&2
echo " -R, --non-replace Disable replace patch (replace is on by default)" >&2
echo " --skip-cleanup Skip post-build cleanup" >&2
echo " --skip-compiler-check Skip compiler version matching check" >&2
echo " (not recommended)" >&2
}
options="$(getopt -o ha:r:s:c:v:j:t:n:o:de:R -l "help,archversion:,sourcerpm:,sourcedir:,config:,vmlinux:,jobs:,target:,name:,output:,oot-module:,debug,skip-gcc-check,skip-compiler-check,skip-cleanup,non-replace" -- "$@")" || die "getopt failed"
eval set -- "$options"
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-a|--archversion)
ARCHVERSION="$2"
shift
;;
-r|--sourcerpm)
[[ ! -f "$2" ]] && die "source rpm '$2' not found"
SRCRPM="$(readlink -f "$2")"
shift
;;
-s|--sourcedir)
[[ ! -d "$2" ]] && die "source dir '$2' not found"
USERSRCDIR="$(readlink -f "$2")"
shift
;;
-c|--config)
[[ ! -f "$2" ]] && die "config file '$2' not found"
CONFIGFILE="$(readlink -f "$2")"
shift
;;
-v|--vmlinux)
[[ ! -f "$2" ]] && die "vmlinux file '$2' not found"
VMLINUX="$(readlink -f "$2")"
shift
;;
-j|--jobs)
[[ ! "$2" -gt 0 ]] && die "Invalid number of make jobs '$2'"
CPUS="$2"
shift
;;
-t|--target)
TARGETS="$TARGETS $2"
shift
;;
-n|--name)
MODNAME="$(module_name_string "$2")"
shift
;;
-o|--output)
[[ ! -d "$2" ]] && die "output dir '$2' not found"
BASE="$(readlink -f "$2")"
shift
;;
-d|--debug)
DEBUG=$((DEBUG + 1))
if [[ $DEBUG -eq 1 ]]; then
echo "DEBUG mode enabled"
fi
;;
-e|--oot-module)
[[ ! -f "$2" ]] && die "out-of-tree module '$2' not found"
OOT_MODULE="$(readlink -f "$2")"
shift
;;
-R|--non-replace)
KLP_REPLACE=0
;;
--skip-cleanup)
echo "Skipping cleanup"
SKIPCLEANUP=1
;;
--skip-gcc-check)
echo "DEPRECATED: --skip-gcc-check is deprecated, use --skip-compiler-check instead"
;&
--skip-compiler-check)
echo "WARNING: Skipping compiler version matching check (not recommended)"
SKIPCOMPILERCHECK=1
;;
*)
[[ "$1" = "--" ]] && shift && continue
[[ ! -f "$1" ]] && die "patch file '$1' not found"
PATCH_LIST+=("$(readlink -f "$1")")
;;
esac
shift
done
if [[ ${#PATCH_LIST[@]} -eq 0 ]]; then
warn "no patch file(s) specified"
usage
exit 1
fi
if [[ $DEBUG -eq 1 ]] || [[ $DEBUG -ge 3 ]]; then
set -o xtrace
fi
if [[ -n "$ARCHVERSION" ]] && [[ -n "$VMLINUX" ]]; then
die "--archversion is incompatible with --vmlinux"
fi
if [[ -n "$SRCRPM" ]]; then
if [[ -n "$ARCHVERSION" ]]; then
warn "--archversion is incompatible with --sourcerpm"
exit 1
fi
rpmname="$(basename "$SRCRPM")"
ARCHVERSION="${rpmname%.src.rpm}.$(uname -m)"
ARCHVERSION="${ARCHVERSION#kernel-}"
ARCHVERSION="${ARCHVERSION#alt-}"
fi
if [[ -n "$OOT_MODULE" ]] && [[ -z "$USERSRCDIR" ]]; then
warn "--oot-module requires --sourcedir"
exit 1
fi
# ensure cachedir and tempdir are setup properly and cleaned
mkdir -p "$TEMPDIR" || die "Couldn't create $TEMPDIR"
rm -rf "${TEMPDIR:?}"/*
rm -f "$LOGFILE"
if [[ -n "$USERSRCDIR" ]]; then
if [[ -n "$ARCHVERSION" ]]; then
warn "--archversion is incompatible with --sourcedir"
exit 1
fi
SRCDIR="$USERSRCDIR"
if [[ -z "$OOT_MODULE" ]]; then
[[ -z "$VMLINUX" ]] && VMLINUX="$SRCDIR"/vmlinux
[[ ! -e "$VMLINUX" ]] && die "can't find vmlinux"
# Extract the target kernel version from vmlinux in this case.
ARCHVERSION="$(strings "$VMLINUX" | grep -m 1 -e "^Linux version" | awk '{ print($3); }')"
else
ARCHVERSION="$(modinfo -F vermagic "$OOT_MODULE" | awk '{print $1}')"
fi
fi
[[ -z "$ARCHVERSION" ]] && ARCHVERSION="$(uname -r)"
[[ "$SKIPCLEANUP" -eq 0 ]] && trap cleanup EXIT INT TERM HUP
KVER="${ARCHVERSION%%-*}"
if [[ "$ARCHVERSION" =~ - ]]; then
KREL="${ARCHVERSION##*-}"
KREL="${KREL%.*}"
fi
[[ "$ARCHVERSION" =~ .el7a. ]] && ALT="-alt"
[[ -z "$TARGETS" ]] && TARGETS="vmlinux modules"
# Don't check external file.
# shellcheck disable=SC1090
[[ -f "$RELEASE_FILE" ]] && source "$RELEASE_FILE"
DISTRO="$ID"
if [[ "$DISTRO" = fedora ]] || [[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = ol ]] || [[ "$DISTRO" = centos ]]; then
[[ -z "$VMLINUX" ]] && VMLINUX="/usr/lib/debug/lib/modules/$ARCHVERSION/vmlinux"
[[ -e "$VMLINUX" ]] || die "kernel-debuginfo-$ARCHVERSION not installed"
export PATH="/usr/lib64/ccache:$PATH"
elif [[ "$DISTRO" = ubuntu ]] || [[ "$DISTRO" = debian ]]; then
[[ -z "$VMLINUX" ]] && VMLINUX="/usr/lib/debug/boot/vmlinux-$ARCHVERSION"
if [[ "$DISTRO" = ubuntu ]]; then
[[ -e "$VMLINUX" ]] || die "linux-image-$ARCHVERSION-dbgsym not installed"
elif [[ "$DISTRO" = debian ]]; then
[[ -e "$VMLINUX" ]] || die "linux-image-$ARCHVERSION-dbg not installed"
fi
export PATH="/usr/lib/ccache:$PATH"
fi
find_dirs || die "can't find supporting tools"
if [[ -n "$USERSRCDIR" ]]; then
echo "Using source directory at $USERSRCDIR"
# save original vmlinux before it gets overwritten by sourcedir build
if [[ -z "$OOT_MODULE" ]] && [[ "$VMLINUX" -ef "$SRCDIR"/vmlinux ]]; then
cp -f "$VMLINUX" "$TEMPDIR/vmlinux" || die
VMLINUX="$TEMPDIR/vmlinux"
fi
# For external modules, use the running kernel's config
if [[ -n "$OOT_MODULE" ]] && [[ -z "$CONFIGFILE" ]]; then
CONFIGFILE="/boot/config-${ARCHVERSION}"
fi
elif [[ -e "$SRCDIR"/.config ]] && [[ -e "$VERSIONFILE" ]] && [[ "$(cat "$VERSIONFILE")" = "$ARCHVERSION" ]]; then
echo "Using cache at $SRCDIR"
else
if [[ "$DISTRO" = fedora ]] || [[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = ol ]] || [[ "$DISTRO" = centos ]]; then
echo "Fedora/Red Hat distribution detected"
clean_cache
echo "Downloading kernel source for $ARCHVERSION"
if [[ -z "$SRCRPM" ]]; then
if [[ "$DISTRO" = fedora ]]; then
wget -P "$TEMPDIR" "http://kojipkgs.fedoraproject.org/packages/kernel/$KVER/$KREL/src/kernel-$KVER-$KREL.src.rpm" 2>&1 | logger || die
else
command -v yumdownloader &>/dev/null || die "yumdownloader (yum-utils or dnf-utils) not installed"
yumdownloader --source --destdir "$TEMPDIR" "kernel$ALT-$KVER-$KREL" 2>&1 | logger || die
fi
SRCRPM="$TEMPDIR/kernel$ALT-$KVER-$KREL.src.rpm"
fi
echo "Unpacking kernel source"
rpm -D "_topdir $RPMTOPDIR" -ivh "$SRCRPM" 2>&1 | logger || die
rpmbuild -D "_topdir $RPMTOPDIR" -bp --nodeps "--target=$(uname -m)" "$RPMTOPDIR"/SPECS/kernel$ALT.spec 2>&1 | logger ||
die "rpmbuild -bp failed. you may need to run 'yum-builddep kernel' first."
mv "$RPMTOPDIR"/BUILD/kernel-*/linux-* "$SRCDIR" 2>&1 | logger || die
rm -rf "$RPMTOPDIR"
rm -rf "$SRCDIR/.git"
if [[ "$ARCHVERSION" == *-* ]]; then
sed -i "s/^EXTRAVERSION.*/EXTRAVERSION = -${ARCHVERSION##*-}/" "$SRCDIR/Makefile" || die
fi
echo "$ARCHVERSION" > "$VERSIONFILE" || die
[[ -z "$CONFIGFILE" ]] && CONFIGFILE="$SRCDIR/configs/kernel$ALT-$KVER-$ARCH.config"
(cd "$SRCDIR" && make mrproper 2>&1 | logger) || die
elif [[ "$DISTRO" = ubuntu ]] || [[ "$DISTRO" = debian ]]; then
echo "Debian/Ubuntu distribution detected"
if [[ "$DISTRO" = ubuntu ]]; then
# url may be changed for a different mirror
url="http://archive.ubuntu.com/ubuntu/pool/main/l"
sublevel="SUBLEVEL = 0"
elif [[ "$DISTRO" = debian ]]; then
# url may be changed for a different mirror
url="http://ftp.debian.org/debian/pool/main/l"
sublevel="SUBLEVEL ="
fi
pkgname="$(dpkg-query -W -f='${Source}' "linux-image-$ARCHVERSION" | sed s/-signed//)"
pkgver="$(dpkg-query -W -f='${Version}' "linux-image-$ARCHVERSION")"
dscname="${pkgname}_${pkgver}.dsc"
clean_cache
cd "$TEMPDIR" || die
echo "Downloading and unpacking the kernel source for $ARCHVERSION"
# Download source deb pkg
(dget -u "$url/${pkgname}/${dscname}" 2>&1) | logger || die "dget: Could not fetch/unpack $url/${pkgname}/${dscname}"
mv "${pkgname}-$KVER" "$SRCDIR" || die
[[ -z "$CONFIGFILE" ]] && CONFIGFILE="/boot/config-${ARCHVERSION}"
if [[ "$ARCHVERSION" == *-* ]]; then
echo "-${ARCHVERSION#*-}" > "$SRCDIR/localversion" || die
fi
# for some reason the Ubuntu kernel versions don't follow the
# upstream SUBLEVEL; they are always at SUBLEVEL 0
sed -i "s/^SUBLEVEL.*/${sublevel}/" "$SRCDIR/Makefile" || die
echo "$ARCHVERSION" > "$VERSIONFILE" || die
else
die "Unsupported distribution"
fi
fi
[[ -z "$CONFIGFILE" ]] && CONFIGFILE="$SRCDIR"/.config
[[ ! -e "$CONFIGFILE" ]] && die "can't find config file"
if [[ ! "$CONFIGFILE" -ef "$SRCDIR"/.config ]] ; then
cp -f "$CONFIGFILE" "$SRCDIR/.config" || die
fi
# kernel option checking
grep -q "CONFIG_DEBUG_INFO=y" "$CONFIGFILE" || die "kernel doesn't have 'CONFIG_DEBUG_INFO' enabled"
# Build variables - Set some defaults, then adjust features
# according to .config and kernel version
KBUILD_EXTRA_SYMBOLS=""
KPATCH_LDFLAGS=""
USE_KLP=0
USE_KLP_ARCH=0
CONFIG_PARAVIRT=0
CONFIG_UNWINDER_ORC=0
CONFIG_JUMP_LABEL=0
CONFIG_MODVERSIONS=0
CONFIG_CC_IS_CLANG=0
CONFIG_LD_IS_LLD=0
if grep -q "CONFIG_LIVEPATCH=y" "$CONFIGFILE" && (kernel_is_rhel || kernel_version_gte 4.9.0); then
USE_KLP=1
if use_klp_arch; then
USE_KLP_ARCH=1
KPATCH_LDFLAGS="--unique=.parainstructions --unique=.altinstructions"
CDO_FLAGS="--klp-arch"
fi
if [[ "$KLP_REPLACE" -eq 1 ]] ; then
support_klp_replace || die "The kernel doesn't support klp replace"
else
export KBUILD_CFLAGS_MODULE="$KBUILD_CFLAGS_MODULE -DKLP_REPLACE_ENABLE=false"
fi
else
# No support for livepatch in the kernel. Kpatch core module is needed.
# There may be ordering bugs, with jump labels and other special
# sections. Use with caution!
echo "WARNING: Use of kpatch core module (kpatch.ko) is deprecated! There may be bugs!" >&2
if [[ "$KLP_REPLACE" -eq 1 ]] ; then
die "kpatch core module (kpatch.ko) does not support replace, please add -R|--non-replace"
fi
find_core_symvers || die "unable to find Module.symvers for kpatch core module"
KBUILD_EXTRA_SYMBOLS="$SYMVERSFILE"
fi
# optional kernel configs:
grep -q "CONFIG_PARAVIRT=y" "$CONFIGFILE" && CONFIG_PARAVIRT=1
grep -q "CONFIG_UNWINDER_ORC=y" "$CONFIGFILE" && CONFIG_UNWINDER_ORC=1
grep -q "CONFIG_JUMP_LABEL=y" "$CONFIGFILE" && CONFIG_JUMP_LABEL=1
grep -q "CONFIG_MODVERSIONS=y" "$CONFIGFILE" && CONFIG_MODVERSIONS=1
grep -q "CONFIG_CC_IS_CLANG=y" "$CONFIGFILE" && CONFIG_CC_IS_CLANG=1
grep -q "CONFIG_LD_IS_LLD=y" "$CONFIGFILE" && CONFIG_LD_IS_LLD=1
# unsupported kernel option checking
grep -q "CONFIG_DEBUG_INFO_SPLIT=y" "$CONFIGFILE" && die "kernel option 'CONFIG_DEBUG_INFO_SPLIT' not supported"
grep -q "CONFIG_GCC_PLUGIN_LATENT_ENTROPY=y" "$CONFIGFILE" && die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported"
grep -q "CONFIG_GCC_PLUGIN_RANDSTRUCT=y" "$CONFIGFILE" && die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported"
# CONFIG_DEBUG_INFO_BTF invokes pahole, for which some versions don't
# support extended ELF sections. Disable the BTF typeinfo generation in
# link-vmlinux.sh and Makefile.modfinal since kpatch doesn't care about
# that anyway.
if grep -q "CONFIG_DEBUG_INFO_BTF=y" "$CONFIGFILE" ; then
cp -f "$SRCDIR/scripts/link-vmlinux.sh" "$TEMPDIR/link-vmlinux.sh" || die
sed -i 's/CONFIG_DEBUG_INFO_BTF/DISABLED_FOR_KPATCH_BUILD/g' "$SRCDIR"/scripts/link-vmlinux.sh || die
if [[ -e "$SRCDIR/scripts/Makefile.modfinal" ]]; then
cp -f "$SRCDIR/scripts/Makefile.modfinal" "$TEMPDIR/Makefile.modfinal" || die
sed -i 's/CONFIG_DEBUG_INFO_BTF_MODULES/DISABLED_FOR_KPATCH_BUILD/g' "$SRCDIR"/scripts/Makefile.modfinal || die
fi
fi
if [[ "$CONFIG_CC_IS_CLANG" -eq 1 ]]; then
echo "WARNING: Clang support is experimental"
fi
if [[ "$SKIPCOMPILERCHECK" -eq 0 ]]; then
if [[ -n "$OOT_MODULE" ]]; then
target="$OOT_MODULE"
else
target="$VMLINUX"
fi
if [[ "$CONFIG_CC_IS_CLANG" -eq 0 ]]; then
gcc_version_check "$target" || die
else
clang_version_check "$target" || die
fi
fi
echo "Testing patch file(s)"
cd "$SRCDIR" || die
verify_patch_files
apply_patches
remove_patches
cp -LR "$DATADIR/patch" "$TEMPDIR" || die
if [[ "$ARCH" = "ppc64le" ]]; then
ARCH_KCFLAGS="-mcmodel=large -fplugin=$PLUGINDIR/ppc64le-plugin.so"
fi
export KCFLAGS="-I$DATADIR/patch -ffunction-sections -fdata-sections \
$ARCH_KCFLAGS $DEBUG_KCFLAGS"
echo "Reading special section data"
find_special_section_data
if [[ $DEBUG -ge 4 ]]; then
export KPATCH_GCC_DEBUG=1
fi
echo "Building original source"
[[ -n "$OOT_MODULE" ]] || ./scripts/setlocalversion --save-scmversion || die
unset KPATCH_GCC_TEMPDIR
KPATCH_CC_PREFIX="$TOOLSDIR/kpatch-cc "
declare -a MAKEVARS
if [ "$CONFIG_CC_IS_CLANG" -eq 1 ]; then
MAKEVARS+=("CC=${KPATCH_CC_PREFIX}clang")
MAKEVARS+=("HOSTCC=clang")
else
MAKEVARS+=("CC=${KPATCH_CC_PREFIX}gcc")
fi
if [ "$CONFIG_LD_IS_LLD" -eq 1 ]; then
MAKEVARS+=("LD=${KPATCH_CC_PREFIX}ld.lld")
MAKEVARS+=("HOSTLD=ld.lld")
else
MAKEVARS+=("LD=${KPATCH_CC_PREFIX}ld")
fi
# $TARGETS used as list, no quotes.
# shellcheck disable=SC2086
make "${MAKEVARS[@]}" "-j$CPUS" $TARGETS 2>&1 | logger || die
# Save original module symvers
cp -f "$SRCDIR/Module.symvers" "$TEMPDIR/Module.symvers" || die
echo "Building patched source"
apply_patches
mkdir -p "$TEMPDIR/orig" "$TEMPDIR/patched"
KPATCH_GCC_TEMPDIR="$TEMPDIR"
export KPATCH_GCC_TEMPDIR
KPATCH_GCC_SRCDIR="$SRCDIR"
export KPATCH_GCC_SRCDIR
# $TARGETS used as list, no quotes.
# shellcheck disable=SC2086
KBUILD_MODPOST_WARN=1 make "${MAKEVARS[@]}" "-j$CPUS" $TARGETS 2>&1 | logger || die
# source.c:(.section+0xFF): undefined reference to `symbol'
grep "undefined reference" "$LOGFILE" | sed -r "s/^.*\`(.*)'$/\\1/" \
>"${TEMPDIR}"/undefined_references
# WARNING: "symbol" [path/to/module.ko] undefined!
grep "undefined!" "$LOGFILE" | cut -d\" -f2 >>"${TEMPDIR}"/undefined_references
if [[ ! -e "$TEMPDIR/changed_objs" ]]; then
die "no changed objects found"
fi
[[ -n "$OOT_MODULE" ]] || grep -q vmlinux "$SRCDIR/Module.symvers" || die "truncated $SRCDIR/Module.symvers file"
if [[ "$CONFIG_MODVERSIONS" -eq 1 ]]; then
while read -ra sym_line; do
if [[ ${#sym_line[@]} -lt 4 ]]; then
die "Malformed ${TEMPDIR}/Module.symvers file"
fi
sym=${sym_line[1]}
read -ra patched_sym_line <<< "$(grep "\s$sym\s" "$SRCDIR/Module.symvers")"
if [[ ${#patched_sym_line[@]} -lt 4 ]]; then
die "Malformed symbol entry for ${sym} in ${SRCDIR}/Module.symvers file"
fi
# Assume that both original and patched symvers have the same format.
# In both cases, the symbol should have the same CRC, belong to the same
# Module/Namespace and have the same export type.
if [[ ${#sym_line[@]} -ne ${#patched_sym_line[@]} || \
"${sym_line[*]}" != "${patched_sym_line[*]}" ]]; then
warn "Version disagreement for symbol ${sym}"
fi
done < "${TEMPDIR}/Module.symvers"
fi
# Read as words, no quotes.
# shellcheck disable=SC2013
for i in $(cat "$TEMPDIR/changed_objs")
do
mkdir -p "$TEMPDIR/patched/$(dirname "$i")" || die
cp -f "$SRCDIR/$i" "$TEMPDIR/patched/$i" || die
done
echo "Extracting new and modified ELF sections"
# If no kpatch module name was provided on the command line:
# - For single input .patch, use the patch filename
# - For multiple input .patches, use "patch"
# - Prefix with "kpatch" or "livepatch" accordingly
if [[ -z "$MODNAME" ]] ; then
if [[ "${#PATCH_LIST[@]}" -eq 1 ]]; then
MODNAME="$(basename "${PATCH_LIST[0]}")"
if [[ "$MODNAME" =~ \.patch$ ]] || [[ "$MODNAME" =~ \.diff$ ]]; then
MODNAME="${MODNAME%.*}"
fi
else
MODNAME="patch"
fi
if [[ "$USE_KLP" -eq 1 ]]; then
MODNAME="livepatch-$MODNAME"
else
MODNAME="kpatch-$MODNAME"
fi
MODNAME="$(module_name_string "$MODNAME")"
fi
FILES="$(cat "$TEMPDIR/changed_objs")"
cd "$TEMPDIR" || die
mkdir output
declare -a objnames
CHANGED=0
ERROR=0
# Prepare OOT module symvers file
if [[ -n "$OOT_MODULE" ]]; then
BUILDDIR="/lib/modules/$ARCHVERSION/build/"
cp -f "$SRCDIR/Module.symvers" "$TEMPDIR/Module.symvers" || die
awk '{ print $1 "\t" $2 "\t" $3 "\t" $4}' "${BUILDDIR}/Module.symvers" >> "$TEMPDIR/Module.symvers"
fi
for i in $FILES; do
# In RHEL 7 based kernels, copy_user_64.o misuses the .fixup section,
# which confuses create-diff-object. It's fine to skip it, it's an
# assembly file anyway.
[[ "$DISTRO" = rhel ]] || [[ "$DISTRO" = centos ]] || [[ "$DISTRO" = ol ]] && \
[[ "$i" = arch/x86/lib/copy_user_64.o ]] && continue
[[ "$i" = usr/initramfs_data.o ]] && continue
mkdir -p "output/$(dirname "$i")"
cd "$SRCDIR" || die
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="$SRCDIR/Module.symvers"
elif [[ "$(basename "$KOBJFILE")" = "$(basename "$OOT_MODULE")" ]]; then
KOBJFILE_NAME="$(basename --suffix=.ko "$OOT_MODULE")"
KOBJFILE_PATH="$OOT_MODULE"
SYMTAB="${TEMPDIR}/module/${KOBJFILE_NAME}.symtab"
SYMVERS_FILE="$TEMPDIR/Module.symvers"
else
KOBJFILE_NAME=$(basename "${KOBJFILE%.ko}")
KOBJFILE_NAME="${KOBJFILE_NAME//-/_}"
KOBJFILE_PATH="${TEMPDIR}/module/$KOBJFILE"
SYMTAB="${KOBJFILE_PATH}.symtab"
SYMVERS_FILE="$SRCDIR/Module.symvers"
fi
readelf -s --wide "$KOBJFILE_PATH" > "$SYMTAB"
if [[ "$ARCH" = "ppc64le" ]]; then
sed -ri 's/\s+\[<localentry>: 8\]//' "$SYMTAB"
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" \
"$SYMTAB" "$SYMVERS_FILE" "${MODNAME//-/_}" \
"output/$i" 2>&1 | logger 1
check_pipe_status create-diff-object
# create-diff-object returns 3 if no functional change is found
[[ "$rc" -eq 0 ]] || [[ "$rc" -eq 3 ]] || ERROR="$((ERROR + 1))"
if [[ "$rc" -eq 0 ]]; then
[[ -n "$ERROR_IF_DIFF" ]] && die "$ERROR_IF_DIFF"
CHANGED=1
objnames[${#objnames[@]}]="$KOBJFILE"
fi
else
cp -f "patched/$i" "output/$i" || die
objnames[${#objnames[@]}]="$KOBJFILE"
fi
done
if [[ "$ERROR" -ne 0 ]]; then
die "$ERROR error(s) encountered"
fi
if [[ "$CHANGED" -eq 0 ]]; then
die "no functional changes found"
fi
echo -n "Patched objects:"
for i in $(echo "${objnames[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')
do
echo -n " $i"
done
echo
export KCFLAGS="-I$DATADIR/patch $ARCH_KCFLAGS"
if [[ "$USE_KLP" -eq 0 ]]; then
export KCPPFLAGS="-D__KPATCH_MODULE__"
fi
echo "Building patch module: $MODNAME.ko"
if [[ -z "$USERSRCDIR" ]] && [[ "$DISTRO" = ubuntu ]]; then
# UBUNTU: add UTS_UBUNTU_RELEASE_ABI to utsrelease.h after regenerating it
UBUNTU_ABI="${ARCHVERSION#*-}"
UBUNTU_ABI="${UBUNTU_ABI%-*}"
echo "#define UTS_UBUNTU_RELEASE_ABI $UBUNTU_ABI" >> "$SRCDIR"/include/generated/utsrelease.h
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
if [[ "$USE_KLP" -eq 1 ]]; then
cp -f "$TEMPDIR"/patch/tmp_output.o "$TEMPDIR"/patch/output.o || die
# Avoid MODPOST warning (pre-v5.8) and error (v5.8+) with an empty .cmd file
touch "$TEMPDIR"/patch/.output.o.cmd || die
else
# Add .kpatch.checksum for kpatch script
md5sum ../patch/tmp_output.o | awk '{printf "%s\0", $1}' > checksum.tmp || die
objcopy --add-section .kpatch.checksum=checksum.tmp --set-section-flags .kpatch.checksum=alloc,load,contents,readonly ../patch/tmp_output.o || die
rm -f checksum.tmp
"$TOOLSDIR"/create-kpatch-module "$TEMPDIR"/patch/tmp_output.o "$TEMPDIR"/patch/output.o 2>&1 | logger 1
check_pipe_status create-kpatch-module
fi
cd "$TEMPDIR/patch" || die
if [[ -z "$OOT_MODULE" ]]; then
KPATCH_BUILD="$SRCDIR"
else
KPATCH_BUILD="/lib/modules/$ARCHVERSION/build"
fi
# We no longer need kpatch-cc
for ((idx=0; idx<${#MAKEVARS[@]}; idx++)); do
MAKEVARS[$idx]=${MAKEVARS[$idx]/${KPATCH_CC_PREFIX}/}
done
KPATCH_BUILD="$KPATCH_BUILD" KPATCH_NAME="$MODNAME" \
KBUILD_EXTRA_SYMBOLS="$KBUILD_EXTRA_SYMBOLS" \
KPATCH_LDFLAGS="$KPATCH_LDFLAGS" \
make "${MAKEVARS[@]}" 2>&1 | logger || die
if [[ "$USE_KLP" -eq 1 ]]; then
if [[ "$USE_KLP_ARCH" -eq 0 ]]; then
extra_flags="--no-klp-arch-sections"
fi
cp -f "$TEMPDIR/patch/$MODNAME.ko" "$TEMPDIR/patch/tmp.ko" || die
"$TOOLSDIR"/create-klp-module $extra_flags "$TEMPDIR/patch/tmp.ko" "$TEMPDIR/patch/$MODNAME.ko" 2>&1 | logger 1
check_pipe_status create-klp-module
fi
if [[ "$CONFIG_MODVERSIONS" -eq 1 ]]; then
# Check that final module does not reference symbols with different version
# than the target kernel
KP_MOD_VALID=true
# shellcheck disable=SC2086
while read -ra mod_symbol; do
if [[ ${#mod_symbol[@]} -lt 2 ]]; then
continue
fi
# Check if the symbol exists in the old Module.symvers, and if it does
# check that the CRCs are unchanged.
if ! awk -v sym="${mod_symbol[1]}" -v crc="${mod_symbol[0]}" \
'$2==sym && $1!=crc { exit 1 }' "$TEMPDIR/Module.symvers"; then
warn "Patch module references ${mod_symbol[1]} with invalid version"
KP_MOD_VALID=false
fi
done <<< "$(modprobe --dump-modversions $TEMPDIR/patch/$MODNAME.ko)"
if ! $KP_MOD_VALID; then
die "Patch module referencing altered exported kernel symbols cannot be loaded"
fi
fi
readelf --wide --symbols "$TEMPDIR/patch/$MODNAME.ko" 2>/dev/null | \
sed -r 's/\s+\[<localentry>: 8\]//' | \
awk '($4=="FUNC" || $4=="OBJECT") && ($5=="GLOBAL" || $5=="WEAK") && $7!="UND" {print $NF}' \
>"${TEMPDIR}"/new_symbols
if [[ "$USE_KLP" -eq 0 ]]; then
cat >>"${TEMPDIR}"/new_symbols <<-EOF
kpatch_shadow_free
kpatch_shadow_alloc
kpatch_register
kpatch_shadow_get
kpatch_unregister
kpatch_root_kobj
EOF
fi
# Compare undefined_references and new_symbols files and print only the first
# column containing lines unique to first file.
UNDEFINED=$(comm -23 <(sort -u "${TEMPDIR}"/undefined_references) \
<(sort -u "${TEMPDIR}"/new_symbols) | tr '\n' ' ')
[[ -n "$UNDEFINED" ]] && die "Undefined symbols: $UNDEFINED"
cp -f "$TEMPDIR/patch/$MODNAME.ko" "$BASE" || die
[[ "$DEBUG" -eq 0 && "$SKIPCLEANUP" -eq 0 ]] && rm -f "$LOGFILE"
echo "SUCCESS"