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