kpatch/kpatch-build/kpatch-build
Vincent Bernat e2e7d2cd21 kpatch-build: fix Ubuntu kernel detection on successive retries
If a patch failed a first time, kpatch-build is using the previous
cache directory on subsequent builds. The UBUNTU_KERNEL=1 variable is
not set in this case. Therefore, utsrelease.h is not updated correctly
and the appropriate structures are not used. Just check if distro is
Ubuntu and we didn't request our own specific directory.

Signed-off-by: Vincent Bernat <vincent@bernat.im>
2018-04-12 18:29:18 +02:00

887 lines
26 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 (vmlinux)
# - Builds the patched kernel 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"
DEBUG=0
SKIPCLEANUP=0
SKIPGCCCHECK=0
ARCH_KCFLAGS=""
declare -a PATCH_LIST
APPLIED_PATCHES=0
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
}
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 .config and vmlinux if they were removed with mrproper
[[ -e "$TEMPDIR/.config" ]] && mv -f "$TEMPDIR/.config" "$SRCDIR/"
[[ -e "$TEMPDIR/vmlinux" ]] && mv -f "$TEMPDIR/vmlinux" "$SRCDIR/"
[[ "$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 "no core file found, run 'ulimit -c unlimited' and try to recreate"
fi
}
# $1 >= $2
version_gte() {
[ "$1" = "$(echo -e "$1\n$2" | sort -rV | head -n1)" ]
}
is_rhel() {
[[ $1 =~ \.el[78]\. ]]
}
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 -o 'GCC:.*'
}
gcc_version_check() {
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 "$VMLINUX")"
rm -f "$c" "$o"
if [[ -n "$out" ]]; then
warn "gcc >= 4.8 required for -pg -ffunction-settings"
echo "gcc output: $out"
return 1
fi
# 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-gcc-check"
echo "to skip the version matching enforcement (not recommended)"
return 1
fi
return
}
find_special_section_data_ppc64le() {
SPECIAL_VARS="$(readelf -wi "$VMLINUX" |
gawk --non-decimal-data '
BEGIN { f = b = e = 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}
# 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 }
# Now that we know the size, stop parsing for it
f == 1 {printf("export FIXUP_STRUCT_SIZE=%d\n", $4); a = 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}
# Bail out once we have everything
f == 2 && b == 2 && e == 2 {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"
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"
SPECIAL_VARS="$(readelf -wi "$VMLINUX" |
gawk --non-decimal-data $AWK_OPTIONS '
BEGIN { a = b = p = e = 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}
# 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 }
# 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}
# Bail out once we have everything
a == 2 && b == 2 && (p == 2 || skip_p) && e == 2 {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"
return
}
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 -l "$grepname" "$last_deep_find"/.*.cmd | grep -Fv "$pdir/.${file}.cmd" | head -n1)"
num="$(grep -l "$grepname" "$last_deep_find"/.*.cmd | grep -Fvc "$pdir/.${file}.cmd")"
fi
if [[ "$num" -eq 0 ]]; then
parent="$(find ./* -name ".*.cmd" -print0 | xargs -0 grep -l "$grepname" | grep -Fv "$pdir/.${file}.cmd" | cut -c3- | head -n1)"
num="$(find ./* -name ".*.cmd" -print0 | xargs -0 grep -l "$grepname" | grep -Fvc "$pdir/.${file}.cmd")"
[[ "$num" -eq 1 ]] && last_deep_find="$(dirname "$parent")"
fi
else
parent="$(grep -l "$grepname" "$dir"/.*.cmd | grep -Fv "$dir/.${file}.cmd" | head -n1)"
num="$(grep -l "$grepname" "$dir"/.*.cmd | grep -Fvc "$dir/.${file}.cmd")"
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}"
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 " --skip-cleanup Skip post-build cleanup" >&2
echo " --skip-gcc-check Skip gcc version matching check" >&2
echo " (not recommended)" >&2
}
options="$(getopt -o ha:r:s:c:v:j:t:n:o:d -l "help,archversion:,sourcerpm:,sourcedir:,config:,vmlinux:,jobs:,target:,name:,output:,debug,skip-gcc-check,skip-cleanup" -- "$@")" || 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
;;
--skip-cleanup)
echo "Skipping cleanup"
SKIPCLEANUP=1
;;
--skip-gcc-check)
echo "WARNING: Skipping gcc version matching check (not recommended)"
SKIPGCCCHECK=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
warn "--archversion is incompatible with --vmlinux"
exit 1
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-}"
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"
[[ -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); }')"
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
[[ -z "$TARGETS" ]] && TARGETS="vmlinux modules"
# Don't check external file.
# shellcheck disable=SC1091
source /etc/os-release
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 [[ "$SKIPGCCCHECK" -eq 0 ]]; then
gcc_version_check || die
fi
if [[ -n "$USERSRCDIR" ]]; then
echo "Using source directory at $USERSRCDIR"
# save vmlinux before it gets removed with mrproper
[[ "$VMLINUX" -ef "$SRCDIR"/vmlinux ]] && cp -f "$VMLINUX" "$TEMPDIR/vmlinux" && VMLINUX="$TEMPDIR/vmlinux"
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
rpm -q --quiet yum-utils || die "yum-utils not installed"
yumdownloader --source --destdir "$TEMPDIR" "kernel-$ARCHVERSION" 2>&1 | logger || die
fi
SRCRPM="$TEMPDIR/kernel-$KVER-$KREL.src.rpm"
fi
echo "Unpacking kernel source"
rpm -D "_topdir $RPMTOPDIR" -ivh "$SRCRPM" 2>&1 | logger || die
rpmbuild -D "_topdir $RPMTOPDIR" -bp "--target=$(uname -m)" "$RPMTOPDIR"/SPECS/kernel.spec 2>&1 | logger ||
die "rpmbuild -bp failed. you may need to run 'yum-builddep kernel' first."
mv "$RPMTOPDIR"/BUILD/kernel-*/linux-"${ARCHVERSION%.*}"*"${ARCHVERSION##*.}" "$SRCDIR" 2>&1 | logger || die
rm -rf "$RPMTOPDIR"
rm -rf "$SRCDIR/.git"
if [[ "$ARCHVERSION" == *-* ]]; then
echo "-${ARCHVERSION##*-}" > "$SRCDIR/localversion" || die
fi
echo "$ARCHVERSION" > "$VERSIONFILE" || 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")"
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
cp "/boot/config-${ARCHVERSION}" "$SRCDIR/.config" || die
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
# save .config before it gets removed with mrproper
[[ -z "$CONFIGFILE" ]] && CONFIGFILE="$SRCDIR"/.config
[[ ! -e "$CONFIGFILE" ]] && die "can't find config file"
[[ "$CONFIGFILE" -ef "$SRCDIR"/.config ]] && cp -f "$CONFIGFILE" "$TEMPDIR" && CONFIGFILE="$TEMPDIR"/.config
# Build variables - Set some defaults, then adjust features
# according to .config and kernel version
KBUILD_EXTRA_SYMBOLS=""
KPATCH_LDFLAGS=""
KPATCH_MODULE=true
# kernel option checking
grep -q "CONFIG_DEBUG_INFO=y" "$CONFIGFILE" || die "kernel doesn't have 'CONFIG_DEBUG_INFO' enabled"
if grep -q "CONFIG_LIVEPATCH=y" "$CONFIGFILE"; then
# The kernel supports livepatch.
if version_gte "${ARCHVERSION//-*/}" 4.7.0 || is_rhel "$ARCHVERSION"; then
# Use new .klp.rela. sections
KPATCH_MODULE=false
if version_gte "${ARCHVERSION//-*/}" 4.9.0 || is_rhel "$ARCHVERSION"; then
KPATCH_LDFLAGS="--unique=.parainstructions --unique=.altinstructions"
fi
fi
else
# No support for livepatch in the kernel. Kpatch core module is needed.
find_core_symvers || die "unable to find Module.symvers for kpatch core module"
KBUILD_EXTRA_SYMBOLS="$SYMVERSFILE"
fi
# optional kernel configs: CONFIG_PARAVIRT
if grep -q "CONFIG_PARAVIRT=y" "$CONFIGFILE"; then
CONFIG_PARAVIRT=1
else
CONFIG_PARAVIRT=0
fi
# unsupported kernel option checking: CONFIG_DEBUG_INFO_SPLIT
grep -q "CONFIG_DEBUG_INFO_SPLIT=y" "$CONFIGFILE" && die "kernel option 'CONFIG_DEBUG_INFO_SPLIT' not supported"
echo "Testing patch file(s)"
cd "$SRCDIR" || die
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"
echo "Reading special section data"
find_special_section_data
if [[ $DEBUG -ge 4 ]]; then
export KPATCH_GCC_DEBUG=1
fi
echo "Building original kernel"
./scripts/setlocalversion --save-scmversion || die
make mrproper 2>&1 | logger || die
cp -f "$CONFIGFILE" "$SRCDIR/.config"
unset KPATCH_GCC_TEMPDIR
# $TARGETS used as list, no quotes.
# shellcheck disable=SC2086
CROSS_COMPILE="$TOOLSDIR/kpatch-gcc " make "-j$CPUS" $TARGETS 2>&1 | logger || die
echo "Building patched kernel"
apply_patches
mkdir -p "$TEMPDIR/orig" "$TEMPDIR/patched"
KPATCH_GCC_TEMPDIR="$TEMPDIR"
export KPATCH_GCC_TEMPDIR
# $TARGETS used as list, no quotes.
# shellcheck disable=SC2086
CROSS_COMPILE="$TOOLSDIR/kpatch-gcc " \
KBUILD_MODPOST_WARN=1 \
make "-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
# 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 "$KPATCH_MODULE"; then
MODNAME="kpatch-$MODNAME"
else
MODNAME="livepatch-$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
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"
if [[ "$KOBJFILE" = vmlinux ]]; then
KOBJFILE="$VMLINUX"
else
KOBJFILE="$TEMPDIR/module/$KOBJFILE"
fi
cd "$TEMPDIR" || die
if [[ -e "orig/$i" ]]; then
# create-diff-object orig.o patched.o kernel-object output.o Module.symvers patch-mod-name
"$TOOLSDIR"/create-diff-object "orig/$i" "patched/$i" "$KOBJFILE" \
"output/$i" "$SRCDIR/Module.symvers" "${MODNAME//-/_}" 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"
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 " $(basename "$i")"
done
echo
export KCFLAGS="-I$DATADIR/patch $ARCH_KCFLAGS"
if "$KPATCH_MODULE"; 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 "$KPATCH_MODULE"; then
# 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
else
cp "$TEMPDIR"/patch/tmp_output.o "$TEMPDIR"/patch/output.o || die
fi
cd "$TEMPDIR/patch" || die
KPATCH_BUILD="$SRCDIR" KPATCH_NAME="$MODNAME" \
KBUILD_EXTRA_SYMBOLS="$KBUILD_EXTRA_SYMBOLS" \
KPATCH_LDFLAGS="$KPATCH_LDFLAGS" \
make 2>&1 | logger || die
if ! "$KPATCH_MODULE"; then
if [[ -z "$KPATCH_LDFLAGS" ]]; then
extra_flags="--no-klp-arch-sections"
fi
cp "$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
readelf --wide --symbols "$TEMPDIR/patch/$MODNAME.ko" 2>/dev/null | \
awk '($4=="FUNC" || $4=="OBJECT") && ($5=="GLOBAL" || $5=="WEAK") && $7!="UND" {print $NF}' \
>"${TEMPDIR}"/new_symbols
if "$KPATCH_MODULE"; 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' ' ')
[[ ! -z "$UNDEFINED" ]] && die "Undefined symbols: $UNDEFINED"
cp -f "$TEMPDIR/patch/$MODNAME.ko" "$BASE" || die
[[ "$DEBUG" -eq 0 ]] && rm -f "$LOGFILE"
echo "SUCCESS"