Objtool updates for v7.2:

- A large series of KLP fixes and improvements, in preparation of
    the arm64 port (Josh Poimboeuf)
 
  - Fix a number of bugs and issues on specific distro, LTO,
    FineIBT and kCFI configs (Josh Poimboeuf)
 
  - Misc other fixes by Josh Poimboeuf and Joe Lawrence.
 
 Signed-off-by: Ingo Molnar <mingo@kernel.org>
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCgAvFiEEBpT5eoXrXCwVQwEKEnMQ0APhK1gFAmouvAgRHG1pbmdvQGtl
 cm5lbC5vcmcACgkQEnMQ0APhK1g1rBAAuJwhyKKa5KxdgoGC3uSCyILZ77n5en+I
 e5kCpeON2zbiGzg5N1D6Rgr1D1m1Fj1puiPy6FtqDkIO4a5b86Pt/Nv91CZG3A7n
 lYNRJtqVyMNpBr7O9aaxalEnhrEY1kDK5DRebSAidy97hL6HelMFBy3EkV2nzsUI
 eekUg3frCNq3k2oYWdtC5DZ6S7oK8rdAi/Ay3A3XTL6CLjYDPpMHd+pjDdtQsBYg
 ybMoVQthfKb1SDazy2od9bbTJDngJkMVgIqHPrQcnvqMqp1knI46YvEwMJzQ9DWB
 AaS8Xnm1jyk0/cFjIp5xNWo+VVuKiY0upewtFAodH35tvIuqptbt7btIkV5ZX7nu
 IaMfqwh88yHvf3PJMrbAy+5lIE9h6dKEDLTJVBT8Swb7Y6rUG0qMyJ3Ah/RrlAgu
 jcLUN89u8HXVaffWYAfpEj2ehSRsuy9BX2zCUeQoMH/5MCbDY2q4j6i07ylTJCkr
 flYjTosIXma/NwimXIyPzMl+vUtrznMwoubH6Ns2yu7DKAK1Ntb0W9H7oy/vZIPi
 ZAiV2OdmRet6iXdcIwBlS+7Du/eUMIkKlJsf4twWMgU7R4ApxTWoylr1z8yKqQam
 109MNt7d98wFu+iYEWB0S+Uz7I/ZAGwY1Rj7tZ7mHUlSBfcVsYp3b3NEOG4J9YLy
 K6L9ZECJ/Ow=
 =D5SB
 -----END PGP SIGNATURE-----

Merge tag 'objtool-core-2026-06-14' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip

Pull objtool updates from Ingo Molnar:

 - A large series of KLP fixes and improvements, in preparation of the
   arm64 port (Josh Poimboeuf)

 - Fix a number of bugs and issues on specific distro, LTO, FineIBT and
   kCFI configs (Josh Poimboeuf)

 - Misc other fixes by Josh Poimboeuf and Joe Lawrence

* tag 'objtool-core-2026-06-14' of gitolite.kernel.org:pub/scm/linux/kernel/git/tip/tip: (53 commits)
  objtool/klp: Cache dont_correlate() result
  objtool: Improve and simplify prefix symbol detection
  objtool/klp: Fix kCFI prefix finding/cloning
  objtool: Grow __cfi_* prefix symbols for all CFI+CALL_PADDING
  objtool/klp: Fix position-dependent checksums for non-relocated jumps/calls
  objtool: Add insn_sym() helper
  objtool/klp: Add correlation debugging output
  objtool/klp: Rewrite symbol correlation algorithm
  objtool/klp: Calculate object checksums
  klp-build: Validate short-circuit prerequisites
  objtool/klp: Remove "objtool --checksum"
  klp-build: Use "objtool klp checksum" subcommand
  objtool/klp: Add "objtool klp checksum" subcommand
  objtool: Consolidate file decoding into decode_file()
  objtool/klp: Extricate checksum calculation from validate_branch()
  objtool: Add is_cold_func() helper
  objtool: Add is_alias_sym() helper
  objtool/klp: Handle Clang .data..Lanon anonymous data sections
  objtool/klp: Create empty checksum sections for function-less object files
  objtool: Include libsubcmd headers directly from source tree
  ...
This commit is contained in:
Linus Torvalds 2026-06-15 14:31:58 +05:30
commit d8d706a274
23 changed files with 1493 additions and 776 deletions

View File

@ -2436,10 +2436,6 @@ config CALL_THUNKS
def_bool n
select CALL_PADDING
config PREFIX_SYMBOLS
def_bool y
depends on CALL_PADDING && !CFI
menuconfig CPU_MITIGATIONS
bool "Mitigations for CPU vulnerabilities"
default y

View File

@ -3070,7 +3070,7 @@ config FORTIFY_KUNIT_TEST
config LONGEST_SYM_KUNIT_TEST
tristate "Test the longest symbol possible" if !KUNIT_ALL_TESTS
depends on KUNIT && KPROBES
depends on !PREFIX_SYMBOLS && !CFI && !GCOV_KERNEL
depends on !CALL_PADDING && !CFI && !GCOV_KERNEL
default KUNIT_ALL_TESTS
help
Tests the longest symbol possible

View File

@ -190,7 +190,11 @@ objtool-args-$(CONFIG_HAVE_JUMP_LABEL_HACK) += --hacks=jump_label
objtool-args-$(CONFIG_HAVE_NOINSTR_HACK) += --hacks=noinstr
objtool-args-$(CONFIG_MITIGATION_CALL_DEPTH_TRACKING) += --hacks=skylake
objtool-args-$(CONFIG_X86_KERNEL_IBT) += --ibt
objtool-args-$(CONFIG_FINEIBT) += --cfi
objtool-args-$(CONFIG_CALL_PADDING) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES)
ifdef CONFIG_CALL_PADDING
objtool-args-$(CONFIG_CFI) += --cfi
objtool-args-$(CONFIG_FINEIBT) += --fineibt
endif
objtool-args-$(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL) += --mcount
ifdef CONFIG_FTRACE_MCOUNT_USE_OBJTOOL
objtool-args-$(CONFIG_HAVE_OBJTOOL_NOP_MCOUNT) += --mnop
@ -203,7 +207,6 @@ objtool-args-$(CONFIG_STACK_VALIDATION) += --stackval
objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call
objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess
objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable
objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES)
objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror
objtool-args = $(objtool-args-y) \

View File

@ -3,7 +3,7 @@
#
# Build a livepatch module
# shellcheck disable=SC1090,SC2155
# shellcheck disable=SC1090,SC2155,SC2164
if (( BASH_VERSINFO[0] < 4 || \
(BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then
@ -11,21 +11,19 @@ if (( BASH_VERSINFO[0] < 4 || \
exit 1
fi
set -o errexit
set -o errtrace
set -o pipefail
set -o nounset
# Allow doing 'cmd | mapfile -t array' instead of 'mapfile -t array < <(cmd)'.
# This helps keep execution in pipes so pipefail+errexit can catch errors.
# This helps keep execution in pipes so pipefail+ERR trap can catch errors.
shopt -s lastpipe
unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE
REPLACE=1
SHORT_CIRCUIT=0
JOBS="$(getconf _NPROCESSORS_ONLN)"
VERBOSE="-s"
shopt -o xtrace | grep -q 'on' && XTRACE=1
# Avoid removing the previous $TMP_DIR until args have been fully processed.
@ -35,16 +33,16 @@ SCRIPT="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines"
SRC="$(pwd)"
OBJ="$(pwd)"
OBJTOOL="$PWD/tools/objtool/objtool"
CONFIG="$PWD/.config"
TMP_DIR="$PWD/klp-tmp"
CONFIG="$OBJ/.config"
TMP_DIR="$OBJ/klp-tmp"
ORIG_DIR="$TMP_DIR/orig"
PATCHED_DIR="$TMP_DIR/patched"
DIFF_DIR="$TMP_DIR/diff"
KMOD_DIR="$TMP_DIR/kmod"
ORIG_DIR="$TMP_DIR/1-orig"
PATCHED_DIR="$TMP_DIR/2-patched"
ORIG_CSUM_DIR="$TMP_DIR/3-checksum-orig"
PATCHED_CSUM_DIR="$TMP_DIR/3-checksum-patched"
DIFF_DIR="$TMP_DIR/4-diff"
KMOD_DIR="$TMP_DIR/5-kmod"
STASH_DIR="$TMP_DIR/stash"
TIMESTAMP="$TMP_DIR/timestamp"
@ -90,7 +88,7 @@ declare -a STASHED_FILES
stash_file() {
local file="$1"
local rel_file="${file#"$SRC"/}"
local rel_file="${file#"$PWD"/}"
[[ ! -e "$file" ]] && die "no file to stash: $file"
@ -104,7 +102,7 @@ restore_files() {
local file
for file in "${STASHED_FILES[@]}"; do
mv -f "$STASH_DIR/$file" "$SRC/$file" || warn "can't restore file: $file"
mv -f "$STASH_DIR/$file" "$PWD/$file" || warn "can't restore file: $file"
done
STASHED_FILES=()
@ -140,10 +138,11 @@ Options:
Advanced Options:
-d, --debug Show symbol/reloc cloning decisions
-S, --short-circuit=STEP Start at build step (requires prior --keep-tmp)
1|orig Build original kernel (default)
2|patched Build patched kernel
3|diff Diff objects
4|kmod Build patch module
1|orig Build original kernel (default)
2|patched Build patched kernel
3|checksum Generate checksums
4|diff Diff objects
5|kmod Build patch module
-T, --keep-tmp Preserve tmp dir on exit
EOF
@ -158,6 +157,7 @@ process_args() {
local short
local long
local args
local patch
short="hfj:o:vdS:T"
long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
@ -194,7 +194,7 @@ process_args() {
shift
;;
-v | --verbose)
VERBOSE="V=1"
VERBOSE=1
shift
;;
-d | --debug)
@ -206,10 +206,11 @@ process_args() {
[[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir"
keep_tmp=1
case "$2" in
1 | orig) SHORT_CIRCUIT=1; ;;
2 | patched) SHORT_CIRCUIT=2; ;;
3 | diff) SHORT_CIRCUIT=3; ;;
4 | mod) SHORT_CIRCUIT=4; ;;
1 | orig) SHORT_CIRCUIT=1; ;;
2 | patched) SHORT_CIRCUIT=2; ;;
3 | checksum) SHORT_CIRCUIT=3; ;;
4 | diff) SHORT_CIRCUIT=4; ;;
5 | kmod) SHORT_CIRCUIT=5; ;;
*) die "invalid short-circuit step '$2'" ;;
esac
shift 2
@ -236,6 +237,10 @@ process_args() {
KEEP_TMP="$keep_tmp"
PATCHES=("$@")
for patch in "${PATCHES[@]}"; do
[[ -f "$patch" ]] || die "$patch doesn't exist"
done
}
# temporarily disable xtrace for especially verbose code
@ -270,6 +275,9 @@ validate_config() {
[[ "$CONFIG_AS_VERSION" -lt 200000 ]] && \
die "Clang assembler version < 20 not supported"
[[ -x "$OBJTOOL" ]] && "$OBJTOOL" klp 2>&1 | command grep -q "not implemented" && \
die "objtool not built with KLP support; install xxhash-devel/libxxhash-dev (version >= 0.8) and recompile"
return 0
}
@ -301,12 +309,17 @@ set_module_name() {
# Hardcode the value printed by the localversion script to prevent patch
# application from appending it with '+' due to a dirty working tree.
set_kernelversion() {
local file="$SRC/scripts/setlocalversion"
local file="$PWD/scripts/setlocalversion"
local kernelrelease
stash_file "$file"
kernelrelease="$(cd "$SRC" && make syncconfig &>/dev/null && make -s kernelrelease)"
if [[ -n "$(make -s listnewconfig 2>/dev/null)" ]]; then
die ".config mismatch, check your .config or run 'make olddefconfig'"
fi
make syncconfig &>/dev/null || die "make syncconfig failed"
kernelrelease="$(make -s kernelrelease)"
[[ -z "$kernelrelease" ]] && die "failed to get kernel version"
sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion
@ -349,7 +362,7 @@ check_unsupported_patches() {
for file in "${files[@]}"; do
case "$file" in
lib/*|*.S)
lib/*|*/vdso/*|*/realmode/rm/*|*.S)
die "${patch}: unsupported patch to $file"
;;
esac
@ -367,24 +380,24 @@ apply_patch() {
[[ ! -f "$patch" ]] && die "$patch doesn't exist"
status=0
output=$(patch -d "$SRC" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$?
output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$?
if [[ "$status" -ne 0 ]]; then
echo "$output" >&2
die "$patch did not apply"
elif [[ "$output" =~ $drift_regex ]]; then
echo "$output" >&2
[[ -v VERBOSE ]] && echo "$output" >&2
warn "${patch} applied with fuzz"
fi
patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch"
APPLIED_PATCHES+=("$patch")
patch -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch"
}
revert_patch() {
local patch="$1"
local tmp=()
patch -d "$SRC" -p1 -R --silent --no-backup-if-mismatch -r /dev/null < "$patch"
patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true
for p in "${APPLIED_PATCHES[@]}"; do
[[ "$p" == "$patch" ]] && continue
@ -422,8 +435,21 @@ validate_patches() {
do_init() {
# We're not yet smart enough to handle anything other than in-tree
# builds in pwd.
[[ ! "$SRC" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory"
[[ ! "$OBJ" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory"
[[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory"
if (( SHORT_CIRCUIT >= 2 )); then
[[ -f "$ORIG_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_DIR"
fi
if (( SHORT_CIRCUIT >= 3 )); then
[[ -f "$PATCHED_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_DIR"
fi
if (( SHORT_CIRCUIT >= 4 )); then
[[ -f "$ORIG_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_CSUM_DIR"
[[ -f "$PATCHED_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_CSUM_DIR"
fi
if (( SHORT_CIRCUIT >= 5 )); then
[[ -f "$DIFF_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $DIFF_DIR"
fi
(( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR"
mkdir -p "$TMP_DIR"
@ -454,11 +480,11 @@ refresh_patch() {
get_patch_output_files "$patch" | mapfile -t output_files
# Copy orig source files to 'a'
( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" )
echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a"
# Copy patched source files to 'b'
apply_patch "$patch" "--silent"
( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" )
echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b"
revert_patch "$patch"
# Diff 'a' and 'b' to make a clean patch
@ -502,20 +528,14 @@ clean_kernel() {
cmd+=("-j$JOBS")
cmd+=("clean")
(
cd "$SRC"
"${cmd[@]}"
)
"${cmd[@]}"
}
build_kernel() {
local build="$1"
local log="$TMP_DIR/build.log"
local objtool_args=()
local cmd=()
objtool_args=("--checksum")
cmd=("make")
# When a patch to a kernel module references a newly created unexported
@ -535,19 +555,20 @@ build_kernel() {
#
cmd+=("KBUILD_MODPOST_WARN=1")
cmd+=("$VERBOSE")
if [[ -v VERBOSE ]]; then
cmd+=("V=1")
else
cmd+=("-s")
fi
cmd+=("-j$JOBS")
cmd+=("KCFLAGS=-ffunction-sections -fdata-sections")
cmd+=("OBJTOOL_ARGS=${objtool_args[*]}")
cmd+=("vmlinux")
cmd+=("modules")
(
cd "$SRC"
"${cmd[@]}" \
1> >(tee -a "$log") \
2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2)
) || die "$build kernel build failed"
"${cmd[@]}" \
1> >(tee -a "$log") \
2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) \
|| die "$build kernel build failed"
}
find_objects() {
@ -555,9 +576,9 @@ find_objects() {
# Find root-level vmlinux.o and non-root-level .ko files,
# excluding klp-tmp/ and .git/
find "$OBJ" \( -path "$TMP_DIR" -o -path "$OBJ/.git" -o -regex "$OBJ/[^/][^/]*\.ko" \) -prune -o \
find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \
-type f "${opts[@]}" \
\( -name "*.ko" -o -path "$OBJ/vmlinux.o" \) \
\( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \
-printf '%P\n'
}
@ -570,10 +591,10 @@ copy_orig_objects() {
find_objects | mapfile -t files
xtrace_save "copying orig objects"
xtrace_save "copying original objects"
for _file in "${files[@]}"; do
local rel_file="${_file/.ko/.o}"
local file="$OBJ/$rel_file"
local file="$PWD/$rel_file"
local orig_file="$ORIG_DIR/$rel_file"
local orig_dir="$(dirname "$orig_file")"
@ -586,6 +607,7 @@ copy_orig_objects() {
mv -f "$TMP_DIR/build.log" "$ORIG_DIR"
touch "$TIMESTAMP"
touch "$ORIG_DIR/.complete"
}
# Copy all changed objects to $PATCHED_DIR
@ -606,7 +628,7 @@ copy_patched_objects() {
xtrace_save "copying changed objects"
for _file in "${files[@]}"; do
local rel_file="${_file/.ko/.o}"
local file="$OBJ/$rel_file"
local file="$PWD/$rel_file"
local orig_file="$ORIG_DIR/$rel_file"
local patched_file="$PATCHED_DIR/$rel_file"
local patched_dir="$(dirname "$patched_file")"
@ -624,6 +646,36 @@ copy_patched_objects() {
(( found == 0 )) && die "no changes detected"
mv -f "$TMP_DIR/build.log" "$PATCHED_DIR"
touch "$PATCHED_DIR/.complete"
}
# Copy .o files to a separate directory and run "objtool klp checksum" on each
# copy. The checksums are written to a .discard.sym_checksum section.
#
# If match_dir is given, only process files which also exist there.
generate_checksums() {
local src_dir="$1"
local dest_dir="$2"
local match_dir="${3:-}"
local files=()
local file
rm -rf "$dest_dir"
mkdir -p "$dest_dir"
find "$src_dir" -type f -name "*.o" | mapfile -t files
for file in "${files[@]}"; do
local rel="${file#"$src_dir"/}"
local dest="$dest_dir/$rel"
[[ -n "$match_dir" && ! -f "$match_dir/$rel" ]] && continue
mkdir -p "$(dirname "$dest")"
cp -f "$file" "$dest"
"$OBJTOOL" klp checksum "$dest"
done
touch "$dest_dir/.complete"
}
# Diff changed objects, writing output object to $DIFF_DIR
@ -635,23 +687,23 @@ diff_objects() {
rm -rf "$DIFF_DIR"
mkdir -p "$DIFF_DIR"
find "$PATCHED_DIR" -type f -name "*.o" | mapfile -t files
find "$PATCHED_CSUM_DIR" -type f -name "*.o" | mapfile -t files
[[ ${#files[@]} -eq 0 ]] && die "no changes detected"
[[ -v DEBUG_CLONE ]] && opts=("--debug")
# Diff all changed objects
for file in "${files[@]}"; do
local rel_file="${file#"$PATCHED_DIR"/}"
local rel_file="${file#"$PATCHED_CSUM_DIR"/}"
local orig_file="$rel_file"
local patched_file="$PATCHED_DIR/$rel_file"
local patched_file="$PATCHED_CSUM_DIR/$rel_file"
local out_file="$DIFF_DIR/$rel_file"
local filter=()
local cmd=()
mkdir -p "$(dirname "$out_file")"
cmd=("$SRC/tools/objtool/objtool")
cmd=("$OBJTOOL")
cmd+=("klp")
cmd+=("diff")
(( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}")
@ -668,18 +720,21 @@ diff_objects() {
fi
(
cd "$ORIG_DIR"
cd "$ORIG_CSUM_DIR"
[[ -v VERBOSE ]] && echo "cd $ORIG_CSUM_DIR && ${cmd[*]}"
"${cmd[@]}" \
1> >(tee -a "$log") \
2> >(tee -a "$log" | "${filter[@]}" >&2) || \
die "objtool klp diff failed"
)
done
touch "$DIFF_DIR/.complete"
}
# For each changed object, run objtool with --debug-checksum to get the
# per-instruction checksums, and then diff those to find the first changed
# instruction for each function.
# For each changed object, run "objtool klp checksum" with --debug-checksum to
# get the per-instruction checksums, and then diff those to find the first
# changed instruction for each function.
diff_checksums() {
local orig_log="$ORIG_DIR/checksum.log"
local patched_log="$PATCHED_DIR/checksum.log"
@ -703,9 +758,8 @@ diff_checksums() {
fi
done
cmd=("$SRC/tools/objtool/objtool")
cmd+=("--checksum")
cmd+=("--link")
cmd=("$OBJTOOL")
cmd+=("klp" "checksum")
cmd+=("--dry-run")
for file in "${!funcs[@]}"; do
@ -714,21 +768,37 @@ diff_checksums() {
(
cd "$ORIG_DIR"
"${cmd[@]}" "$opt" "$file" &> "$orig_log" || \
( cat "$orig_log" >&2; die "objtool --debug-checksum failed" )
( cat "$orig_log" >&2; die "objtool klp checksum failed" )
cd "$PATCHED_DIR"
"${cmd[@]}" "$opt" "$file" &> "$patched_log" || \
( cat "$patched_log" >&2; die "objtool --debug-checksum failed" )
( cat "$patched_log" >&2; die "objtool klp checksum failed" )
)
for func in ${funcs[$file]}; do
diff <( grep0 -E "^DEBUG: .*checksum: $func " "$orig_log" | sed "s|$ORIG_DIR/||") \
<( grep0 -E "^DEBUG: .*checksum: $func " "$patched_log" | sed "s|$PATCHED_DIR/||") \
| gawk '/^< DEBUG: / {
gsub(/:/, "")
printf "%s: %s: %s\n", $3, $5, $6
exit
}' || true
local -a orig patched
paste <(grep0 -E "^DEBUG: .*checksum: $func " "$orig_log") \
<(grep0 -E "^DEBUG: .*checksum: $func " "$patched_log") |
while IFS= read -r line; do
read -ra orig <<< "${line%%$'\t'*}"
read -ra patched <<< "${line#*$'\t'}"
if [[ ${#patched[@]} -eq 0 ]]; then
printf "%s: %s: %s (removed)\n" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}"
break
elif [[ ${#orig[@]} -eq 0 ]]; then
printf "%s: %s: %s (added)\n" "${patched[1]%:}" "${patched[3]}" "${patched[-2]}"
break
fi
[[ "${orig[-1]}" == "${patched[-1]}" ]] && continue
printf "%s: %s: %s" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}"
[[ "${orig[-2]}" != "${patched[-2]}" ]] && \
printf " (patched: %s)" "${patched[-2]}"
printf "\n"
break
done || true
done
done
}
@ -745,7 +815,7 @@ build_patch_module() {
rm -rf "$KMOD_DIR"
mkdir -p "$KMOD_DIR"
cp -f "$SRC/scripts/livepatch/init.c" "$KMOD_DIR"
cp -f "$SCRIPT_DIR/init.c" "$KMOD_DIR"
echo "obj-m := $NAME.o" > "$makefile"
echo -n "$NAME-y := init.o" >> "$makefile"
@ -780,19 +850,20 @@ build_patch_module() {
[[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
cmd=("make")
cmd+=("$VERBOSE")
if [[ -v VERBOSE ]]; then
cmd+=("V=1")
else
cmd+=("-s")
fi
cmd+=("-j$JOBS")
cmd+=("--directory=.")
cmd+=("M=$KMOD_DIR")
cmd+=("KCFLAGS=${cflags[*]}")
# Build a "normal" kernel module with init.c and the diffed objects
(
cd "$SRC"
"${cmd[@]}" \
1> >(tee -a "$log") \
2> >(tee -a "$log" >&2)
)
"${cmd[@]}" \
1> >(tee -a "$log") \
2> >(tee -a "$log" >&2)
kmod_file="$KMOD_DIR/$NAME.ko"
@ -803,7 +874,7 @@ build_patch_module() {
objcopy --remove-section=.BTF "$kmod_file"
# Fix (and work around) linker wreckage for klp syms / relocs
"$SRC/tools/objtool/objtool" klp post-link "$kmod_file" || die "objtool klp post-link failed"
"$OBJTOOL" klp post-link "$kmod_file" || die "objtool klp post-link failed"
cp -f "$kmod_file" "$OUTFILE"
}
@ -839,6 +910,13 @@ if (( SHORT_CIRCUIT <= 2 )); then
fi
if (( SHORT_CIRCUIT <= 3 )); then
status "Generating original checksums"
generate_checksums "$ORIG_DIR" "$ORIG_CSUM_DIR" "$PATCHED_DIR"
status "Generating patched checksums"
generate_checksums "$PATCHED_DIR" "$PATCHED_CSUM_DIR"
fi
if (( SHORT_CIRCUIT <= 4 )); then
status "Diffing objects"
diff_objects
if [[ -v DIFF_CHECKSUM ]]; then
@ -847,7 +925,7 @@ if (( SHORT_CIRCUIT <= 3 )); then
fi
fi
if (( SHORT_CIRCUIT <= 4 )); then
if (( SHORT_CIRCUIT <= 5 )); then
status "Building patch module: $OUTFILE"
build_patch_module
fi

View File

@ -12,7 +12,7 @@ objtool-$(BUILD_DISAS) += disas.o
objtool-$(BUILD_DISAS) += trace.o
objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o
objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o
objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o klp-post-link.o
objtool-y += libstring.o
objtool-y += libctype.o

View File

@ -58,7 +58,7 @@ INCLUDES := -I$(srctree)/tools/include \
-I$(srctree)/tools/arch/$(SRCARCH)/include \
-I$(srctree)/tools/objtool/include \
-I$(srctree)/tools/objtool/arch/$(SRCARCH)/include \
-I$(LIBSUBCMD_OUTPUT)/include
-I$(srctree)/tools/lib
OBJTOOL_CFLAGS := -std=gnu11 -fomit-frame-pointer -O2 -g $(WARNINGS) \
$(INCLUDES) $(LIBELF_FLAGS) $(LIBXXHASH_CFLAGS) $(HOSTCFLAGS)
@ -135,7 +135,7 @@ $(LIBSUBCMD): fixdep $(LIBSUBCMD_OUTPUT) FORCE
$(Q)$(MAKE) -C $(LIBSUBCMD_DIR) O=$(LIBSUBCMD_OUTPUT) \
DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir= \
$(HOST_OVERRIDES) EXTRA_CFLAGS="$(OBJTOOL_CFLAGS)" \
$@ install_headers
$@
$(LIBSUBCMD)-clean:
$(call QUIET_CLEAN, libsubcmd)

View File

@ -805,14 +805,27 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
break;
}
if (ins.immediate.nbytes)
if (ins.immediate.nbytes) {
insn->immediate = ins.immediate.value;
else if (ins.displacement.nbytes)
insn->immediate_len = ins.immediate.nbytes;
} else if (ins.displacement.nbytes) {
insn->immediate = ins.displacement.value;
insn->immediate_len = ins.displacement.nbytes;
}
return 0;
}
size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
unsigned char *buf)
{
size_t len;
len = insn->len - insn->immediate_len;
memcpy(buf, insn->sec->data->d_buf + insn->offset, len);
return len;
}
void arch_initial_func_cfi_state(struct cfi_init_state *state)
{
int i;

View File

@ -73,8 +73,6 @@ static int parse_hacks(const struct option *opt, const char *str, int unset)
static const struct option check_options[] = {
OPT_GROUP("Actions:"),
OPT_BOOLEAN(0, "checksum", &opts.checksum, "generate per-function checksums"),
OPT_BOOLEAN(0, "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"),
OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"),
OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks),
OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"),
@ -85,7 +83,7 @@ static const struct option check_options[] = {
OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"),
OPT_BOOLEAN(0, "rethunk", &opts.rethunk, "validate and annotate rethunk usage"),
OPT_BOOLEAN(0, "unret", &opts.unret, "validate entry unret placement"),
OPT_INTEGER(0, "prefix", &opts.prefix, "generate prefix symbols"),
OPT_INTEGER(0, "prefix", &opts.prefix, "generate or grow prefix symbols for N-byte function padding"),
OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"),
OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"),
OPT_BOOLEAN('t', "static-call", &opts.static_call, "annotate static calls"),
@ -93,9 +91,10 @@ static const struct option check_options[] = {
OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump),
OPT_GROUP("Options:"),
OPT_BOOLEAN(0, "cfi", &opts.cfi, "grow kCFI preamble symbols (use with --prefix)"),
OPT_BOOLEAN(0, "fineibt", &opts.fineibt, "create .cfi_sites section for FineIBT"),
OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"),
OPT_BOOLEAN(0, "backup", &opts.backup, "create backup (.orig) file on warning/error"),
OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "funcs", "enable checksum debug output"),
OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"),
OPT_BOOLEAN(0, "link", &opts.link, "object is a linked object"),
OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"),
@ -165,20 +164,17 @@ static bool opts_valid(void)
return false;
}
#ifndef BUILD_KLP
if (opts.checksum) {
ERROR("--checksum not supported; install xxhash-devel/libxxhash-dev (version >= 0.8) and recompile");
return false;
}
#endif
if (opts.debug_checksum && !opts.checksum) {
ERROR("--debug-checksum requires --checksum");
if (opts.cfi && !opts.prefix) {
ERROR("--cfi requires --prefix");
return false;
}
if (opts.checksum ||
opts.disas ||
if (opts.fineibt && !opts.cfi) {
ERROR("--fineibt requires --cfi");
return false;
}
if (opts.disas ||
opts.hack_jump_label ||
opts.hack_noinstr ||
opts.ibt ||

View File

@ -13,6 +13,7 @@ struct subcmd {
};
static struct subcmd subcmds[] = {
{ "checksum", "Generate per-function checksums", cmd_klp_checksum, },
{ "diff", "Generate binary diff of two object files", cmd_klp_diff, },
{ "post-link", "Finalize klp symbols/relocs after module linking", cmd_klp_post_link, },
};

View File

@ -18,7 +18,6 @@
#include <objtool/special.h>
#include <objtool/trace.h>
#include <objtool/warn.h>
#include <objtool/checksum.h>
#include <objtool/util.h>
#include <linux/objtool_types.h>
@ -64,8 +63,8 @@ struct instruction *next_insn_same_sec(struct objtool_file *file,
return insn;
}
static struct instruction *next_insn_same_func(struct objtool_file *file,
struct instruction *insn)
struct instruction *next_insn_same_func(struct objtool_file *file,
struct instruction *insn)
{
struct instruction *next = next_insn_same_sec(file, insn);
struct symbol *func = insn_func(insn);
@ -113,10 +112,6 @@ static struct instruction *prev_insn_same_sym(struct objtool_file *file,
for_each_sec(file->elf, __sec) \
sec_for_each_insn(file, __sec, insn)
#define func_for_each_insn(file, func, insn) \
for (insn = find_insn(file, func->sec, func->offset); \
insn; \
insn = next_insn_same_func(file, insn))
#define sym_for_each_insn(file, sym, insn) \
for (insn = find_insn(file, sym->sec, sym->offset); \
@ -491,7 +486,7 @@ static int decode_instructions(struct objtool_file *file)
return -1;
}
if (func->embedded_insn || func->alias != func)
if (func->embedded_insn || is_alias_sym(func))
continue;
if (!find_insn(file, sec, func->offset)) {
@ -500,7 +495,7 @@ static int decode_instructions(struct objtool_file *file)
}
sym_for_each_insn(file, func, insn) {
insn->sym = func;
insn->_sym = func;
if (is_func_sym(func) &&
insn->type == INSN_ENDBR &&
list_empty(&insn->call_node)) {
@ -864,15 +859,14 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file)
list_for_each_entry(insn, &file->endbr_list, call_node) {
int *site = (int *)sec->data->d_buf + idx;
struct symbol *sym = insn->sym;
struct symbol *func = insn_func(insn);
*site = 0;
if (opts.module && sym && is_func_sym(sym) &&
insn->offset == sym->offset &&
(!strcmp(sym->name, "init_module") ||
!strcmp(sym->name, "cleanup_module"))) {
if (opts.module && func && insn->offset == func->offset &&
(!strcmp(func->name, "init_module") ||
!strcmp(func->name, "cleanup_module"))) {
ERROR("%s(): Magic init_module() function name is deprecated, use module_init(fn) instead",
sym->name);
func->name);
return -1;
}
@ -887,6 +881,31 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file)
return 0;
}
/*
* Grow __cfi_ symbols to fill the NOP gap between the 'mov <hash>, %rax' and
* the start of the function.
*/
static int grow_cfi_symbols(struct objtool_file *file)
{
struct symbol *sym;
for_each_sym(file->elf, sym) {
if (!is_func_sym(sym) || !strstarts(sym->name, "__cfi_") ||
sym->len != 5)
continue;
if (!find_func_by_offset(sym->sec, sym->offset + sym->len + opts.prefix))
continue;
sym->len += opts.prefix;
sym->sym.st_size = sym->len;
if (elf_write_symbol(file->elf, sym))
return -1;
}
return 0;
}
static int create_cfi_sections(struct objtool_file *file)
{
struct section *sec;
@ -1023,59 +1042,6 @@ static int create_direct_call_sections(struct objtool_file *file)
return 0;
}
#ifdef BUILD_KLP
static int create_sym_checksum_section(struct objtool_file *file)
{
struct section *sec;
struct symbol *sym;
unsigned int idx = 0;
struct sym_checksum *checksum;
size_t entsize = sizeof(struct sym_checksum);
sec = find_section_by_name(file->elf, ".discard.sym_checksum");
if (sec) {
if (!opts.dryrun)
WARN("file already has .discard.sym_checksum section, skipping");
return 0;
}
for_each_sym(file->elf, sym)
if (sym->csum.checksum)
idx++;
if (!idx)
return 0;
sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
idx, idx);
if (!sec)
return -1;
idx = 0;
for_each_sym(file->elf, sym) {
if (!sym->csum.checksum)
continue;
if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
sym, 0, R_TEXT64))
return -1;
checksum = (struct sym_checksum *)sec->data->d_buf + idx;
checksum->addr = 0; /* reloc */
checksum->checksum = sym->csum.checksum;
mark_sec_changed(file->elf, sec, true);
idx++;
}
return 0;
}
#else
static int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; }
#endif
/*
* Warnings shouldn't be reported for ignored functions.
*/
@ -1349,14 +1315,11 @@ __weak bool arch_is_embedded_insn(struct symbol *sym)
return false;
}
static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
{
struct reloc *reloc;
if (insn->no_reloc)
return NULL;
if (!file)
if (!file || insn->no_reloc || insn->fake)
return NULL;
reloc = find_reloc_by_dest_range(file->elf, insn->sec,
@ -1642,7 +1605,7 @@ static int add_jump_destinations(struct objtool_file *file)
}
if (!dest_sym || is_sec_sym(dest_sym)) {
dest_sym = dest_insn->sym;
dest_sym = insn_sym(dest_insn);
if (!dest_sym)
goto set_jump_dest;
}
@ -1658,7 +1621,7 @@ static int add_jump_destinations(struct objtool_file *file)
continue;
}
if (!insn->sym || insn->sym->pfunc == dest_sym->pfunc)
if (!insn_sym(insn) || insn_sym(insn)->pfunc == dest_sym->pfunc)
goto set_jump_dest;
/*
@ -1831,7 +1794,6 @@ static int handle_group_alt(struct objtool_file *file,
nop->offset = special_alt->new_off + special_alt->new_len;
nop->len = special_alt->orig_len - special_alt->new_len;
nop->type = INSN_NOP;
nop->sym = orig_insn->sym;
nop->alt_group = new_alt_group;
nop->fake = 1;
}
@ -1850,7 +1812,6 @@ static int handle_group_alt(struct objtool_file *file,
last_new_insn = insn;
insn->sym = orig_insn->sym;
insn->alt_group = new_alt_group;
/*
@ -2232,7 +2193,7 @@ static int add_jump_table_alts(struct objtool_file *file)
return 0;
for_each_sym(file->elf, func) {
if (!is_func_sym(func) || func->alias != func)
if (!is_func_sym(func) || is_alias_sym(func))
continue;
mark_func_jump_tables(file, func);
@ -2493,12 +2454,12 @@ static int __annotate_late(struct objtool_file *file, int type, struct instructi
break;
case ANNOTYPE_NOCFI:
sym = insn->sym;
sym = insn_sym(insn);
if (!sym) {
ERROR_INSN(insn, "dodgy NOCFI annotation");
return -1;
}
insn->sym->nocfi = 1;
sym->nocfi = 1;
break;
default:
@ -2566,7 +2527,6 @@ static int classify_symbols(struct objtool_file *file)
static void mark_rodata(struct objtool_file *file)
{
struct section *sec;
bool found = false;
/*
* Search for the following rodata sections, each of which can
@ -2579,15 +2539,11 @@ static void mark_rodata(struct objtool_file *file)
* .rodata.str1.* sections are ignored; they don't contain jump tables.
*/
for_each_sec(file->elf, sec) {
if ((!strncmp(sec->name, ".rodata", 7) &&
!strstr(sec->name, ".str1.")) ||
!strncmp(sec->name, ".data.rel.ro", 12)) {
sec->rodata = true;
found = true;
if (is_rodata_sec(sec)) {
file->rodata = true;
return;
}
}
file->rodata = found;
}
static void mark_holes(struct objtool_file *file)
@ -2604,7 +2560,7 @@ static void mark_holes(struct objtool_file *file)
* favour of a regular symbol, but leaves the code in place.
*/
for_each_insn(file, insn) {
if (insn->sym || !find_symbol_hole_containing(insn->sec, insn->offset)) {
if (insn_sym(insn) || !find_symbol_hole_containing(insn->sec, insn->offset)) {
in_hole = false;
continue;
}
@ -2622,7 +2578,7 @@ static void mark_holes(struct objtool_file *file)
if (insn->jump_dest) {
struct symbol *dest_func = insn_func(insn->jump_dest);
if (dest_func && dest_func->cold)
if (dest_func && is_cold_func(dest_func))
dest_func->ignore = true;
}
}
@ -2630,14 +2586,35 @@ static void mark_holes(struct objtool_file *file)
static bool validate_branch_enabled(void)
{
return opts.stackval ||
opts.orc ||
opts.uaccess ||
return opts.stackval ||
opts.orc ||
opts.uaccess;
}
static bool alts_needed(void)
{
return validate_branch_enabled() ||
opts.noinstr ||
opts.hack_jump_label ||
opts.disas ||
opts.checksum;
}
static int decode_sections(struct objtool_file *file)
int decode_file(struct objtool_file *file)
{
arch_initial_func_cfi_state(&initial_func_cfi);
init_cfi_state(&init_cfi);
init_cfi_state(&func_cfi);
set_func_state(&func_cfi);
init_cfi_state(&force_undefined_cfi);
force_undefined_cfi.force_undefined = true;
if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3)))
return -1;
cfi_hash_add(&init_cfi);
cfi_hash_add(&func_cfi);
file->klp = is_livepatch_module(file);
mark_rodata(file);
@ -2666,7 +2643,7 @@ static int decode_sections(struct objtool_file *file)
* Must be before add_jump_destinations(), which depends on 'func'
* being set for alternatives, to enable proper sibling call detection.
*/
if (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label || opts.disas) {
if (alts_needed()) {
if (add_special_section_alts(file))
return -1;
}
@ -3027,7 +3004,7 @@ static int update_cfi_state(struct instruction *insn,
}
if (op->dest.reg == CFI_BP && op->src.reg == CFI_SP &&
insn->sym->frame_pointer) {
insn_sym(insn)->frame_pointer) {
/* addi.d fp,sp,imm on LoongArch */
if (cfa->base == CFI_SP && cfa->offset == op->src.offset) {
cfa->base = CFI_BP;
@ -3039,7 +3016,7 @@ static int update_cfi_state(struct instruction *insn,
if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) {
/* addi.d sp,fp,imm on LoongArch */
if (cfa->base == CFI_BP && cfa->offset == 0) {
if (insn->sym->frame_pointer) {
if (insn_sym(insn)->frame_pointer) {
cfa->base = CFI_SP;
cfa->offset = -op->src.offset;
}
@ -3662,88 +3639,6 @@ static bool skip_alt_group(struct instruction *insn)
return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC;
}
static int checksum_debug_init(struct objtool_file *file)
{
char *dup, *s;
if (!opts.debug_checksum)
return 0;
dup = strdup(opts.debug_checksum);
if (!dup) {
ERROR_GLIBC("strdup");
return -1;
}
s = dup;
while (*s) {
struct symbol *func;
char *comma;
comma = strchr(s, ',');
if (comma)
*comma = '\0';
func = find_symbol_by_name(file->elf, s);
if (!func || !is_func_sym(func))
WARN("--debug-checksum: can't find '%s'", s);
else
func->debug_checksum = 1;
if (!comma)
break;
s = comma + 1;
}
free(dup);
return 0;
}
static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
struct instruction *insn)
{
struct reloc *reloc = insn_reloc(file, insn);
unsigned long offset;
struct symbol *sym;
if (insn->fake)
return;
checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
if (!reloc) {
struct symbol *call_dest = insn_call_dest(insn);
if (call_dest)
checksum_update(func, insn, call_dest->demangled_name,
strlen(call_dest->demangled_name));
return;
}
sym = reloc->sym;
offset = arch_insn_adjusted_addend(insn, reloc);
if (is_string_sec(sym->sec)) {
char *str;
str = sym->sec->data->d_buf + sym->offset + offset;
checksum_update(func, insn, str, strlen(str));
return;
}
if (is_sec_sym(sym)) {
sym = find_symbol_containing(reloc->sym->sec, offset);
if (!sym)
return;
offset -= sym->offset;
}
checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name));
checksum_update(func, insn, &offset, sizeof(offset));
}
static int validate_branch(struct objtool_file *file, struct symbol *func,
struct instruction *insn, struct insn_state state);
static int do_validate_branch(struct objtool_file *file, struct symbol *func,
@ -4025,9 +3920,6 @@ static int do_validate_branch(struct objtool_file *file, struct symbol *func,
insn->trace = 0;
next_insn = next_insn_to_validate(file, insn);
if (opts.checksum && func && insn->sec)
checksum_update_insn(file, func, insn);
if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
/* Ignore KCFI type preambles, which always fall through */
if (is_prefix_func(func))
@ -4093,9 +3985,6 @@ static int validate_unwind_hint(struct objtool_file *file,
struct symbol *func = insn_func(insn);
int ret;
if (opts.checksum)
checksum_init(func);
ret = validate_branch(file, func, insn, *state);
if (ret)
BT_INSN(insn, "<=== (hint)");
@ -4304,7 +4193,7 @@ static int validate_retpoline(struct objtool_file *file)
* broken.
*/
list_for_each_entry(insn, &file->retpoline_call_list, call_node) {
struct symbol *sym = insn->sym;
struct symbol *sym = insn_sym(insn);
if (sym && (is_notype_sym(sym) ||
is_func_sym(sym)) && !sym->nocfi) {
@ -4407,17 +4296,6 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio
* For FineIBT or kCFI, a certain number of bytes preceding the function may be
* NOPs. Those NOPs may be rewritten at runtime and executed, so give them a
* proper function name: __pfx_<func>.
*
* The NOPs may not exist for the following cases:
*
* - compiler cloned functions (*.cold, *.part0, etc)
* - asm functions created with inline asm or without SYM_FUNC_START()
*
* Also, the function may already have a prefix from a previous objtool run
* (livepatch extracted functions, or manually running objtool multiple times).
*
* So return 0 if the NOPs are missing or the function already has a prefix
* symbol.
*/
static int create_prefix_symbol(struct objtool_file *file, struct symbol *func)
{
@ -4425,10 +4303,6 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func)
char name[SYM_NAME_LEN];
struct cfi_state *cfi;
if (!is_func_sym(func) || is_prefix_func(func) ||
func->cold || func->static_call_tramp)
return 0;
if ((strlen(func->name) + sizeof("__pfx_") > SYM_NAME_LEN)) {
WARN("%s: symbol name too long, can't create __pfx_ symbol",
func->name);
@ -4438,59 +4312,21 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func)
if (snprintf_check(name, SYM_NAME_LEN, "__pfx_%s", func->name))
return -1;
if (file->klp) {
struct symbol *pfx;
pfx = find_symbol_by_offset(func->sec, func->offset - opts.prefix);
if (pfx && is_prefix_func(pfx) && !strcmp(pfx->name, name))
return 0;
}
insn = find_insn(file, func->sec, func->offset);
if (!insn) {
WARN("%s: can't find starting instruction", func->name);
if (!elf_create_symbol(file->elf, name, func->sec,
GELF_ST_BIND(func->sym.st_info),
GELF_ST_TYPE(func->sym.st_info),
func->offset - opts.prefix, opts.prefix))
return -1;
}
for (prev = prev_insn_same_sec(file, insn);
prev;
prev = prev_insn_same_sec(file, prev)) {
u64 offset;
if (prev->type != INSN_NOP)
return 0;
offset = func->offset - prev->offset;
if (offset > opts.prefix)
return 0;
if (offset < opts.prefix)
continue;
if (!elf_create_symbol(file->elf, name, func->sec,
GELF_ST_BIND(func->sym.st_info),
GELF_ST_TYPE(func->sym.st_info),
prev->offset, opts.prefix))
return -1;
break;
}
if (!prev)
return 0;
if (!insn->cfi) {
/*
* This can happen if stack validation isn't enabled or the
* function is annotated with STACK_FRAME_NON_STANDARD.
*/
return 0;
}
/* Propagate insn->cfi to the prefix code */
insn = find_insn(file, func->sec, func->offset);
if (!insn || !insn->cfi)
return 0;
cfi = cfi_hash_find_or_add(insn->cfi);
for (; prev != insn; prev = next_insn_same_sec(file, prev))
for (prev = find_insn(file, func->sec, func->offset - opts.prefix);
prev && prev != insn;
prev = next_insn_same_sec(file, prev))
prev->cfi = cfi;
return 0;
@ -4498,15 +4334,20 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func)
static int create_prefix_symbols(struct objtool_file *file)
{
struct section *sec;
struct section *pfe_sec;
struct symbol *func;
struct reloc *reloc;
for_each_sec(file->elf, sec) {
if (!is_text_sec(sec))
for_each_sec(file->elf, pfe_sec) {
if (strcmp(pfe_sec->name, "__patchable_function_entries"))
continue;
if (!pfe_sec->rsec)
continue;
sec_for_each_sym(sec, func) {
if (create_prefix_symbol(file, func))
for_each_reloc(pfe_sec->rsec, reloc) {
func = find_func_by_offset(reloc->sym->sec,
reloc->sym->offset + reloc_addend(reloc) + opts.prefix);
if (func && create_prefix_symbol(file, func))
return -1;
}
}
@ -4526,7 +4367,7 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
return 1;
}
if (sym->pfunc != sym || sym->alias != sym)
if (sym->pfunc != sym || is_alias_sym(sym))
return 0;
insn = find_insn(file, sec, sym->offset);
@ -4538,9 +4379,6 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
func = insn_func(insn);
if (opts.checksum)
checksum_init(func);
if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) {
trace_enable();
TRACE("%s: validation begin\n", sym->name);
@ -4553,9 +4391,6 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end");
trace_disable();
if (opts.checksum)
checksum_finish(func);
return ret;
}
@ -4948,7 +4783,7 @@ struct insn_chunk {
* which can trigger more allocations for .debug_* sections whose data hasn't
* been read yet.
*/
static void free_insns(struct objtool_file *file)
void free_insns(struct objtool_file *file)
{
struct instruction *insn;
struct insn_chunk *chunks = NULL, *chunk;
@ -4995,26 +4830,7 @@ int check(struct objtool_file *file)
objtool_disas_ctx = disas_ctx;
}
arch_initial_func_cfi_state(&initial_func_cfi);
init_cfi_state(&init_cfi);
init_cfi_state(&func_cfi);
set_func_state(&func_cfi);
init_cfi_state(&force_undefined_cfi);
force_undefined_cfi.force_undefined = true;
if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) {
ret = -1;
goto out;
}
cfi_hash_add(&init_cfi);
cfi_hash_add(&func_cfi);
ret = checksum_debug_init(file);
if (ret)
goto out;
ret = decode_sections(file);
ret = decode_file(file);
if (ret)
goto out;
@ -5064,12 +4880,6 @@ int check(struct objtool_file *file)
goto out;
}
if (opts.cfi) {
ret = create_cfi_sections(file);
if (ret)
goto out;
}
if (opts.rethunk) {
ret = create_return_sites_sections(file);
if (ret)
@ -5089,9 +4899,21 @@ int check(struct objtool_file *file)
}
if (opts.prefix) {
ret = create_prefix_symbols(file);
if (ret)
goto out;
if (!opts.cfi) {
ret = create_prefix_symbols(file);
if (ret)
goto out;
} else {
ret = grow_cfi_symbols(file);
if (ret)
goto out;
if (opts.fineibt) {
ret = create_cfi_sections(file);
if (ret)
goto out;
}
}
}
if (opts.ibt) {
@ -5103,12 +4925,6 @@ int check(struct objtool_file *file)
if (opts.noabs)
warnings += check_abs_references(file);
if (opts.checksum) {
ret = create_sym_checksum_section(file);
if (ret)
goto out;
}
if (opts.orc && nr_insns) {
ret = orc_create(file);
if (ret)

View File

@ -210,7 +210,7 @@ static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo)
offset = addr - alt_group->first_insn->offset;
addr = orig_first_insn->offset + offset;
sym = orig_first_insn->sym;
sym = insn_sym(orig_first_insn);
disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo);
@ -222,15 +222,13 @@ static void disas_print_addr_noreloc(bfd_vma addr,
{
struct disas_context *dctx = dinfo->application_data;
struct instruction *insn = dctx->insn;
struct symbol *sym = NULL;
struct symbol *sym = insn_sym(insn);
if (disas_print_addr_alt(addr, dinfo))
return;
if (insn->sym && addr >= insn->sym->offset &&
addr < insn->sym->offset + insn->sym->len) {
sym = insn->sym;
}
if (sym && (addr < sym->offset || addr >= sym->offset + sym->len))
sym = NULL;
disas_print_addr_sym(insn->sec, sym, addr, dinfo);
}
@ -291,9 +289,9 @@ static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo)
* up. So check it first.
*/
jump_dest = insn->jump_dest;
if (jump_dest && jump_dest->sym && jump_dest->offset == addr) {
if (jump_dest && insn_sym(jump_dest) && jump_dest->offset == addr) {
if (!disas_print_addr_alt(addr, dinfo))
disas_print_addr_sym(jump_dest->sec, jump_dest->sym,
disas_print_addr_sym(jump_dest->sec, insn_sym(jump_dest),
addr, dinfo);
return;
}
@ -768,8 +766,8 @@ static int disas_alt_jump(struct disas_alt *dalt)
if (orig_insn->len == 5)
suffix[0] = 'q';
str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix,
dest_insn->offset, dest_insn->sym->name,
dest_insn->offset - dest_insn->sym->offset);
dest_insn->offset, insn_sym(dest_insn)->name,
dest_insn->offset - insn_sym(dest_insn)->offset);
nops = 0;
} else {
str = strfmt("nop%d", orig_insn->len);
@ -794,8 +792,8 @@ static int disas_alt_extable(struct disas_alt *dalt)
alt_insn = dalt->alt->insn;
str = strfmt("resume at 0x%lx <%s+0x%lx>",
alt_insn->offset, alt_insn->sym->name,
alt_insn->offset - alt_insn->sym->offset);
alt_insn->offset, insn_sym(alt_insn)->name,
alt_insn->offset - insn_sym(alt_insn)->offset);
if (!str)
return -1;

View File

@ -27,27 +27,16 @@
static ssize_t demangled_name_len(const char *name);
static inline u32 str_hash(const char *str)
{
return jhash(str, strlen(str), 0);
}
static inline u32 str_hash_demangled(const char *str)
u32 str_hash_demangled(const char *str)
{
return jhash(str, demangled_name_len(str), 0);
}
#define __elf_table(name) (elf->name##_hash)
#define __elf_bits(name) (elf->name##_bits)
#define __elf_table_entry(name, key) \
__elf_table(name)[hash_min(key, __elf_bits(name))]
#define elf_hash_add(name, node, key) \
({ \
struct elf_hash_node *__node = node; \
__node->next = __elf_table_entry(name, key); \
__elf_table_entry(name, key) = __node; \
__node->next = __elf_table_entry(elf, name, key); \
__elf_table_entry(elf, name, key) = __node; \
})
static inline void __elf_hash_del(struct elf_hash_node *node,
@ -69,30 +58,20 @@ static inline void __elf_hash_del(struct elf_hash_node *node,
}
#define elf_hash_del(name, node, key) \
__elf_hash_del(node, &__elf_table_entry(name, key))
#define elf_list_entry(ptr, type, member) \
({ \
typeof(ptr) __ptr = (ptr); \
__ptr ? container_of(__ptr, type, member) : NULL; \
})
#define elf_hash_for_each_possible(name, obj, member, key) \
for (obj = elf_list_entry(__elf_table_entry(name, key), typeof(*obj), member); \
obj; \
obj = elf_list_entry(obj->member.next, typeof(*(obj)), member))
__elf_hash_del(node, &__elf_table_entry(elf, name, key))
#define elf_alloc_hash(name, size) \
({ \
__elf_bits(name) = max(10, ilog2(size)); \
__elf_table(name) = mmap(NULL, sizeof(struct elf_hash_node *) << __elf_bits(name), \
__elf_bits(elf, name) = max(10, ilog2(size)); \
__elf_table(elf, name) = mmap(NULL, \
sizeof(struct elf_hash_node *) << __elf_bits(elf, name), \
PROT_READ|PROT_WRITE, \
MAP_PRIVATE|MAP_ANON, -1, 0); \
if (__elf_table(name) == (void *)-1L) { \
if (__elf_table(elf, name) == (void *)-1L) { \
ERROR_GLIBC("mmap fail " #name); \
__elf_table(name) = NULL; \
__elf_table(elf, name) = NULL; \
} \
__elf_table(name); \
__elf_table(elf, name); \
})
static inline unsigned long __sym_start(struct symbol *s)
@ -141,7 +120,7 @@ struct section *find_section_by_name(const struct elf *elf, const char *name)
{
struct section *sec;
elf_hash_for_each_possible(section_name, sec, name_hash, str_hash(name)) {
elf_hash_for_each_possible(elf, section_name, sec, name_hash, str_hash(name)) {
if (!strcmp(sec->name, name))
return sec;
}
@ -154,7 +133,7 @@ static struct section *find_section_by_index(struct elf *elf,
{
struct section *sec;
elf_hash_for_each_possible(section, sec, hash, idx) {
elf_hash_for_each_possible(elf, section, sec, hash, idx) {
if (sec->idx == idx)
return sec;
}
@ -166,7 +145,7 @@ static struct symbol *find_symbol_by_index(struct elf *elf, unsigned int idx)
{
struct symbol *sym;
elf_hash_for_each_possible(symbol, sym, hash, idx) {
elf_hash_for_each_possible(elf, symbol, sym, hash, idx) {
if (sym->idx == idx)
return sym;
}
@ -228,6 +207,20 @@ struct symbol *find_symbol_containing(const struct section *sec, unsigned long o
return sym ? sym->alias : NULL;
}
/*
* Also match the symbol end address which can be used for a bounds comparison.
*/
struct symbol *find_symbol_containing_inclusive(const struct section *sec,
unsigned long offset)
{
struct symbol *sym = find_symbol_containing(sec, offset);
if (!sym && offset)
sym = find_symbol_containing(sec, offset - 1);
return sym;
}
/*
* Returns size of hole starting at @offset.
*/
@ -285,7 +278,7 @@ struct symbol *find_symbol_by_name(const struct elf *elf, const char *name)
{
struct symbol *sym;
elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) {
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash(name)) {
if (!strcmp(sym->name, name))
return sym;
}
@ -300,7 +293,7 @@ static struct symbol *find_local_symbol_by_file_and_name(const struct elf *elf,
{
struct symbol *sym;
elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash_demangled(name)) {
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash_demangled(name)) {
if (sym->bind == STB_LOCAL && sym->file == file &&
!strcmp(sym->name, name)) {
return sym;
@ -314,7 +307,7 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam
{
struct symbol *sym;
elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash_demangled(name)) {
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash_demangled(name)) {
if (!strcmp(sym->name, name) && !is_local_sym(sym))
return sym;
}
@ -322,21 +315,9 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam
return NULL;
}
void iterate_global_symbol_by_demangled_name(const struct elf *elf,
const char *demangled_name,
void (*process)(struct symbol *sym, void *data),
void *data)
{
struct symbol *sym;
elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(demangled_name)) {
if (!strcmp(sym->demangled_name, demangled_name) && !is_local_sym(sym))
process(sym, data);
}
}
/* If there are multiple matches, return the first one in the range */
struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec,
unsigned long offset, unsigned int len)
unsigned long offset, unsigned int len)
{
struct reloc *reloc, *r = NULL;
struct section *rsec;
@ -347,7 +328,7 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se
return NULL;
for_offset_range(o, offset, offset + len) {
elf_hash_for_each_possible(reloc, reloc, hash,
elf_hash_for_each_possible(elf, reloc, reloc, hash,
sec_offset_hash(rsec, o)) {
if (reloc->sec != rsec)
continue;
@ -358,11 +339,11 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se
r = reloc;
}
}
if (r)
if (r && (reloc_offset(r) & OFFSET_STRIDE_MASK) == o)
return r;
}
return NULL;
return r;
}
struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset)
@ -668,7 +649,7 @@ static int read_symbols(struct elf *elf)
if (is_file_sym(sym))
file = sym;
else if (sym->bind == STB_LOCAL)
else if (sym->bind == STB_LOCAL && !is_sec_sym(sym))
sym->file = file;
}
@ -1016,6 +997,26 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name,
return sym;
}
int elf_write_symbol(struct elf *elf, struct symbol *sym)
{
struct section *symtab, *symtab_shndx;
symtab = find_section_by_name(elf, ".symtab");
if (!symtab) {
ERROR("no .symtab");
return -1;
}
symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
if (elf_update_symbol(elf, symtab, symtab_shndx, sym))
return -1;
mark_sec_changed(elf, symtab, true);
return 0;
}
struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec)
{
struct symbol *sym = calloc(1, sizeof(*sym));
@ -1172,6 +1173,17 @@ static int read_relocs(struct elf *elf)
return 0;
}
static void mark_rodata(struct elf *elf)
{
struct section *sec;
for_each_sec(elf, sec) {
if ((strstarts(sec->name, ".rodata") && !strstr(sec->name, ".str1.")) ||
strstarts(sec->name, ".data.rel.ro"))
sec->rodata = true;
}
}
struct elf *elf_open_read(const char *name, int flags)
{
struct elf *elf;
@ -1222,6 +1234,8 @@ struct elf *elf_open_read(const char *name, int flags)
if (read_sections(elf))
goto err;
mark_rodata(elf);
if (read_symbols(elf))
goto err;

View File

@ -79,6 +79,9 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
unsigned long offset, unsigned int maxlen,
struct instruction *insn);
size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
unsigned char *buf);
bool arch_callee_saved_reg(unsigned char reg);
unsigned long arch_jump_destination(struct instruction *insn);

View File

@ -9,8 +9,8 @@
struct opts {
/* actions: */
bool cfi;
bool checksum;
const char *disas;
bool dump_orc;
bool hack_jump_label;
bool hack_noinstr;
@ -20,6 +20,7 @@ struct opts {
bool noabs;
bool noinstr;
bool orc;
int prefix;
bool retpoline;
bool rethunk;
bool unret;
@ -27,14 +28,14 @@ struct opts {
bool stackval;
bool static_call;
bool uaccess;
int prefix;
const char *disas;
/* options: */
bool backtrace;
bool backup;
bool cfi;
const char *debug_checksum;
bool dryrun;
bool fineibt;
bool link;
bool mnop;
bool module;

View File

@ -68,6 +68,7 @@ struct instruction {
s8 instr;
u32 idx : INSN_CHUNK_BITS,
immediate_len : 4,
dead_end : 1,
ignore_alts : 1,
hint : 1,
@ -81,7 +82,7 @@ struct instruction {
hole : 1,
fake : 1,
trace : 1;
/* 9 bit hole */
/* 4 bit hole */
struct alt_group *alt_group;
struct instruction *jump_dest;
@ -94,14 +95,30 @@ struct instruction {
};
};
struct alternative *alts;
struct symbol *sym;
struct symbol *_sym;
struct stack_op *stack_ops;
struct cfi_state *cfi;
};
/*
* Return the symbol associated with an instruction. For alternative
* replacements, return the symbol of the original code being replaced rather
* than NULL. insn->_sym reflects the actual location in the ELF file.
*/
static inline struct symbol *insn_sym(struct instruction *insn)
{
struct symbol *sym = insn->_sym;
if ((!sym || !is_func_sym(sym)) &&
insn->alt_group && insn->alt_group->orig_group)
sym = insn->alt_group->orig_group->first_insn->_sym;
return sym;
}
static inline struct symbol *insn_func(struct instruction *insn)
{
struct symbol *sym = insn->sym;
struct symbol *sym = insn_sym(insn);
if (sym && sym->type != STT_FUNC)
sym = NULL;
@ -144,6 +161,12 @@ struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset);
struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruction *insn);
struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn);
#define func_for_each_insn(file, func, insn) \
for (insn = find_insn(file, func->sec, func->offset); \
insn; \
insn = next_insn_same_func(file, insn))
#define sec_for_each_insn(file, _sec, insn) \
for (insn = find_insn(file, _sec, 0); \
@ -155,6 +178,11 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc
insn && insn->offset < sym->offset + sym->len; \
insn = next_insn_same_sec(file, insn))
struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn);
int decode_file(struct objtool_file *file);
void free_insns(struct objtool_file *file);
const char *objtool_disas_insn(struct instruction *insn);
extern size_t sym_name_max_len;

View File

@ -6,37 +6,54 @@
#ifdef BUILD_KLP
static inline void checksum_init(struct symbol *func)
static inline void checksum_init(struct symbol *sym)
{
if (func && !func->csum.state) {
func->csum.state = XXH3_createState();
XXH3_64bits_reset(func->csum.state);
if (sym && !sym->csum.state) {
sym->csum.state = XXH3_createState();
XXH3_64bits_reset(sym->csum.state);
}
}
static inline void checksum_update(struct symbol *func,
struct instruction *insn,
const void *data, size_t size)
static inline void __checksum_update(struct symbol *sym, const void *data,
size_t size)
{
XXH3_64bits_update(func->csum.state, data, size);
dbg_checksum(func, insn, XXH3_64bits_digest(func->csum.state));
XXH3_64bits_update(sym->csum.state, data, size);
}
static inline void checksum_finish(struct symbol *func)
static inline void __checksum_update_insn(struct symbol *sym,
struct instruction *insn,
const void *data, size_t size)
{
if (func && func->csum.state) {
func->csum.checksum = XXH3_64bits_digest(func->csum.state);
func->csum.state = NULL;
__checksum_update(sym, data, size);
dbg_checksum_insn(sym, insn, XXH3_64bits_digest(sym->csum.state));
}
static inline void __checksum_update_object(struct symbol *sym,
unsigned long offset,
const char *what, const void *data,
size_t size)
{
__checksum_update(sym, &offset, sizeof(offset));
__checksum_update(sym, data, size);
dbg_checksum_object(sym, offset, what, XXH3_64bits_digest(sym->csum.state));
}
static inline void checksum_finish(struct symbol *sym)
{
if (sym && sym->csum.state) {
sym->csum.checksum = XXH3_64bits_digest(sym->csum.state);
XXH3_freeState(sym->csum.state);
sym->csum.state = NULL;
}
}
int calculate_checksums(struct objtool_file *file);
int create_sym_checksum_section(struct objtool_file *file);
#else /* !BUILD_KLP */
static inline void checksum_init(struct symbol *func) {}
static inline void checksum_update(struct symbol *func,
struct instruction *insn,
const void *data, size_t size) {}
static inline void checksum_finish(struct symbol *func) {}
static inline int calculate_checksums(struct objtool_file *file) { return -ENOSYS; }
static inline int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; }
#endif /* !BUILD_KLP */

View File

@ -21,6 +21,13 @@
#define SEC_NAME_LEN 1024
#define SYM_NAME_LEN 512
static inline u32 str_hash(const char *str)
{
return jhash(str, strlen(str), 0);
}
u32 str_hash_demangled(const char *str);
#define bswap_if_needed(elf, val) __bswap_if_needed(&elf->ehdr, val)
#ifdef LIBELF_USE_DEPRECATED
@ -89,6 +96,7 @@ struct symbol {
u8 changed : 1;
u8 included : 1;
u8 klp : 1;
u8 dont_correlate : 1;
struct list_head pv_target;
struct reloc *relocs;
struct section *group_sec;
@ -130,6 +138,23 @@ struct elf {
struct symbol *symbol_data;
};
#define __elf_table(elf, name) ((elf)->name##_hash)
#define __elf_bits(elf, name) ((elf)->name##_bits)
#define __elf_table_entry(elf, name, key) \
__elf_table(elf, name)[hash_min(key, __elf_bits(elf, name))]
#define elf_list_entry(ptr, type, member) \
({ \
typeof(ptr) __ptr = (ptr); \
__ptr ? container_of(__ptr, type, member) : NULL; \
})
#define elf_hash_for_each_possible(elf, name, obj, member, key) \
for (obj = elf_list_entry(__elf_table_entry(elf, name, key), typeof(*obj), member); \
obj; \
obj = elf_list_entry(obj->member.next, typeof(*(obj)), member))
struct elf *elf_open_read(const char *name, int flags);
struct elf *elf_create_file(GElf_Ehdr *ehdr, const char *name);
@ -175,6 +200,7 @@ struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec,
struct symbol *sym,
s64 addend);
int elf_write_symbol(struct elf *elf, struct symbol *sym);
int elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset,
unsigned int len, const char *insn);
@ -186,10 +212,8 @@ struct symbol *find_func_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset);
struct symbol *find_symbol_by_name(const struct elf *elf, const char *name);
struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name);
void iterate_global_symbol_by_demangled_name(const struct elf *elf, const char *demangled_name,
void (*process)(struct symbol *sym, void *data),
void *data);
struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset);
struct symbol *find_symbol_containing_inclusive(const struct section *sec, unsigned long offset);
int find_symbol_hole_containing(const struct section *sec, unsigned long offset);
struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset);
struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec,
@ -276,11 +300,21 @@ static inline bool is_local_sym(struct symbol *sym)
return sym->bind == STB_LOCAL;
}
static inline bool is_alias_sym(struct symbol *sym)
{
return sym->alias != sym;
}
static inline bool is_prefix_func(struct symbol *sym)
{
return sym->prefix;
}
static inline bool is_cold_func(struct symbol *sym)
{
return sym->cold;
}
static inline bool is_reloc_sec(struct section *sec)
{
return sec->sh.sh_type == SHT_RELA || sec->sh.sh_type == SHT_REL;
@ -296,6 +330,11 @@ static inline bool is_text_sec(struct section *sec)
return sec->sh.sh_flags & SHF_EXECINSTR;
}
static inline bool is_rodata_sec(struct section *sec)
{
return sec->rodata;
}
static inline bool sec_changed(struct section *sec)
{
return sec->_changed;
@ -468,6 +507,16 @@ static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next)
#define for_each_sym_continue(elf, sym) \
list_for_each_entry_continue(sym, &elf->symbols, global_list)
#define for_each_sym_by_name(elf, _name, sym) \
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \
str_hash_demangled(_name)) \
if (strcmp(sym->name, _name)) {} else
#define for_each_sym_by_demangled_name(elf, name, sym) \
elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \
str_hash(name)) \
if (strcmp(sym->demangled_name, name)) {} else
#define rsec_next_reloc(rsec, reloc) \
reloc_idx(reloc) < sec_num_entries(rsec) - 1 ? reloc + 1 : NULL
@ -491,10 +540,10 @@ static inline struct symbol *get_func_prefix(struct symbol *func)
{
struct symbol *prev;
if (!is_func_sym(func))
if (!is_func_sym(func) || !func->offset)
return NULL;
prev = sec_prev_sym(func);
prev = find_func_containing(func->sec, func->offset - 1);
if (prev && is_prefix_func(prev))
return prev;

View File

@ -29,6 +29,7 @@ struct klp_reloc {
u32 type;
};
int cmd_klp_checksum(int argc, const char **argv);
int cmd_klp_diff(int argc, const char **argv);
int cmd_klp_post_link(int argc, const char **argv);

View File

@ -77,13 +77,13 @@ static inline char *offstr(struct section *sec, unsigned long offset)
#define WARN_INSN(insn, format, ...) \
({ \
struct instruction *_insn = (insn); \
if (!_insn->sym || !_insn->sym->warned) { \
if (!insn_sym(_insn) || !insn_sym(_insn)->warned) { \
WARN_FUNC(_insn->sec, _insn->offset, format, \
##__VA_ARGS__); \
BT_INSN(_insn, ""); \
} \
if (_insn->sym) \
_insn->sym->warned = 1; \
if (insn_sym(_insn)) \
insn_sym(_insn)->warned = 1; \
})
#define BT_INSN(insn, format, ...) \
@ -109,7 +109,7 @@ static inline char *offstr(struct section *sec, unsigned long offset)
#define ERROR_FUNC(sec, offset, format, ...) __WARN_FUNC(ERROR_STR, sec, offset, format, ##__VA_ARGS__)
#define ERROR_INSN(insn, format, ...) ERROR_FUNC(insn->sec, insn->offset, format, ##__VA_ARGS__)
extern bool debug;
extern bool debug, debug_correlate, debug_clone;
extern int indent;
static inline void unindent(int *unused) { indent--; }
@ -130,32 +130,39 @@ static inline void unindent(int *unused) { indent--; }
objname ? ": " : "", \
##__VA_ARGS__)
#define dbg(args...) \
#define dbg_checksum_insn(func, insn, checksum) \
({ \
if (unlikely(debug)) \
__dbg(args); \
})
#define __dbg_indent(format, ...) \
({ \
if (unlikely(debug)) \
__dbg("%*s" format, indent * 8, "", ##__VA_ARGS__); \
})
#define dbg_indent(args...) \
int __cleanup(unindent) __dummy_##__COUNTER__; \
__dbg_indent(args); \
indent++
#define dbg_checksum(func, insn, checksum) \
({ \
if (unlikely(insn->sym && insn->sym->pfunc && \
insn->sym->pfunc->debug_checksum)) { \
if (unlikely(func->debug_checksum)) { \
char *insn_off = offstr(insn->sec, insn->offset); \
__dbg("checksum: %s %s %016llx", \
__dbg("checksum: %s(): %s %016llx", \
func->name, insn_off, (unsigned long long)checksum);\
free(insn_off); \
} \
})
#define dbg_checksum_object(sym, offset, what, checksum) \
({ \
if (unlikely(sym->debug_checksum)) \
__dbg("checksum: %s+0x%lx: %s %016llx", \
sym->name, offset, what, \
(unsigned long long)checksum); \
})
#define dbg_correlate(args...) \
({ \
if (unlikely(debug_correlate)) \
__dbg(args); \
})
#define __dbg_clone(format, ...) \
({ \
if (unlikely(debug_clone)) \
__dbg("%*s" format, indent * 8, "", ##__VA_ARGS__); \
})
#define dbg_clone(args...) \
int __cleanup(unindent) __dummy_##__COUNTER__; \
__dbg_clone(args); \
indent++
#endif /* _WARN_H */

View File

@ -0,0 +1,347 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <subcmd/parse-options.h>
#include <objtool/arch.h>
#include <objtool/builtin.h>
#include <objtool/check.h>
#include <objtool/elf.h>
#include <objtool/klp.h>
#include <objtool/objtool.h>
#include <objtool/warn.h>
#include <objtool/checksum.h>
static int checksum_debug_init(struct objtool_file *file)
{
char *dup, *s;
if (!opts.debug_checksum)
return 0;
dup = strdup(opts.debug_checksum);
if (!dup) {
ERROR_GLIBC("strdup");
return -1;
}
s = dup;
while (*s) {
bool found = false;
struct symbol *sym;
char *comma;
comma = strchr(s, ',');
if (comma)
*comma = '\0';
for_each_sym_by_name(file->elf, s, sym) {
if (!is_func_sym(sym) && !is_object_sym(sym))
continue;
sym->debug_checksum = 1;
found = true;
}
if (!found)
WARN("--debug-checksum: can't find '%s'", s);
if (!comma)
break;
s = comma + 1;
}
free(dup);
return 0;
}
static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
struct instruction *insn)
{
struct reloc *reloc = insn_reloc(file, insn);
struct alternative *alt;
unsigned long offset;
struct symbol *sym;
static bool in_alt;
if (insn->fake)
return;
if (!reloc) {
struct symbol *call_dest = insn_call_dest(insn);
struct instruction *jump_dest = insn->jump_dest;
/*
* For a jump/call non-relocated dest offset embedded in the
* instruction, the offset may vary due to changes in
* surrounding code. Just hash the opcode and a
* position-independent representation of the destination.
*/
if (call_dest || jump_dest) {
unsigned char buf[16];
size_t len;
len = arch_jump_opcode_bytes(file, insn, buf);
__checksum_update_insn(func, insn, buf, len);
if (call_dest) {
__checksum_update_insn(func, insn, call_dest->demangled_name,
strlen(call_dest->demangled_name));
} else if (jump_dest) {
struct symbol *dest_sym;
unsigned long offset;
/*
* use insn->_sym instead of insn_sym() here.
* For alternative replacements, the latter
* would give the function of the code being
* replaced.
*/
dest_sym = jump_dest->_sym;
if (!dest_sym)
goto alts;
__checksum_update_insn(func, insn, dest_sym->demangled_name,
strlen(dest_sym->demangled_name));
offset = jump_dest->offset - dest_sym->offset;
__checksum_update_insn(func, insn, &offset, sizeof(offset));
}
goto alts;
}
}
__checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
if (!reloc)
goto alts;
sym = reloc->sym;
offset = arch_insn_adjusted_addend(insn, reloc);
if (is_string_sec(sym->sec)) {
char *str;
str = sym->sec->data->d_buf + sym->offset + offset;
__checksum_update_insn(func, insn, str, strlen(str));
goto alts;
}
if (is_sec_sym(sym)) {
sym = find_symbol_containing(reloc->sym->sec, offset);
if (!sym)
goto alts;
offset -= sym->offset;
}
__checksum_update_insn(func, insn, sym->demangled_name,
strlen(sym->demangled_name));
__checksum_update_insn(func, insn, &offset, sizeof(offset));
alts:
for (alt = insn->alts; alt; alt = alt->next) {
struct alt_group *alt_group = alt->insn->alt_group;
/* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */
if (in_alt)
break;
in_alt = true;
__checksum_update_insn(func, insn, &alt->type,
sizeof(alt->type));
if (alt_group && alt_group->orig_group) {
struct instruction *alt_insn;
__checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature));
for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) {
checksum_update_insn(file, func, alt_insn);
if (!alt_group->last_insn || alt_insn == alt_group->last_insn)
break;
}
} else {
checksum_update_insn(file, func, alt->insn);
}
in_alt = false;
}
}
static void checksum_update_object(struct objtool_file *file, struct symbol *sym)
{
struct reloc *reloc;
__checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len));
if (sym->sec->data->d_buf)
__checksum_update_object(sym, 0, "data",
sym->sec->data->d_buf + sym->offset,
sym->len);
sym_for_each_reloc(file->elf, sym, reloc) {
unsigned long sym_offset = reloc_offset(reloc) - sym->offset;
struct symbol *target = reloc->sym;
s64 offset;
offset = reloc_addend(reloc);
if (is_string_sec(target->sec)) {
char *str;
str = target->sec->data->d_buf + target->offset + offset;
__checksum_update_object(sym, sym_offset,
"reloc string", str, strlen(str));
continue;
}
if (is_sec_sym(target)) {
target = find_symbol_containing(reloc->sym->sec, offset);
if (!target)
continue;
offset -= target->offset;
}
__checksum_update_object(sym, sym_offset, "reloc name",
target->demangled_name,
strlen(target->demangled_name));
__checksum_update_object(sym, sym_offset, "reloc addend",
&offset, sizeof(offset));
}
}
int calculate_checksums(struct objtool_file *file)
{
struct instruction *insn;
struct symbol *sym;
if (checksum_debug_init(file))
return -1;
for_each_sym(file->elf, sym) {
/*
* Skip cold subfunctions and aliases: they share the
* parent's checksum via func_for_each_insn() which
* follows func->cfunc into the cold subfunction.
*/
if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len ||
!sym->sec || !sym->sec->data)
continue;
if (is_func_sym(sym)) {
checksum_init(sym);
func_for_each_insn(file, sym, insn)
checksum_update_insn(file, sym, insn);
checksum_finish(sym);
} else if (is_object_sym(sym)) {
checksum_init(sym);
checksum_update_object(file, sym);
checksum_finish(sym);
}
}
return 0;
}
int create_sym_checksum_section(struct objtool_file *file)
{
struct section *sec;
struct symbol *sym;
unsigned int idx = 0;
struct sym_checksum *checksum;
size_t entsize = sizeof(struct sym_checksum);
sec = find_section_by_name(file->elf, ".discard.sym_checksum");
if (sec) {
if (!opts.dryrun)
WARN("file already has .discard.sym_checksum section, skipping");
return 0;
}
for_each_sym(file->elf, sym)
if (sym->csum.checksum)
idx++;
sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
idx, idx);
if (!sec)
return -1;
idx = 0;
for_each_sym(file->elf, sym) {
if (!sym->csum.checksum)
continue;
if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
sym, 0, R_TEXT64))
return -1;
checksum = (struct sym_checksum *)sec->data->d_buf + idx;
checksum->addr = 0; /* reloc */
checksum->checksum = sym->csum.checksum;
mark_sec_changed(file->elf, sec, true);
idx++;
}
return 0;
}
static const char * const klp_checksum_usage[] = {
"objtool klp checksum [<options>] file.o",
NULL,
};
int cmd_klp_checksum(int argc, const char **argv)
{
struct objtool_file *file;
int ret;
const struct option options[] = {
OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"),
OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"),
OPT_END(),
};
argc = parse_options(argc, argv, options, klp_checksum_usage, 0);
if (argc != 1)
usage_with_options(klp_checksum_usage, options);
opts.checksum = true;
objname = argv[0];
file = objtool_open_read(objname);
if (!file)
return 1;
ret = decode_file(file);
if (ret)
goto out;
ret = calculate_checksums(file);
if (ret)
goto out;
ret = create_sym_checksum_section(file);
out:
free_insns(file);
if (ret)
return ret;
if (!opts.dryrun && file->elf->changed && elf_write(file->elf))
return 1;
return elf_close(file->elf);
}

File diff suppressed because it is too large Load Diff

View File

@ -16,9 +16,6 @@
#include <objtool/objtool.h>
#include <objtool/warn.h>
bool debug;
int indent;
static struct objtool_file file;
struct objtool_file *objtool_open_read(const char *filename)

View File

@ -169,8 +169,8 @@ void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
*/
TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s for instruction at 0x%lx <%s+0x%lx>",
alt_name,
orig_insn->offset, orig_insn->sym->name,
orig_insn->offset - orig_insn->sym->offset);
orig_insn->offset, insn_sym(orig_insn)->name,
orig_insn->offset - insn_sym(orig_insn)->offset);
} else {
TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s", alt_name);
}
@ -185,8 +185,8 @@ void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
if (orig_insn->type == INSN_NOP) {
suffix[0] = (orig_insn->len == 5) ? 'q' : '\0';
TRACE_ADDR(orig_insn, "jmp%-3s %lx <%s+0x%lx>", suffix,
alt_insn->offset, alt_insn->sym->name,
alt_insn->offset - alt_insn->sym->offset);
alt_insn->offset, insn_sym(alt_insn)->name,
alt_insn->offset - insn_sym(alt_insn)->offset);
} else {
TRACE_ADDR(orig_insn, "nop%d", orig_insn->len);
trace_depth--;