Files
Snapintosh/tools/test-update-check-sparse.py
Acelogic f47befb905 probes: split xnuboot.c into ordered parts
Phase 1 of decomposing the 15,770-line monitor unit: move contiguous
top-level ranges verbatim into probes/xnuboot/*.c, and have
probes/xnuboot.c #include them in their original order. The compiler
still sees one translation unit. Every #include of a header stays in
probes/xnuboot.c, because quoted includes resolve beside the including
file and the monitor build has no -Icompat.

Cuts fall only between top-level declarations; no function, struct,
initializer or conditional is split, and every part has balanced
#if/#endif. Two exceptions are recorded for phase 2:
- monitor_call_impl is 1,318 lines on its own, so
  probes/xnuboot/sptm_dispatch.c is 1,370 lines.
- The 1,894-line #if SNAP_FINE_LOCKS region is split into three
  consecutive identical #if SNAP_FINE_LOCKS blocks (fine_locks_*.c).
  SNAP_FINE_LOCKS is not defined or undefined inside the region, and
  nothing inside it includes a header.
The parts that carry the m1n1-derived SPTM endpoint numbers and return
codes (report_layout.c, sptm_dispatch.c) keep the file's notice.

Tools that read xnuboot.c as text now read the whole unit through
tools/source_text.py read_source(); tools that hash or pin monitor
sources add every part through source_files() (generic over each .c
in their lists, so later splits are covered the same way).

Verification (base 84dd208 vs this commit, clang 23.1.3):
- clang -c, qualification flags (b12v20 qualification.json, no -flto)
  plus -I shadow -I q1n1/platform/uefi: probes/xnuboot.c objects
  byte-identical (cmp). PASS
- clang -c, probes/run.py FLAGS plus the same -I: byte-identical. PASS
- Qualification flags with -flto: llvm-dis output equal; the full IR
  differs only in 468 inline-asm !srcloc source-location cookies. PASS
- probes/run.py build('xnuboot'), every unit linked: xnuboot.bin and
  xnuboot.elf byte-identical with the qualification flags (LTO,
  bin sha256 0d8b9ec3...) and with run.py FLAGS (2b5d5f6b...). PASS
- Reconstruction: expanding the includes and dropping the part headers
  and the duplicated #if/#endif lines reproduces xnuboot.c exactly.
- Host battery (63 commands: the two gcc SPTM suites, test-idregs,
  test-audit-sptm-refs and every tools/test-*.py that reads these
  sources, plus the edited tools), 300 s limit, base vs split:
  no new failure. 76 of 78 outcomes equal; test-audit-sparse and
  test-monitor-perf timed out (NOT RUN) on split under host load and
  on rerun match base (PASS; FAIL with the same record). Per-class
  reruns of the long suites (21 commands) equal on both trees.
  Pre-existing failures (missing private inputs or stale checks) are
  unchanged.
- wc -l: xnuboot.c 918, every part under 1,000 except sptm_dispatch.c
  (1,370).
2026-10-23 04:04:44 -04:00

129 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""SNAP_UPDATE_CHECK_SPARSE: the user-update kernel skip's six-CPU kernel-copy
re-derivation runs 1 in 1024 skips instead of 1 in 64 (opt-in; a weaker audit,
accepted for speed; the skip itself is unchanged).
python3 tools/test-update-check-sparse.py [--build [--out DIR] [--expect fast=SHA256]]
Offline. Checks that probes/xnuboot.c defaults the define to 0, requires
SNAP_USER_UPDATE_KERNEL_SKIP, changes only the sampling mask (1023 for 63),
and that tools/kernel_ops_perf.py accepts exactly verified == skipped // 1024
with the flag and exactly skipped // 64 without it. --build: absent == =0,
=1 differs, the code budget holds, --expect pins the default build.
Failure inventory: the check silently off (rate 0 or never), the decoder
accepting either rate, a default build that changed.
"""
from pathlib import Path
import argparse
import hashlib
import importlib.util
import json
import os
import struct
import sys
import tempfile
import unittest
from source_text import read_source
ROOT = Path(__file__).resolve().parents[1]
SOURCE = read_source(ROOT / 'probes/xnuboot.c')
def arguments():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--build', action='store_true')
ap.add_argument('--out')
ap.add_argument('--expect', action='append', default=[], metavar='NAME=SHA256')
args, rest = ap.parse_known_args()
if args.out and Path(args.out).exists():
ap.error(f'--out {args.out} exists: use a fresh directory')
return args, rest
ARGS, REST = arguments() if __name__ == '__main__' else (
argparse.Namespace(build=False, out=None, expect=[]), [])
def between(text, start, end, after=0):
begin = text.index(start, after)
return text[begin:text.index(end, begin)]
class Source(unittest.TestCase):
def test_default_and_rate(self):
self.assertIn('#ifndef SNAP_UPDATE_CHECK_SPARSE\n#define SNAP_UPDATE_CHECK_SPARSE 0\n#endif\n'
'_Static_assert(!SNAP_UPDATE_CHECK_SPARSE || SNAP_USER_UPDATE_KERNEL_SKIP,', SOURCE)
skip = between(SOURCE, 'static void user_update_kernel_skip(struct context *c)', '\n}\n')
self.assertIn('#if SNAP_UPDATE_CHECK_SPARSE\n if(!(++k->skipped&1023) && c->kernel_views.count) {\n'
'#else\n if(!(++k->skipped&63) && c->kernel_views.count) {\n#endif', skip)
self.assertEqual(SOURCE.count('SNAP_UPDATE_CHECK_SPARSE'), 5)
def decode(flags, skipped, verified):
spec = importlib.util.spec_from_file_location('kernel_ops_perf', ROOT / 'tools/kernel_ops_perf.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
raw = {'user_update_skip': struct.pack('<3Q', skipped, verified, 0)}
audit = {'root_switch': {}, 'user_updates': {'calls': skipped, 'entries': skipped}}
return module.decode(raw, flags, audit)
class Decoder(unittest.TestCase):
def test_each_rate_exactly(self):
skip = 'SNAP_USER_UPDATE_KERNEL_SKIP'
for skipped in (64, 1023, 1024, 6734020):
decode([skip, 'SNAP_UPDATE_CHECK_SPARSE'], skipped, skipped // 1024)
decode([skip], skipped, skipped // 64)
if skipped // 1024 != skipped // 64:
with self.assertRaises(ValueError):
decode([skip, 'SNAP_UPDATE_CHECK_SPARSE'], skipped, skipped // 64)
with self.assertRaises(ValueError):
decode([skip], skipped, skipped // 1024)
@unittest.skipUnless(ARGS.build, 'pass --build to compile the monitor')
class Build(unittest.TestCase):
def test_monitor_builds(self):
sys.path.insert(0, str(ROOT / 'probes'))
sys.path.insert(0, str(ROOT.parent / 'q1n1' / 'tools'))
import run as monitor_run # noqa: E402
spec = importlib.util.spec_from_file_location('root_miss', ROOT / 'tools/test-root-miss.py')
module = importlib.util.module_from_spec(spec)
saved_argv = sys.argv
sys.argv = [saved_argv[0]]
try:
spec.loader.exec_module(module)
finally:
sys.argv = saved_argv
sets, _ = module.flag_sets()
expect = dict(e.split('=', 1) for e in ARGS.expect)
out = Path(ARGS.out) if ARGS.out else Path(tempfile.mkdtemp(prefix='check-sparse-build-'))
budget = monitor_run.OUT_AT - monitor_run.CODE_AT
saved = list(monitor_run.FLAGS)
results = {}
try:
for name in ('control', 'fast'):
blobs = {}
for label, extra in (('default', []), ('off', ['-DSNAP_UPDATE_CHECK_SPARSE=0']),
('on', ['-DSNAP_UPDATE_CHECK_SPARSE=1'])):
monitor_run.FLAGS[:] = sets[name] + extra
blob = monitor_run.build('xnuboot', out / f'{name}-{label}')
blobs[label] = blob
results[f'{name}-{label}'] = dict(bytes=len(blob), headroom=budget - len(blob),
sha256=hashlib.sha256(blob).hexdigest())
self.assertEqual(blobs['default'], blobs['off'], name)
self.assertNotEqual(blobs['default'], blobs['on'], name)
if name in expect:
self.assertEqual(results[f'{name}-default']['sha256'], expect[name])
finally:
monitor_run.FLAGS[:] = saved
(out / 'result.json').write_text(json.dumps(results, indent=1) + '\n')
print(json.dumps(results, indent=1), file=sys.stderr)
if __name__ == '__main__':
os.chdir(ROOT)
unittest.main(argv=[sys.argv[0], *REST])