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).
150 lines
6.0 KiB
Python
Executable File
150 lines
6.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: MIT
|
|
"""SNAP_PANIC_SERVE: after XNU's PANIC_ENTRY, ordinary monitor calls are served, not stopped.
|
|
|
|
python3 tools/test-panic-serve.py [--build]
|
|
|
|
1. The gate in probes/xnuboot.c monitor_call_impl is extracted verbatim and compiled on the host
|
|
against compat/panic-state.c, with SNAP_PANIC_SERVE 0 and 1: before a panic every call passes;
|
|
after a valid panic entry the default stops on every non-entry call and the opt-in serves it
|
|
(counted in panic_state.rejected); a corrupt panic state stops in both.
|
|
2. --build: the monitor's control, fast and fast+WFI default builds equal their -DSNAP_PANIC_SERVE=0
|
|
builds and differ from =1.
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from source_text import read_source
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
checks = 0
|
|
|
|
|
|
def expect(ok, what):
|
|
global checks
|
|
checks += 1
|
|
if not ok:
|
|
raise AssertionError(what)
|
|
|
|
|
|
def gate():
|
|
text = read_source(ROOT / 'probes/xnuboot.c')
|
|
expect(text.count('#ifndef SNAP_PANIC_SERVE\n#define SNAP_PANIC_SERVE 0\n#endif') == 1, 'define block')
|
|
m = re.search(r' if\(!sptm_panic_dispatch\(&c->panic_state,\(unsigned\)vcpu_of\(c\)->slot,x16\)\n'
|
|
r'(#if SNAP_PANIC_SERVE\n.*?\n#endif\n) \) \{\n'
|
|
r' record\(c,EV_PANIC_STATE_FAILED,\*elr,x16,c->panic_state\.owner,vcpu_of\(c\)->slot\);\n'
|
|
r' return 0;\n', text, re.S)
|
|
expect(m is not None, 'gate shape in monitor_call_impl')
|
|
expect(text.count('SNAP_PANIC_SERVE') == 3, 'SNAP_PANIC_SERVE used only in the define block and the gate')
|
|
return m.group(1)
|
|
|
|
|
|
HARNESS = r'''
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "panic-state.h"
|
|
struct vcpu { unsigned slot; };
|
|
struct context { struct sptm_panic_state panic_state; struct vcpu v; };
|
|
static struct vcpu *vcpu_of(struct context *c) { return &c->v; }
|
|
static int stops;
|
|
static void record(struct context *c, int kind, uint64_t a, uint64_t b, uint64_t d, uint64_t e)
|
|
{ (void)c; (void)kind; (void)a; (void)b; (void)d; (void)e; stops++; }
|
|
#define EV_PANIC_STATE_FAILED 43
|
|
static int call(struct context *c, uint64_t x16, unsigned cpu)
|
|
{
|
|
uint64_t elr0 = 0, *elr = &elr0;
|
|
c->v.slot = cpu;
|
|
if(!sptm_panic_dispatch(&c->panic_state,(unsigned)vcpu_of(c)->slot,x16)
|
|
@GATE@ ) {
|
|
record(c,EV_PANIC_STATE_FAILED,*elr,x16,c->panic_state.owner,vcpu_of(c)->slot);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
#define CHECK(x) do { if (!(x)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #x); exit(1); } n++; } while (0)
|
|
int main(void)
|
|
{
|
|
int n = 0;
|
|
struct context c;
|
|
sptm_panic_init(&c.panic_state);
|
|
for (unsigned cpu = 0; cpu < 6; cpu++) CHECK(call(&c, 0xd, cpu) == 1);
|
|
CHECK(c.panic_state.rejected == 0 && stops == 0);
|
|
CHECK(sptm_panic_enter(&c.panic_state, 2));
|
|
CHECK(c.panic_state.owner == 2);
|
|
/* v32: CPU 1 calls SWITCH_ROOT (endpoint 0xd) after CPU 2's entry. */
|
|
CHECK(call(&c, 0xd, 1) == @SERVE@);
|
|
CHECK(call(&c, 0x2, 2) == @SERVE@); /* the owner's ordinary call */
|
|
CHECK(call(&c, UINT64_C(0x0200000000000000), 3) == 1); /* exact PANIC_ENTRY word: admitted either way */
|
|
CHECK(c.panic_state.rejected == 2);
|
|
CHECK(stops == (@SERVE@ ? 0 : 2));
|
|
/* A corrupt panic state stops in both builds. */
|
|
c.panic_state.owner = 7;
|
|
int before = stops;
|
|
CHECK(call(&c, 0xd, 1) == 0 && stops == before + 1);
|
|
c.panic_state.owner = 2; c.panic_state.phase = 0;
|
|
CHECK(call(&c, 0xd, 1) == 0 && stops == before + 2);
|
|
c.panic_state.phase = 1;
|
|
CHECK(call(&c, 0xd, 6) == 0); /* CPU out of range */
|
|
printf("%d\n", n);
|
|
return 0;
|
|
}
|
|
'''
|
|
|
|
|
|
def host(serve):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
src = Path(tmp) / 'h.c'
|
|
src.write_text(HARNESS.replace('@SERVE@', str(serve)).replace('@GATE@', gate()))
|
|
exe = Path(tmp) / 'h'
|
|
subprocess.run(['cc', '-std=c11', '-Wall', '-Werror', '-fsanitize=address,undefined',
|
|
f'-DSNAP_PANIC_SERVE={serve}', '-I', str(ROOT / 'compat'), str(src),
|
|
str(ROOT / 'compat/panic-state.c'), '-o', str(exe)], check=True)
|
|
out = subprocess.run([str(exe)], check=True, capture_output=True, text=True).stdout
|
|
expect(int(out) >= 14, f'harness checks ran (serve={serve})')
|
|
|
|
|
|
def build():
|
|
sys.path.insert(0, str(ROOT / 'probes'))
|
|
sys.path.insert(0, str(ROOT.parent / 'q1n1' / 'tools'))
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location('test_wfi_resume', ROOT / 'tools/test-wfi-resume.py')
|
|
argv, sys.argv = sys.argv, [sys.argv[0]]
|
|
twr = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(twr)
|
|
sys.argv = argv
|
|
import run
|
|
sets, _ = twr.flag_sets()
|
|
saved = list(run.FLAGS)
|
|
out = Path(tempfile.mkdtemp(prefix='panic-serve-build-'))
|
|
try:
|
|
for name, flags in (('control', sets['control']), ('fast', sets['fast']),
|
|
('fast+wfi', sets['fast'] + ['-DSNAP_WFI_RESUME=1'])):
|
|
blobs = {}
|
|
for label, extra in (('default', []), ('off', ['-DSNAP_PANIC_SERVE=0']), ('on', ['-DSNAP_PANIC_SERVE=1'])):
|
|
run.FLAGS[:] = flags + extra
|
|
blobs[label] = run.build('xnuboot', out / f'{name}-{label}')
|
|
expect(run.OUT_AT - run.CODE_AT - len(blobs[label]) > 0, f'{name}-{label} fits')
|
|
expect(blobs['default'] == blobs['off'], f'{name}: default equals SNAP_PANIC_SERVE=0')
|
|
expect(blobs['default'] != blobs['on'], f'{name}: SNAP_PANIC_SERVE=1 changes the build')
|
|
print(f'{name}: default {hashlib.sha256(blobs["default"]).hexdigest()[:16]}, '
|
|
f'on {hashlib.sha256(blobs["on"]).hexdigest()[:16]}', file=sys.stderr)
|
|
finally:
|
|
run.FLAGS[:] = saved
|
|
|
|
|
|
def main():
|
|
os.chdir(ROOT)
|
|
host(0)
|
|
host(1)
|
|
if '--build' in sys.argv[1:]:
|
|
build()
|
|
print(f'OK ({checks} checks)')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|