xref: /linux/tools/objtool/tests/lib.sh (revision 317cec905af82e9ccca93cec661e88b1d3968a43)
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)"
12d76312b8SSong Liu
13d76312b8SSong Liu# Tests live in generic/ or in an architecture directory beside it, and each
14d76312b8SSong Liu# carries its own fixtures.
15d76312b8SSong LiuFIXTURES_DIR="$(cd "$(dirname "$0")/fixtures" 2>/dev/null && pwd)"
16cff27d90SPuranjay Mohan
17c009fabcSSong Liu# The kernel's convention: CROSS_COMPILE is the one knob, with per-tool
18c009fabcSSong Liu# overrides for what it does not cover.  objtool itself is always a host binary
19c009fabcSSong Liu# -- it is built with HOSTCC and only reads ELF -- so an arm64 machine can run
20c009fabcSSong Liu# the x86 tests against x86 objects given a compiler that emits them.
21c009fabcSSong Liu#
22c009fabcSSong Liu# readelf reads any target, so it rarely needs overriding, and either GNU
23c009fabcSSong Liu# readelf or llvm-readelf will do: the assertions match on fields rather than
24c009fabcSSong Liu# on columns, and where the two spell something differently -- "OS [0xff20]"
25c009fabcSSong Liu# against "OS[0xff20]" for SHN_LIVEPATCH -- they accept both.  BFD's objcopy is
26c009fabcSSong Liu# usually built for the host's target alone, and llvm-objcopy is the
27c009fabcSSong Liu# target-agnostic replacement.
28c009fabcSSong LiuCROSS_COMPILE="${CROSS_COMPILE:-}"
29c009fabcSSong LiuCC="${CC:-${CROSS_COMPILE}gcc}"
30c009fabcSSong LiuLD="${LD:-${CROSS_COMPILE}ld}"
31c009fabcSSong LiuREADELF="${READELF:-${CROSS_COMPILE}readelf}"
32c009fabcSSong LiuOBJCOPY="${OBJCOPY:-${CROSS_COMPILE}objcopy}"
33c009fabcSSong Liu
34cff27d90SPuranjay MohanOBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}"
35c009fabcSSong Liu
36c009fabcSSong Liu# klp_preflight
37c009fabcSSong Liu#
38c009fabcSSong Liu# Check the environment once, before any test runs, and report what was found.
39c009fabcSSong Liu#
40c009fabcSSong Liuklp_preflight()
41c009fabcSSong Liu{
42d76312b8SSong Liu	local tmp tool cc_version arch host cc_arch
43c009fabcSSong Liu
44c009fabcSSong Liu	bail() { echo "Bail out! $*" >&2; exit 1; }
45c009fabcSSong Liu
46c009fabcSSong Liu	# A relative $OBJTOOL is relative to the objtool directory, not tests/.
47c009fabcSSong Liu	[ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] ||
48c009fabcSSong Liu		OBJTOOL="$TESTS_DIR/../$OBJTOOL"
49c009fabcSSong Liu
50c009fabcSSong Liu	[ -x "$OBJTOOL" ] ||
51c009fabcSSong Liu		bail "objtool not found at '$OBJTOOL' -- build it first"
52c009fabcSSong Liu
53c009fabcSSong Liu	# run_diff() runs objtool from inside the test's working directory, so
54c009fabcSSong Liu	# a relative path would resolve against that instead.
55c009fabcSSong Liu	OBJTOOL="$(realpath "$OBJTOOL")"
56c009fabcSSong Liu
57c009fabcSSong Liu	"$OBJTOOL" klp 2>&1 | grep -q checksum ||
58c009fabcSSong Liu		bail "objtool was built without klp support; install libxxhash (>= 0.8) and rebuild"
59c009fabcSSong Liu
60c009fabcSSong Liu	command -v "${CC%% *}" >/dev/null || bail "compiler not found: $CC"
61c009fabcSSong Liu
62c009fabcSSong Liu	for tool in "$READELF" "$OBJCOPY" "$LD"; do
63c009fabcSSong Liu		command -v "${tool%% *}" >/dev/null || bail "$tool not found"
64c009fabcSSong Liu	done
65c009fabcSSong Liu
66c009fabcSSong Liu	tmp="$(mktemp -d)" || bail "mktemp failed"
67c009fabcSSong Liu	echo 'int probe(void) { return 0; }' > "$tmp/probe.c"
68c009fabcSSong Liu	$CC -c -o "$tmp/probe.o" "$tmp/probe.c" 2>/dev/null ||
69c009fabcSSong Liu		{ rm -rf "$tmp"; bail "$CC cannot compile a trivial object"; }
70c009fabcSSong Liu
71c009fabcSSong Liu	# $CC, $ARCH and objtool have to agree about the target, and cross runs
72c009fabcSSong Liu	# are where they stop agreeing: plain "CC=clang ARCH=x86_64" on an arm64
73c009fabcSSong Liu	# box selects the x86 tests and then builds arm64 objects, because clang
74c009fabcSSong Liu	# needs --target= to emit anything but the host's.
75c009fabcSSong Liu	#
76c009fabcSSong Liu	# Ask objtool rather than comparing machine names.  It rejects an object
77c009fabcSSong Liu	# it was not built for -- "unexpected ELF machine type" -- so one check
78c009fabcSSong Liu	# covers every way the three can disagree, and says so once instead of
79c009fabcSSong Liu	# failing every test for the same reason.
80c009fabcSSong Liu	"$OBJTOOL" klp checksum "$tmp/probe.o" >/dev/null 2>&1 ||
81c009fabcSSong Liu		{ rm -rf "$tmp"
82c009fabcSSong Liu		  bail "objtool rejects an object built by '$CC'; they target" \
83c009fabcSSong Liu		       "different architectures (set CROSS_COMPILE, or" \
84c009fabcSSong Liu		       "--target= for clang)"; }
85c009fabcSSong Liu
86c009fabcSSong Liu	# BFD objcopy is usually built for the host's target alone, and
87c009fabcSSong Liu	# checksum_of() needs it to read the object under test.
88c009fabcSSong Liu	$OBJCOPY -O binary --only-section=.text "$tmp/probe.o" "$tmp/probe.bin" 2>/dev/null ||
89c009fabcSSong Liu		{ rm -rf "$tmp"
90c009fabcSSong Liu		  bail "$OBJCOPY cannot read objects built by '$CC'; install" \
91c009fabcSSong Liu		       "binutils-multiarch or set OBJCOPY=llvm-objcopy"; }
92c009fabcSSong Liu	# $ARCH only chooses which directory of tests runs, so it can disagree
93c009fabcSSong Liu	# with what $CC builds without objtool noticing -- and the result is the
94c009fabcSSong Liu	# wrong set of tests, quietly.
95c009fabcSSong Liu	case "$($READELF -hW "$tmp/probe.o" | sed -n 's/.*Machine: *//p')" in
96c009fabcSSong Liu	*X86-64*|*Intel*80386*)	cc_arch=x86 ;;
97c009fabcSSong Liu	*AArch64*)		cc_arch=arm64 ;;
98c009fabcSSong Liu	*)			cc_arch= ;;
99c009fabcSSong Liu	esac
100c009fabcSSong Liu	rm -rf "$tmp"
101c009fabcSSong Liu
102d76312b8SSong Liu	# Normalize to the kernel's SRCARCH.
103d76312b8SSong Liu	case "${ARCH:-$(uname -m)}" in
104d76312b8SSong Liu	x86_64|i?86)	arch=x86 ;;
105d76312b8SSong Liu	aarch64*)	arch=arm64 ;;
106d76312b8SSong Liu	*)		arch="${ARCH:-$(uname -m)}" ;;
107d76312b8SSong Liu	esac
108d76312b8SSong Liu
109d76312b8SSong Liu	case "$(uname -m)" in
110d76312b8SSong Liu	x86_64|i?86)	host=x86 ;;
111d76312b8SSong Liu	aarch64*)	host=arm64 ;;
112d76312b8SSong Liu	*)		host="$(uname -m)" ;;
113d76312b8SSong Liu	esac
114d76312b8SSong Liu
115d76312b8SSong Liu	[ -z "$cc_arch" ] || [ "$cc_arch" = "$arch" ] ||
116d76312b8SSong Liu		bail "ARCH says $arch but '$CC' builds $cc_arch objects;" \
117d76312b8SSong Liu		     "the $arch tests would run against the wrong architecture"
118d76312b8SSong Liu
119d76312b8SSong Liu	KLP_TEST_ARCH="$arch"
120c009fabcSSong Liu	KLP_TEST_PREFLIGHT=done
121d76312b8SSong Liu	export OBJTOOL CC KLP_TEST_ARCH KLP_TEST_PREFLIGHT
122c009fabcSSong Liu
123c009fabcSSong Liu	cc_version="$($CC --version 2>/dev/null | head -1)"
124c009fabcSSong Liu	cat <<EOF
125c009fabcSSong Liu# preflight
126c009fabcSSong Liu#   objtool   $OBJTOOL (klp: yes)
127c009fabcSSong Liu#   compiler  $cc_version
128c009fabcSSong Liu#   arch      $KLP_TEST_ARCH$([ "$arch" = "$host" ] || echo "  (host $host, cross)")
129c009fabcSSong LiuEOF
130c009fabcSSong Liu}
131c009fabcSSong Liu
132c009fabcSSong Liu[ -n "${KLP_TEST_PREFLIGHT:-}" ] || klp_preflight
133cff27d90SPuranjay Mohan
134cff27d90SPuranjay Mohan# klp-build compiles the kernel this way; klp diff needs per-symbol sections to
135cff27d90SPuranjay Mohan# extract individual functions.
136cff27d90SPuranjay MohanFIXTURE_CFLAGS="-c -O2 -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables"
137cff27d90SPuranjay Mohan
138cff27d90SPuranjay Mohantest_name="$(basename "$0" .sh)"
139cff27d90SPuranjay Mohanworkdir=
140cff27d90SPuranjay Mohan
141cff27d90SPuranjay Mohanpass() { echo "ok - $test_name${1:+: $1}"; exit 0; }
142*317cec90SSong Liufail() { echo "not ok - $test_name: $1"; exit 1; }
143*317cec90SSong Liu
144*317cec90SSong Liu# Two kinds of skip, and the runner tells them apart.
145*317cec90SSong Liu#
146*317cec90SSong Liu#   declared_skip  the test said in advance it does not apply here, e.g.
147*317cec90SSong Liu#                  gcc_only on a clang run.  Expected indefinitely.
148*317cec90SSong Liu#   probe_skip     the construct did not turn up in the built object this
149*317cec90SSong Liu#                  time.  Weaker: it may appear on another compiler version,
150*317cec90SSong Liu#                  and one which becomes permanent is a fixture that quietly
151*317cec90SSong Liu#                  stopped testing anything.
152*317cec90SSong Liu#
153*317cec90SSong Liu# A bare skip() is neither, and the runner counts it as a failure: a test which
154*317cec90SSong Liu# gives up for a reason it never declared is a hole, not an outcome.
155*317cec90SSong Liudeclared_skip() { echo "ok - $test_name # SKIP (declared) $*"; exit 0; }
156*317cec90SSong Liuprobe_skip()    { echo "ok - $test_name # SKIP (probe) $*"; exit 0; }
157*317cec90SSong Liuskip()          { echo "ok - $test_name # SKIP $*"; exit 0; }
158*317cec90SSong Liu
159*317cec90SSong Liu# TAP directives.  A test which is known to fail reports it rather than being
160*317cec90SSong Liu# commented out and forgotten, and one which starts passing again says so
161*317cec90SSong Liu# instead of quietly going green: the expectation has to be removed by hand,
162*317cec90SSong Liu# which is the point.
163*317cec90SSong Liuxfail()
164*317cec90SSong Liu{
165*317cec90SSong Liu	echo "not ok - $test_name${1:+: $1} # TODO known failure"
166*317cec90SSong Liu	exit 0
167*317cec90SSong Liu}
168*317cec90SSong Liu
169*317cec90SSong Liuxpass()
170*317cec90SSong Liu{
171*317cec90SSong Liu	echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed"
172*317cec90SSong Liu	exit 1
173*317cec90SSong Liu}
174cff27d90SPuranjay Mohan
175cff27d90SPuranjay Mohancleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; }
176cff27d90SPuranjay Mohan
177cff27d90SPuranjay Mohan# setup [exported symbol...]
178cff27d90SPuranjay Mohansetup()
179cff27d90SPuranjay Mohan{
180c009fabcSSong Liu	# The environment was checked once when this file was sourced, so there
181c009fabcSSong Liu	# is nothing to verify here: objtool exists at the resolved path, has
182c009fabcSSong Liu	# klp support, and $CC works.
183cff27d90SPuranjay Mohan	workdir="$(mktemp -d)" || fail "mktemp failed"
184cff27d90SPuranjay Mohan	trap cleanup EXIT
185cff27d90SPuranjay Mohan
186cff27d90SPuranjay Mohan	export_syms "$@"
187cff27d90SPuranjay Mohan}
188cff27d90SPuranjay Mohan
189cff27d90SPuranjay Mohan# export_syms [symbol...]
190cff27d90SPuranjay Mohan#
191cff27d90SPuranjay Mohan# Rewrite Module.symvers so exactly these symbols are exported by vmlinux.
192cff27d90SPuranjay Mohan# Whether a symbol is listed decides between an ordinary relocation and a klp
193cff27d90SPuranjay Mohan# relocation, so tests flip it to cover both.
194cff27d90SPuranjay Mohanexport_syms()
195cff27d90SPuranjay Mohan{
196cff27d90SPuranjay Mohan	: > "$workdir/Module.symvers"
197cff27d90SPuranjay Mohan	for sym in "$@"; do
198cff27d90SPuranjay Mohan		printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \
199cff27d90SPuranjay Mohan			"$sym" >> "$workdir/Module.symvers"
200cff27d90SPuranjay Mohan	done
201cff27d90SPuranjay Mohan}
202cff27d90SPuranjay Mohan
203*317cec90SSong Liu# gcc_only / clang_only <reason>
204*317cec90SSong Liugcc_only()
205*317cec90SSong Liu{
206*317cec90SSong Liu	case "$($CC --version 2>/dev/null | head -1)" in
207*317cec90SSong Liu	*[Gg][Cc][Cc]*)	return 0 ;;
208*317cec90SSong Liu	esac
209*317cec90SSong Liu	declared_skip "gcc only${1:+: $1}"
210*317cec90SSong Liu}
211*317cec90SSong Liu
212*317cec90SSong Liuclang_only()
213*317cec90SSong Liu{
214*317cec90SSong Liu	case "$($CC --version 2>/dev/null | head -1)" in
215*317cec90SSong Liu	*clang*)	return 0 ;;
216*317cec90SSong Liu	esac
217*317cec90SSong Liu	declared_skip "clang only${1:+: $1}"
218*317cec90SSong Liu}
219*317cec90SSong Liu
220cff27d90SPuranjay Mohan# build_pair <fixture.c> [cflags...]
221cff27d90SPuranjay Mohanbuild_pair()
222cff27d90SPuranjay Mohan{
223cff27d90SPuranjay Mohan	local fixture="$FIXTURES_DIR/$1"; shift
224cff27d90SPuranjay Mohan
225cff27d90SPuranjay Mohan	[ -f "$fixture" ] || fail "missing fixture $fixture"
226cff27d90SPuranjay Mohan
227cff27d90SPuranjay Mohan	$CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" ||
228*317cec90SSong Liu		probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")"
229cff27d90SPuranjay Mohan	$CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" ||
230*317cec90SSong Liu		probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")"
231cff27d90SPuranjay Mohan}
232cff27d90SPuranjay Mohan
233cff27d90SPuranjay Mohan# run_diff [expected exit status]
234cff27d90SPuranjay Mohanrun_diff()
235cff27d90SPuranjay Mohan{
236cff27d90SPuranjay Mohan	local expect="${1:-0}" rc=0
237cff27d90SPuranjay Mohan
238cff27d90SPuranjay Mohan	# Checksums live in the objects, so only generate them once even when a
239cff27d90SPuranjay Mohan	# test diffs the same pair again with a different Module.symvers.
240cff27d90SPuranjay Mohan	if [ ! -e "$workdir/.checksummed" ]; then
241cff27d90SPuranjay Mohan		"$OBJTOOL" klp checksum "$workdir/orig.o" ||
242cff27d90SPuranjay Mohan			fail "klp checksum orig.o failed"
243cff27d90SPuranjay Mohan		"$OBJTOOL" klp checksum "$workdir/patched.o" ||
244cff27d90SPuranjay Mohan			fail "klp checksum patched.o failed"
245cff27d90SPuranjay Mohan		touch "$workdir/.checksummed"
246cff27d90SPuranjay Mohan	fi
247cff27d90SPuranjay Mohan
248cff27d90SPuranjay Mohan	# klp diff looks for Module.symvers relative to the working directory.
249cff27d90SPuranjay Mohan	( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \
250cff27d90SPuranjay Mohan		> "$workdir/diff.log" 2>&1 || rc=$?
251cff27d90SPuranjay Mohan
252cff27d90SPuranjay Mohan	[ "$rc" = "$expect" ] ||
253cff27d90SPuranjay Mohan		fail "klp diff exited $rc, expected $expect: $(tail -2 "$workdir/diff.log")"
254cff27d90SPuranjay Mohan}
255cff27d90SPuranjay Mohan
256cff27d90SPuranjay Mohancc_supports()
257cff27d90SPuranjay Mohan{
258cff27d90SPuranjay Mohan	echo 'int f(void) { return 0; }' > "$workdir/flagtest.c"
259cff27d90SPuranjay Mohan	$CC $1 -c "$workdir/flagtest.c" -o "$workdir/flagtest.o" 2>/dev/null
260cff27d90SPuranjay Mohan}
261cff27d90SPuranjay Mohan
262cff27d90SPuranjay Mohan# partial_link <output> <object...>
263cff27d90SPuranjay Mohan#
264cff27d90SPuranjay Mohan# "ld -r" through the compiler driver so the link targets the same
265cff27d90SPuranjay Mohan# architecture as the objects.
266cff27d90SPuranjay Mohanpartial_link()
267cff27d90SPuranjay Mohan{
268cff27d90SPuranjay Mohan	local out="$1"; shift
269cff27d90SPuranjay Mohan
270cff27d90SPuranjay Mohan	$CC -r -nostdlib -o "$out" "$@" 2>/dev/null ||
271cff27d90SPuranjay Mohan		$CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null
272cff27d90SPuranjay Mohan}
273cff27d90SPuranjay Mohan
274cff27d90SPuranjay Mohan# find_thinlto_toolchain
275cff27d90SPuranjay Mohan#
276*317cec90SSong Liu# Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC).  A
277cff27d90SPuranjay Mohan# mismatched pair fails with "Invalid summary version", which reads like a
278cff27d90SPuranjay Mohan# broken test rather than a broken environment.
279*317cec90SSong Liu#
280*317cec90SSong Liu# ThinLTO is clang-only; callers must use clang_only before calling this.
281*317cec90SSong Liu# Only $CC (or an explicit THIN_CC override) is consulted -- the harness does
282*317cec90SSong Liu# not search for a second compiler beside a gcc $CC.
283cff27d90SPuranjay Mohanfind_thinlto_toolchain()
284cff27d90SPuranjay Mohan{
285*317cec90SSong Liu	local cc ver ld
286cff27d90SPuranjay Mohan
287*317cec90SSong Liu	for cc in "${THIN_CC:-}" "$CC"; do
288cff27d90SPuranjay Mohan		[ -n "$cc" ] || continue
289*317cec90SSong Liu		command -v "${cc%% *}" >/dev/null 2>&1 || return 1
290cff27d90SPuranjay Mohan
291cff27d90SPuranjay Mohan		ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1)
292cff27d90SPuranjay Mohan
293cff27d90SPuranjay Mohan		for ld in "${THIN_LD:-}" "ld.lld-$ver" ld.lld; do
294cff27d90SPuranjay Mohan			[ -n "$ld" ] || continue
295cff27d90SPuranjay Mohan			command -v "$ld" >/dev/null 2>&1 || continue
296cff27d90SPuranjay Mohan
297cff27d90SPuranjay Mohan			echo 'int probe(void) { return 0; }' > "$workdir/probe.c"
298cff27d90SPuranjay Mohan			$cc -flto=thin -O2 -c "$workdir/probe.c" \
299cff27d90SPuranjay Mohan				-o "$workdir/probe.o" 2>/dev/null || continue
300cff27d90SPuranjay Mohan			"$ld" -r "$workdir/probe.o" -o "$workdir/probe.elf" \
301cff27d90SPuranjay Mohan				2>/dev/null || continue
302cff27d90SPuranjay Mohan
303cff27d90SPuranjay Mohan			THIN_CC="$cc"
304cff27d90SPuranjay Mohan			THIN_LD="$ld"
305cff27d90SPuranjay Mohan			return 0
306cff27d90SPuranjay Mohan		done
307cff27d90SPuranjay Mohan	done
308cff27d90SPuranjay Mohan
309cff27d90SPuranjay Mohan	return 1
310cff27d90SPuranjay Mohan}
311cff27d90SPuranjay Mohan
312c009fabcSSong Liuout_sections() { $READELF -S -W "$workdir/out.o" 2>/dev/null; }
313c009fabcSSong Liuout_relocs()   { $READELF -r -W "$workdir/out.o" 2>/dev/null; }
314c009fabcSSong Liuout_symbols()  { $READELF -s -W "$workdir/out.o" 2>/dev/null; }
315cff27d90SPuranjay Mohandiff_log()     { cat "$workdir/diff.log"; }
316cff27d90SPuranjay Mohan
317cff27d90SPuranjay Mohanassert_section()
318cff27d90SPuranjay Mohan{
319cff27d90SPuranjay Mohan	out_sections | grep -q "[[:space:]]$1[[:space:]]" ||
320cff27d90SPuranjay Mohan		fail "expected section '$1' in output"
321cff27d90SPuranjay Mohan}
322cff27d90SPuranjay Mohan
323cff27d90SPuranjay Mohanassert_patched()
324cff27d90SPuranjay Mohan{
325cff27d90SPuranjay Mohan	assert_section ".text.$1"
326cff27d90SPuranjay Mohan}
327cff27d90SPuranjay Mohan
328cff27d90SPuranjay Mohanassert_not_patched()
329cff27d90SPuranjay Mohan{
330cff27d90SPuranjay Mohan	out_sections | grep -q "[[:space:]].text.$1[[:space:]]" &&
331cff27d90SPuranjay Mohan		fail "function '$1' should not have been cloned"
332cff27d90SPuranjay Mohan	return 0
333cff27d90SPuranjay Mohan}
334