xref: /linux/tools/objtool/tests/lib.sh (revision ae04b6d0f9bf308d1e80cd8d7fb0f917ae52c2ef)
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# What every fixture is built with.  These describe the kernel a fixture stands
135# in for; -c is build_one's contract rather than a property of that kernel, so
136# it lives at the compile where an override cannot drop it.
137#
138#   -O2					the kernel's default
139#   -ffunction-sections -fdata-sections	klp-build passes these itself, through
140#					KCFLAGS, whatever the configuration
141#   -fno-asynchronous-unwind-tables	arch/x86/Makefile sets this always, so
142#					kernel objects carry no .eh_frame
143#   -fno-common				the kernel's Makefile sets it, so an
144#					uninitialised global there lands in
145#					.bss rather than being SHN_COMMON,
146#					which has no section and so no
147#					checksum
148#
149# A test overrides it; see tools/objtool/Documentation/klp-write-tests.txt.
150FIXTURE_CFLAGS="-O2 -ffunction-sections -fdata-sections -fno-common \
151		-fno-asynchronous-unwind-tables"
152
153test_name="$(basename "$0" .sh)"
154workdir=
155
156# The pair run_diff() and the checksum helpers work on.  build_pair() names
157# them again and make_vmlinux_pair() repoints orig_obj at the image it links,
158# but a test which builds its objects itself with build_one() sets neither, so
159# the default belongs here.
160orig_obj=orig.o
161patched_obj=patched.o
162
163pass() { echo "ok - $test_name${1:+: $1}"; exit 0; }
164fail() { echo "not ok - $test_name: $1"; exit 1; }
165
166# Two kinds of skip, and the runner tells them apart.
167#
168#   declared_skip  the test said in advance it does not apply here, e.g.
169#                  gcc_only on a clang run.  Expected indefinitely.
170#   probe_skip     the construct did not turn up in the built object this
171#                  time.  Weaker: it may appear on another compiler version,
172#                  and one which becomes permanent is a fixture that quietly
173#                  stopped testing anything.
174#
175# A bare skip() is neither, and the runner counts it as a failure: a test which
176# gives up for a reason it never declared is a hole, not an outcome.
177declared_skip() { echo "ok - $test_name # SKIP (declared) $*"; exit 0; }
178probe_skip()    { echo "ok - $test_name # SKIP (probe) $*"; exit 0; }
179skip()          { echo "ok - $test_name # SKIP $*"; exit 0; }
180
181# TAP directives.  A test which is known to fail reports it rather than being
182# commented out and forgotten, and one which starts passing again says so
183# instead of quietly going green: the expectation has to be removed by hand,
184# which is the point.
185xfail()
186{
187	echo "not ok - $test_name${1:+: $1} # TODO known failure"
188	exit 0
189}
190
191xpass()
192{
193	echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed"
194	exit 1
195}
196
197cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; }
198
199# setup [exported symbol...]
200setup()
201{
202	# The environment was checked once when this file was sourced, so there
203	# is nothing to verify here: objtool exists at the resolved path, has
204	# klp support, and $CC works.
205	workdir="$(mktemp -d)" || fail "mktemp failed"
206	trap cleanup EXIT
207
208	export_syms "$@"
209}
210
211# export_syms [symbol...]
212#
213# Rewrite Module.symvers so exactly these symbols are exported by vmlinux.
214# Whether a symbol is listed decides between an ordinary relocation and a klp
215# relocation, so tests flip it to cover both.
216export_syms()
217{
218	: > "$workdir/Module.symvers"
219	for sym in "$@"; do
220		printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \
221			"$sym" >> "$workdir/Module.symvers"
222	done
223}
224
225# gcc_only / clang_only <reason>
226gcc_only()
227{
228	case "$($CC --version 2>/dev/null | head -1)" in
229	*[Gg][Cc][Cc]*)	return 0 ;;
230	esac
231	declared_skip "gcc only${1:+: $1}"
232}
233
234clang_only()
235{
236	case "$($CC --version 2>/dev/null | head -1)" in
237	*clang*)	return 0 ;;
238	esac
239	declared_skip "clang only${1:+: $1}"
240}
241
242# build_one <fixture.c> <output object> [cflags...]
243build_one()
244{
245	local fixture out
246	fixture="$FIXTURES_DIR/$1"
247	out="$workdir/$2"
248	shift 2
249
250	[ -f "$fixture" ] || fail "missing fixture $fixture"
251
252	# run_checksum only runs once per workdir.  A fresh object has no
253	# checksums in it, so anything built now needs that to happen again.
254	rm -f "$workdir/.checksummed"
255
256	$CC -c $FIXTURE_CFLAGS "$@" -o "$out" "$fixture" 2>"$workdir/cc.log" ||
257		fail "$(basename "$fixture") does not build: $(tail -1 "$workdir/cc.log")"
258}
259
260# build_pair <fixture.c> [cflags...]
261build_pair()
262{
263	local fixture="$1"; shift
264
265	# Name what this builds.  A test may run several segments, and
266	# make_vmlinux_pair() repoints orig_obj at the image it links, so
267	# without this the next run_diff() would still be reading that.
268	orig_obj=orig.o
269	patched_obj=patched.o
270
271	build_one "$fixture" orig.o "$@"
272	build_one "$fixture" patched.o "$@" -DPATCHED
273}
274
275# run_objtool_check <objtool arguments...>
276#
277# Run objtool's ordinary check pass over the pair, as the kernel build does.
278#
279# Some of what klp diff consumes is produced by this pass rather than by the
280# compiler: .static_call_sites, .mcount_loc, .ibt_endbr_seal, ORC.
281#
282# Only module objects see it before klp-build -- with CONFIG_KLP_BUILD the
283# per-object pass is deferred, so built-in objects reach klp diff exactly as
284# the compiler left them.
285run_objtool_check()
286{
287	local obj
288
289	# This rewrites both objects, so checksums taken before it describe
290	# something that no longer exists.  As in build_one(), drop the marker
291	# so run_checksum() takes them again.
292	rm -f "$workdir/.checksummed"
293
294	for obj in "$orig_obj" "$patched_obj"; do
295		"$OBJTOOL" "$@" "$workdir/$obj" ||
296			fail "objtool $* failed on $obj"
297	done
298}
299
300run_checksum()
301{
302	# Checksums live in the objects, and a test may ask for them more than
303	# once -- diffing the same pair again with a different Module.symvers,
304	# say.  objtool does the right thing when asked twice, leaving the
305	# object alone, but it says so, and that warning would be most of what
306	# a passing run prints.  Remember instead, and keep quiet.
307	[ -e "$workdir/.checksummed" ] && return 0
308
309	"$OBJTOOL" klp checksum "$workdir/$orig_obj" ||
310		fail "klp checksum $orig_obj failed"
311	"$OBJTOOL" klp checksum "$workdir/$patched_obj" ||
312		fail "klp checksum $patched_obj failed"
313	touch "$workdir/.checksummed"
314}
315
316# run_diff [expected exit status]
317run_diff()
318{
319	local expect="${1:-0}" rc=0
320
321	run_checksum
322
323	# klp diff looks for Module.symvers relative to the working directory.
324	( cd "$workdir" && "$OBJTOOL" klp diff "$orig_obj" "$patched_obj" out.o ) \
325		> "$workdir/diff.log" 2>&1 || rc=$?
326
327	[ "$rc" = "$expect" ] ||
328		fail "klp diff exited $rc, expected $expect: $(tail -2 "$workdir/diff.log")"
329}
330
331cc_supports()
332{
333	echo 'int f(void) { return 0; }' > "$workdir/flagtest.c"
334	$CC $1 -c "$workdir/flagtest.c" -o "$workdir/flagtest.o" 2>/dev/null
335}
336
337# partial_link <output> <object...>
338#
339# "ld -r" through the compiler driver so the link targets the same
340# architecture as the objects.
341partial_link()
342{
343	local out="$1"; shift
344
345	rm -f "$workdir/.checksummed"
346
347	$CC -r -nostdlib -o "$out" "$@" 2>/dev/null ||
348		$CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null
349}
350
351# link_vmlinux <output> <object...>
352#
353# Link objects into an executable, the way the kernel's final link produces
354# vmlinux from vmlinux.o.  Entry point 0 and no libc: nothing runs it, it only
355# has to be a linked image with resolved addresses.
356#
357# The sub-sections have to come out in name order rather than object order,
358# the way the kernel's linker script gathers .text.unlikely and .data.. apart
359# from the rest.  That reordering is the entire reason .klp.symid exists: a
360# link which preserves order cannot tell a correct sympos from one that merely
361# counted, and the caller checks the two orders really did diverge.
362#
363# A linker script rather than --sort-section=name, because lld accepts that
364# option and ignores it -- so on a host where only lld can link the target, the
365# test would quietly stop testing the thing it is named for.
366#
367# Three attempts because a cross run has neither $LD nor the compiler's default
368# linker able to touch the target: on an arm64 host linking x86 objects, only
369# lld will do it.
370link_vmlinux()
371{
372	local out="$1" lds="$workdir/sort.lds"; shift
373
374	echo 'SECTIONS { .data : { *(SORT_BY_NAME(.data.*)) } }' > "$lds"
375
376	$LD -e 0 -T "$lds" -o "$out" "$@" 2>/dev/null ||
377		$CC -nostdlib -Wl,-e,0 -Wl,-T,"$lds" \
378			-o "$out" "$@" 2>/dev/null ||
379		$CC -nostdlib -fuse-ld=lld -Wl,-e,0 -Wl,-T,"$lds" \
380			-o "$out" "$@" 2>/dev/null
381}
382
383# make_vmlinux_pair <orig object...> -- <patched object...>
384#
385# Build the vmlinux.o / vmlinux pair klp diff needs to resolve sympos the way
386# it does for built-in code, and point the diff at it.
387#
388# For a module, sympos is a count in symbol table order, which klp diff can do
389# from the object alone.  vmlinux is different: the final link reorders
390# sub-sections, so the position comes from the linked image, bridged by
391# .klp.symid.  klp diff only looks for that when the object it was handed is
392# called vmlinux.o and a vmlinux sits beside it -- so both the name and the
393# linked image matter.
394make_vmlinux_pair()
395{
396	local orig=() patched=() seen= arg
397
398	for arg in "$@"; do
399		if [ "$arg" = -- ]; then seen=y; continue; fi
400		if [ -n "$seen" ]; then patched+=( "$arg" ); else orig+=( "$arg" ); fi
401	done
402
403	# Both sides have to have been named.  Without this, forgetting the --
404	# leaves one list empty, the link of nothing fails, and the test skips
405	# saying the toolchain cannot link -- which is a test bug wearing the
406	# costume of an environment one.
407	[ "${#orig[@]}" -gt 0 ] && [ "${#patched[@]}" -gt 0 ] ||
408		fail "make_vmlinux_pair needs objects either side of --"
409
410	partial_link "$workdir/vmlinux.o" "${orig[@]}" ||
411		probe_skip "partial link unavailable"
412	partial_link "$workdir/patched.o" "${patched[@]}" ||
413		probe_skip "partial link unavailable"
414
415	"$OBJTOOL" --klp-symids --link "$workdir/vmlinux.o" ||
416		fail "objtool --klp-symids failed"
417
418	link_vmlinux "$workdir/vmlinux" "$workdir/vmlinux.o" ||
419		probe_skip "cannot link a vmlinux here"
420
421	orig_obj=vmlinux.o
422}
423
424# build_module_pair <fixture.c> <module name> [cflags...]
425#
426# Build the pair as objects belonging to a module rather than to vmlinux.  klp
427# diff reads the object's module name from .modinfo, and that decides which
428# object a relocation is attributed to and whether a reference counts as
429# cross-module, so a good deal of the code has a module path the vmlinux
430# fixtures never reach.
431#
432# The fixture defines its .modinfo name from MODNAME.  Passing that through
433# -D needs two levels of quoting, which is easy to get wrong at the call site.
434build_module_pair()
435{
436	local fixture="$1" modname="$2"; shift 2
437
438	build_pair "$fixture" -DMODNAME="\"$modname\"" "$@"
439}
440
441# find_thinlto_toolchain
442#
443# Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC).  A
444# mismatched pair fails with "Invalid summary version", which reads like a
445# broken test rather than a broken environment.
446#
447# ThinLTO is clang-only; callers must use clang_only before calling this.
448# Only $CC (or an explicit THIN_CC override) is consulted -- the harness does
449# not search for a second compiler beside a gcc $CC.
450find_thinlto_toolchain()
451{
452	local cc ver ld
453
454	for cc in "${THIN_CC:-}" "$CC"; do
455		[ -n "$cc" ] || continue
456		command -v "${cc%% *}" >/dev/null 2>&1 || return 1
457
458		ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1)
459
460		for ld in "${THIN_LD:-}" "ld.lld-$ver" ld.lld; do
461			[ -n "$ld" ] || continue
462			command -v "$ld" >/dev/null 2>&1 || continue
463
464			echo 'int probe(void) { return 0; }' > "$workdir/probe.c"
465			$cc -flto=thin -O2 -c "$workdir/probe.c" \
466				-o "$workdir/probe.o" 2>/dev/null || continue
467			"$ld" -r "$workdir/probe.o" -o "$workdir/probe.elf" \
468				2>/dev/null || continue
469
470			THIN_CC="$cc"
471			THIN_LD="$ld"
472			return 0
473		done
474	done
475
476	return 1
477}
478
479out_sections() { $READELF -S -W "$workdir/out.o" 2>/dev/null; }
480out_relocs()   { $READELF -r -W "$workdir/out.o" 2>/dev/null; }
481out_symbols()  { $READELF -s -W "$workdir/out.o" 2>/dev/null; }
482diff_log()     { cat "$workdir/diff.log"; }
483
484assert_section()
485{
486	out_sections | grep -q "[[:space:]]$1[[:space:]]" ||
487		fail "expected section '$1' in output"
488}
489
490assert_patched()
491{
492	assert_section ".text.$1"
493}
494
495assert_not_patched()
496{
497	out_sections | grep -q "[[:space:]].text.$1[[:space:]]" &&
498		fail "function '$1' should not have been cloned"
499	return 0
500}
501