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