Skip to content

Commit 49d6542

Browse files
committed
fixtures: recover _text truth where kallsyms withholds it
mips exports only _stext, at any privilege, so a capture from a mips kernel carries no truth for the quantity kasld solves and validate-bundle reports N/A. The booted image carries both symbols, and the gap between them is this build's fact: runtime _text = runtime _stext - (link _stext - link _text). tests/vm/run records that into the capture's meta.txt, and validate-bundle reads it when the sysroot has none, printing the stated source beside it. The truth has to come from the image. Reconstructing _text from the physical address in /proc/iomem needs PAGE_OFFSET, which cancels against the window the truth is checked against, leaving a comparison that holds for any kernel ever built — a check that cannot fail reports as coverage. STEXT_OFFSET is no good either: a per-arch fallback constant, not this build's fact. Verified against kbuild-mips-mainline-7.0: 0x80100000 recovered from _stext 0x80100400, matching System.map, and nothing recorded when the image is stripped or when kallsyms already exports _text. Only the low word reaches shell arithmetic. /bin/sh is dash, whose $(( )) is intmax_t and silently yields 0 for any value above INTMAX_MAX — which every mips64 kernel address is. A cell's variant now reaches the fixture id. It was <distro>-<release> alone, so armv7-mainline-7.0-vmsplit2g and armv7-mainline-7.0 resolved to the same directory, which the capture rm -rf's first: whichever cell ran last was silently the one kept, and the same held for the aarch64 va39/va42 pair. Default cells keep their ids, so nothing is renamed and a re-run is idempotent.
1 parent 1375eec commit 49d6542

2 files changed

Lines changed: 112 additions & 4 deletions

File tree

extra/validate-bundle

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ IM=$BUNDLE/sysroot/proc/iomem
328328
truth_stext=""
329329
truth_text=""
330330
truth_end=""
331+
truth_text_src=""
331332
if [ -s "$KS" ]; then
332333
truth_stext=$(awk '$3 == "_stext" { print $1 }' "$KS")
333334
truth_text=$(awk '$3 == "_text" { print $1 }' "$KS")
@@ -339,6 +340,42 @@ if [ -s "$KS" ]; then
339340
0000000000000000|00000000|"") truth_text="" ;;
340341
esac
341342
fi
343+
# A capture may record the runtime _text directly, for kernels that do not
344+
# export it in kallsyms -- mips exports only _stext, at any privilege, so no
345+
# amount of root recovers it from that file. The recording harness reads it from
346+
# the booted kernel's own symbol table.
347+
#
348+
# It must come from the kernel's symbols and NOT be reconstructed from an
349+
# address KASLD reports. The window this truth is checked against is built from
350+
# the same arch constants a reconstruction would use -- project the physical
351+
# _text from iomem through PAGE_OFFSET, say, and PAGE_OFFSET cancels against a
352+
# window anchored on it, leaving a comparison that holds for any kernel ever
353+
# built. A truth that cannot fail is worse than no truth: it reports as coverage.
354+
#
355+
# So the field is trusted only as far as its stated source, which is printed
356+
# beside it.
357+
if [ -z "$truth_text" ] && [ -s "$BUNDLE/meta.txt" ]; then
358+
_oob=$(awk -F: '/^truth_virt_text:/ { gsub(/[ \t]/, "", $2); print $2 }' \
359+
"$BUNDLE/meta.txt" 2>/dev/null | head -1)
360+
case "$_oob" in
361+
0x* | 0X*) _oob=${_oob#0[xX]} ;;
362+
esac
363+
case "$_oob" in
364+
"" | *[!0-9a-fA-F]*) : ;;
365+
*)
366+
case "$_oob" in
367+
*[1-9a-fA-F]*)
368+
truth_text=$_oob
369+
truth_text_src=$(awk -F: '/^truth_virt_text_source:/ {
370+
sub(/^[^:]*:[ \t]*/, ""); print }' "$BUNDLE/meta.txt" 2>/dev/null |
371+
head -1)
372+
[ -n "$truth_text_src" ] || truth_text_src="source not stated"
373+
;;
374+
esac
375+
;;
376+
esac
377+
fi
378+
342379
# _text is the quantity kasld solves and the one its windows bound. _stext is a
343380
# DIFFERENT symbol: it sits a per-arch head gap above _text -- 0x400 on mips,
344381
# 0x10000 on arm64, 0x20000 on loongarch64 -- and coincides with it only where
@@ -348,8 +385,11 @@ fi
348385
# against a _text answer, which reads as an off-by-head-gap mismatch on exactly
349386
# the arches whose kallsyms exports only _stext (mips does: every recorded boot
350387
# has _stext and no _text). Where the gap is zero the two genuinely are one
351-
# address, and that case is restored below; the rest is checked against kasld's
352-
# own _stext projection instead.
388+
# address, and that case is restored below. Where it is not, the virtual check
389+
# needs the recorded truth above; without it the bundle reports N/A, which is the
390+
# honest answer -- nothing here reconstructs _text from _stext, because the gap
391+
# is a per-build property and STEXT_OFFSET is a fallback constant, not this
392+
# build's fact.
353393
truth_vtext=$truth_text
354394
[ -n "$truth_vtext" ] && truth_vtext=$(hex16 "$truth_vtext")
355395
[ -n "$truth_stext" ] && truth_stext=$(hex16 "$truth_stext")
@@ -377,7 +417,9 @@ fi
377417
echo
378418

379419
printf '%sGround truth:%s\n' "$BOLD" "$RESET"
380-
if [ -n "$truth_vtext" ]; then
420+
if [ -n "$truth_vtext" ] && [ -n "$truth_text_src" ]; then
421+
printf ' virt _text = 0x%s (recorded: %s)\n' "$truth_vtext" "$truth_text_src"
422+
elif [ -n "$truth_vtext" ]; then
381423
printf ' virt _text = 0x%s\n' "$truth_vtext"
382424
elif [ -n "$truth_stext" ]; then
383425
printf ' virt _text = %s(kallsyms exports _stext 0x%s, not _text)%s\n' \

tests/vm/run

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,57 @@ region_verdict() {
303303
# verdict <log>: prints "PASS|FAIL|N/A <detail>" and returns 0 only on PASS/NA.
304304
# Gates on BOTH axes: the virtual text base AND (on decoupled arches) the physical
305305
# image base must lie inside their resolved windows.
306+
# Runtime _text truth for a kernel that does not export _text through kallsyms.
307+
#
308+
# mips exports only _stext, at any privilege, so a capture from one carries no
309+
# truth for the quantity kasld solves. The image that was booted carries both
310+
# symbols, and the gap between them is this build's fact: runtime _text =
311+
# runtime _stext - (link _stext - link _text).
312+
#
313+
# It has to come from the image. Reconstructing _text from the physical address
314+
# in /proc/iomem would need PAGE_OFFSET, which cancels against the window the
315+
# truth is later checked against -- leaving a comparison that holds for any
316+
# kernel ever built. STEXT_OFFSET is no good either: a per-arch fallback
317+
# constant, not this build's fact.
318+
#
319+
# Only the low word reaches the shell. Arithmetic here is intmax_t, and every
320+
# mips64 kernel address is above INTMAX_MAX, where dash silently yields 0 --
321+
# so the halves are compared as strings and only the low 32 bits are subtracted.
322+
hex_gap() {
323+
_ga=$(printf '%s' "$1" | tr 'A-F' 'a-f')
324+
_gb=$(printf '%s' "$2" | tr 'A-F' 'a-f')
325+
# A 32-bit address is exactly the low word, so the high half is empty -- that
326+
# is a valid case, not a missing one. Equal halves is the whole requirement.
327+
_gah=${_ga%????????}; _gbh=${_gb%????????}
328+
[ "$_ga" != "$_gah" ] && [ "$_gah" = "$_gbh" ] || return 1
329+
_gd=$(( 0x${_gb#"$_gbh"} - 0x${_ga#"$_gah"} ))
330+
[ "$_gd" -ge 0 ] || return 1
331+
printf '%s' "$_gd"
332+
}
333+
334+
# Echoes the runtime _text as 0x-hex, or nothing when it cannot be established:
335+
# no image symbols (stripped or compressed), no runtime _stext, or the image
336+
# already exports _text, in which case the capture needs no recorded truth.
337+
runtime_text_truth() {
338+
_rt_img=$1; _rt_ks=$2
339+
[ -f "$_rt_img" ] && [ -s "$_rt_ks" ] || return 0
340+
command -v readelf >/dev/null 2>&1 || return 0
341+
awk '$3 == "_text" { exit 1 }' "$_rt_ks" || return 0
342+
_rt_st=$(awk '$3 == "_stext" { print $1; exit }' "$_rt_ks")
343+
[ -n "$_rt_st" ] || return 0
344+
_rt_syms=$(readelf -sW "$_rt_img" 2>/dev/null) || return 0
345+
_rt_lt=$(printf '%s\n' "$_rt_syms" | awk '$8 == "_text" { print $2; exit }')
346+
_rt_ls=$(printf '%s\n' "$_rt_syms" | awk '$8 == "_stext" { print $2; exit }')
347+
[ -n "$_rt_lt" ] && [ -n "$_rt_ls" ] || return 0
348+
_rt_gap=$(hex_gap "$_rt_lt" "$_rt_ls") || return 0
349+
_rt_lo=$(printf '%s' "$_rt_st" | tr 'A-F' 'a-f')
350+
_rt_hi=${_rt_lo%????????}
351+
[ "$_rt_lo" != "$_rt_hi" ] || return 0
352+
_rt_d=$(( 0x${_rt_lo#"$_rt_hi"} - _rt_gap ))
353+
[ "$_rt_d" -ge 0 ] || return 0
354+
printf '0x%s%08x' "$_rt_hi" "$_rt_d"
355+
}
356+
306357
verdict() {
307358
_log=$1
308359
grep -aq 'KASLD VM DONE' "$_log" || { echo "INCOMPLETE (boot did not finish; see $_log)"; return 1; }
@@ -463,7 +514,14 @@ reconstruct_fixture() {
463514
# flavor=local => a mainline kernel.org build; else an Alpine distro kernel.
464515
_canon=$(fixture_arch "$_prefix")
465516
if [ "$_flavor" = local ]; then _distro="mainline"; else _distro="alpine-$ALPINE_VER"; fi
466-
_fid=$(printf '%s-%s' "$_distro" "$_rel" | sed 's/[^A-Za-z0-9._-]/-/g')
517+
# A cell name is <prefix>-<flavor>-<version>[-<variant>]; the variant is what
518+
# makes the boot non-default (a VA size, a VMSPLIT). It has to reach the
519+
# fixture id: without it a variant cell and its default sibling resolve to the
520+
# same directory, and the capture rm -rf's the destination first, so whichever
521+
# ran last would silently be the one kept.
522+
_variant=$(printf '%s' "$_name" | cut -s -d- -f4-)
523+
_fid=$(printf '%s-%s%s' "$_distro" "$_rel" "${_variant:+-$_variant}" |
524+
sed 's/[^A-Za-z0-9._-]/-/g')
467525

468526
# Boot image: size (sizes.txt) + 4 KiB header prefix, taken host-side from the
469527
# kernel we booted — mirrors extra/collect's boot_image "hdr" mode.
@@ -476,6 +534,8 @@ reconstruct_fixture() {
476534
: > "$_tmp/sizes.txt"
477535
fi
478536

537+
_tvt=$(runtime_text_truth "$_kimg" "$_sr/proc/kallsyms")
538+
479539
_kbin=""
480540
for _c in "$BUILD_DIR/$_prefix"-*/kasld; do [ -x "$_c" ] && { _kbin=$_c; break; }; done
481541
_kid=unknown
@@ -493,6 +553,12 @@ reconstruct_fixture() {
493553
echo "host_scrubbed: 1"
494554
echo "kallsyms: 1"
495555
echo "source: vm ($_name)"
556+
# Only where the kernel withholds _text from kallsyms; elsewhere the sysroot
557+
# already carries it, and a second copy is a second thing to keep true.
558+
if [ -n "$_tvt" ]; then
559+
echo "truth_virt_text: $_tvt"
560+
echo "truth_virt_text_source: booted vmlinux symtab (_stext - head gap)"
561+
fi
496562
} > "$_tmp/meta.txt"
497563

498564
dest="$ROOT/tests/fixtures/$_canon/$_fid"

0 commit comments

Comments
 (0)