1# SPDX-License-Identifier: GPL-2.0 2# 3# Helpers for the objtool klp tests. A test builds a fixture twice, as the 4# original and (with -DPATCHED) the patched object, runs both through 5# "klp checksum" and diffs them, then asserts on the result. 6# 7# Assertions check properties rather than compare against recorded output: 8# codegen varies between compilers and golden files would report churn instead 9# of regressions. 10 11TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 12 13# Tests live in generic/ or in an architecture directory beside it, and each 14# carries its own fixtures. 15FIXTURES_DIR="$(cd "$(dirname "$0")/fixtures" 2>/dev/null && pwd)" 16 17# The kernel's convention: CROSS_COMPILE is the one knob, with per-tool 18# overrides for what it does not cover. objtool itself is always a host binary 19# -- it is built with HOSTCC and only reads ELF -- so an arm64 machine can run 20# the x86 tests against x86 objects given a compiler that emits them. 21# 22# readelf reads any target, so it rarely needs overriding, and either GNU 23# readelf or llvm-readelf will do: the assertions match on fields rather than 24# on columns, and where the two spell something differently -- "OS [0xff20]" 25# against "OS[0xff20]" for SHN_LIVEPATCH -- they accept both. BFD's objcopy is 26# usually built for the host's target alone, and llvm-objcopy is the 27# target-agnostic replacement. 28CROSS_COMPILE="${CROSS_COMPILE:-}" 29CC="${CC:-${CROSS_COMPILE}gcc}" 30LD="${LD:-${CROSS_COMPILE}ld}" 31READELF="${READELF:-${CROSS_COMPILE}readelf}" 32OBJCOPY="${OBJCOPY:-${CROSS_COMPILE}objcopy}" 33 34OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" 35 36# klp_preflight 37# 38# Check the environment once, before any test runs, and report what was found. 39# 40klp_preflight() 41{ 42 local tmp tool cc_version arch host cc_arch 43 44 bail() { echo "Bail out! $*" >&2; exit 1; } 45 46 # A relative $OBJTOOL is relative to the objtool directory, not tests/. 47 [ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] || 48 OBJTOOL="$TESTS_DIR/../$OBJTOOL" 49 50 [ -x "$OBJTOOL" ] || 51 bail "objtool not found at '$OBJTOOL' -- build it first" 52 53 # run_diff() runs objtool from inside the test's working directory, so 54 # a relative path would resolve against that instead. 55 OBJTOOL="$(realpath "$OBJTOOL")" 56 57 "$OBJTOOL" klp 2>&1 | grep -q checksum || 58 bail "objtool was built without klp support; install libxxhash (>= 0.8) and rebuild" 59 60 command -v "${CC%% *}" >/dev/null || bail "compiler not found: $CC" 61 62 for tool in "$READELF" "$OBJCOPY" "$LD"; do 63 command -v "${tool%% *}" >/dev/null || bail "$tool not found" 64 done 65 66 tmp="$(mktemp -d)" || bail "mktemp failed" 67 echo 'int probe(void) { return 0; }' > "$tmp/probe.c" 68 $CC -c -o "$tmp/probe.o" "$tmp/probe.c" 2>/dev/null || 69 { rm -rf "$tmp"; bail "$CC cannot compile a trivial object"; } 70 71 # $CC, $ARCH and objtool have to agree about the target, and cross runs 72 # are where they stop agreeing: plain "CC=clang ARCH=x86_64" on an arm64 73 # box selects the x86 tests and then builds arm64 objects, because clang 74 # needs --target= to emit anything but the host's. 75 # 76 # Ask objtool rather than comparing machine names. It rejects an object 77 # it was not built for -- "unexpected ELF machine type" -- so one check 78 # covers every way the three can disagree, and says so once instead of 79 # failing every test for the same reason. 80 "$OBJTOOL" klp checksum "$tmp/probe.o" >/dev/null 2>&1 || 81 { rm -rf "$tmp" 82 bail "objtool rejects an object built by '$CC'; they target" \ 83 "different architectures (set CROSS_COMPILE, or" \ 84 "--target= for clang)"; } 85 86 # BFD objcopy is usually built for the host's target alone, and 87 # checksum_of() needs it to read the object under test. 88 $OBJCOPY -O binary --only-section=.text "$tmp/probe.o" "$tmp/probe.bin" 2>/dev/null || 89 { rm -rf "$tmp" 90 bail "$OBJCOPY cannot read objects built by '$CC'; install" \ 91 "binutils-multiarch or set OBJCOPY=llvm-objcopy"; } 92 # $ARCH only chooses which directory of tests runs, so it can disagree 93 # with what $CC builds without objtool noticing -- and the result is the 94 # wrong set of tests, quietly. 95 case "$($READELF -hW "$tmp/probe.o" | sed -n 's/.*Machine: *//p')" in 96 *X86-64*|*Intel*80386*) cc_arch=x86 ;; 97 *AArch64*) cc_arch=arm64 ;; 98 *) cc_arch= ;; 99 esac 100 rm -rf "$tmp" 101 102 # Normalize to the kernel's SRCARCH. 103 case "${ARCH:-$(uname -m)}" in 104 x86_64|i?86) arch=x86 ;; 105 aarch64*) arch=arm64 ;; 106 *) arch="${ARCH:-$(uname -m)}" ;; 107 esac 108 109 case "$(uname -m)" in 110 x86_64|i?86) host=x86 ;; 111 aarch64*) host=arm64 ;; 112 *) host="$(uname -m)" ;; 113 esac 114 115 [ -z "$cc_arch" ] || [ "$cc_arch" = "$arch" ] || 116 bail "ARCH says $arch but '$CC' builds $cc_arch objects;" \ 117 "the $arch tests would run against the wrong architecture" 118 119 KLP_TEST_ARCH="$arch" 120 KLP_TEST_PREFLIGHT=done 121 export OBJTOOL CC KLP_TEST_ARCH KLP_TEST_PREFLIGHT 122 123 cc_version="$($CC --version 2>/dev/null | head -1)" 124 cat <<EOF 125# preflight 126# objtool $OBJTOOL (klp: yes) 127# compiler $cc_version 128# arch $KLP_TEST_ARCH$([ "$arch" = "$host" ] || echo " (host $host, cross)") 129EOF 130} 131 132[ -n "${KLP_TEST_PREFLIGHT:-}" ] || klp_preflight 133 134# klp-build compiles the kernel this way; klp diff needs per-symbol sections to 135# extract individual functions. 136FIXTURE_CFLAGS="-c -O2 -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables" 137 138test_name="$(basename "$0" .sh)" 139workdir= 140 141pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } 142fail() { echo "not ok - $test_name: $1" >&2; exit 1; } 143skip() { echo "ok - $test_name # SKIP $1"; exit 0; } 144 145cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } 146 147# setup [exported symbol...] 148setup() 149{ 150 # The environment was checked once when this file was sourced, so there 151 # is nothing to verify here: objtool exists at the resolved path, has 152 # klp support, and $CC works. 153 workdir="$(mktemp -d)" || fail "mktemp failed" 154 trap cleanup EXIT 155 156 export_syms "$@" 157} 158 159# export_syms [symbol...] 160# 161# Rewrite Module.symvers so exactly these symbols are exported by vmlinux. 162# Whether a symbol is listed decides between an ordinary relocation and a klp 163# relocation, so tests flip it to cover both. 164export_syms() 165{ 166 : > "$workdir/Module.symvers" 167 for sym in "$@"; do 168 printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \ 169 "$sym" >> "$workdir/Module.symvers" 170 done 171} 172 173# build_pair <fixture.c> [cflags...] 174build_pair() 175{ 176 local fixture="$FIXTURES_DIR/$1"; shift 177 178 [ -f "$fixture" ] || fail "missing fixture $fixture" 179 180 $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || 181 skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" 182 $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || 183 skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" 184} 185 186# run_diff [expected exit status] 187run_diff() 188{ 189 local expect="${1:-0}" rc=0 190 191 # Checksums live in the objects, so only generate them once even when a 192 # test diffs the same pair again with a different Module.symvers. 193 if [ ! -e "$workdir/.checksummed" ]; then 194 "$OBJTOOL" klp checksum "$workdir/orig.o" || 195 fail "klp checksum orig.o failed" 196 "$OBJTOOL" klp checksum "$workdir/patched.o" || 197 fail "klp checksum patched.o failed" 198 touch "$workdir/.checksummed" 199 fi 200 201 # klp diff looks for Module.symvers relative to the working directory. 202 ( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ 203 > "$workdir/diff.log" 2>&1 || rc=$? 204 205 [ "$rc" = "$expect" ] || 206 fail "klp diff exited $rc, expected $expect: $(tail -2 "$workdir/diff.log")" 207} 208 209cc_supports() 210{ 211 echo 'int f(void) { return 0; }' > "$workdir/flagtest.c" 212 $CC $1 -c "$workdir/flagtest.c" -o "$workdir/flagtest.o" 2>/dev/null 213} 214 215# partial_link <output> <object...> 216# 217# "ld -r" through the compiler driver so the link targets the same 218# architecture as the objects. 219partial_link() 220{ 221 local out="$1"; shift 222 223 $CC -r -nostdlib -o "$out" "$@" 2>/dev/null || 224 $CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null 225} 226 227# find_thinlto_toolchain 228# 229# Set $THIN_CC and $THIN_LD to a clang and lld from the same LLVM release. A 230# mismatched pair fails with "Invalid summary version", which reads like a 231# broken test rather than a broken environment. 232find_thinlto_toolchain() 233{ 234 local cc ld ver 235 236 for cc in "${THIN_CC:-}" "$CC" clang; do 237 [ -n "$cc" ] || continue 238 command -v "${cc%% *}" >/dev/null 2>&1 || continue 239 240 ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1) 241 242 for ld in "${THIN_LD:-}" "ld.lld-$ver" ld.lld; do 243 [ -n "$ld" ] || continue 244 command -v "$ld" >/dev/null 2>&1 || continue 245 246 echo 'int probe(void) { return 0; }' > "$workdir/probe.c" 247 $cc -flto=thin -O2 -c "$workdir/probe.c" \ 248 -o "$workdir/probe.o" 2>/dev/null || continue 249 "$ld" -r "$workdir/probe.o" -o "$workdir/probe.elf" \ 250 2>/dev/null || continue 251 252 THIN_CC="$cc" 253 THIN_LD="$ld" 254 return 0 255 done 256 done 257 258 return 1 259} 260 261out_sections() { $READELF -S -W "$workdir/out.o" 2>/dev/null; } 262out_relocs() { $READELF -r -W "$workdir/out.o" 2>/dev/null; } 263out_symbols() { $READELF -s -W "$workdir/out.o" 2>/dev/null; } 264diff_log() { cat "$workdir/diff.log"; } 265 266assert_section() 267{ 268 out_sections | grep -q "[[:space:]]$1[[:space:]]" || 269 fail "expected section '$1' in output" 270} 271 272assert_patched() 273{ 274 assert_section ".text.$1" 275} 276 277assert_not_patched() 278{ 279 out_sections | grep -q "[[:space:]].text.$1[[:space:]]" && 280 fail "function '$1' should not have been cloned" 281 return 0 282} 283