Files
Snapintosh/tools/test-link.py
T
Acelogic 1510d8c6ce Snapintosh public release
An EL2 monitor/hypervisor and toolchain that boots macOS 27 on the ASUS A16
(Snapdragon X2 Elite), with an Adreno Metal plug-in and IOGPU kext, and
air2ir3, an AIR to Mesa ir3 shader compiler.

MIT, with GPL-2.0 components listed in LICENSE and THIRD_PARTY_NOTICES.md.
No Apple, Qualcomm, ASUS or Microsoft binaries are included.
2026-10-21 21:26:36 -04:00

83 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Every probe still builds and links for the target.
This exists because of a failure the other suites cannot see. `test-mmu`,
`test-shadow`, `test-classify` and `test-profile` all run on the host, where
there is a libc; the probe runs at EL2 on bare metal, where there is not. A
change can pass all four and still produce a probe that does not link.
The one that happened: clang lowers a struct assignment into a call to memcpy,
and `-ffreestanding` does not stop it -- that flag promises the compiler will
not assume the library's *semantics*, not that it will stop emitting calls into
it. The source said `c->first_exc = regs;` and the linker asked for a symbol the
source never mentions:
ld.lld: error: undefined symbol: memcpy
>>> referenced by xnuboot.c
Compiling is not enough to catch that, because the reference only becomes an
error at link time. So this links, and then checks the result has no undefined
symbols at all -- a blob that is about to be branched to on hardware cannot have
any.
python3 tools/test-link.py
"""
import importlib.util
import pathlib
import subprocess
import sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
spec = importlib.util.spec_from_file_location('run', ROOT / 'probes' / 'run.py')
run = importlib.util.module_from_spec(spec)
spec.loader.exec_module(run)
# Every probe run.py knows how to build. The multi-translation-unit ones are the
# ones at risk, but a single-file probe costs nothing to check.
PROBES = ['idregs', 'tgran16', 'tid3', 'shadowmmu', 'xnuboot']
checks = 0
out = ROOT / 'build'
for name in PROBES:
try:
blob = run.build(name, out)
except subprocess.CalledProcessError as error:
print(f'FAIL: {name} did not build\n {error}')
sys.exit(1)
if not blob or len(blob) > run.OUT_AT - run.CODE_AT:
print(f'FAIL: {name} built outside its reserved code size')
sys.exit(1)
checks += 1
# A flat blob is branched to at offset 0 with no loader and no relocation,
# so an unresolved symbol is not a warning, it is a jump into nothing.
elf = out / f'{name}.elf'
if not elf.exists():
continue # single-file probes go straight to .bin
nm = subprocess.run([str(run.LLVM / 'llvm-nm'), '--undefined-only', str(elf)],
capture_output=True, text=True)
undefined = [line for line in nm.stdout.splitlines() if line.strip()]
if undefined:
print(f'FAIL: {name}.elf has undefined symbols, which means the blob '
f'calls something that is not there:')
for line in undefined:
print(f' {line}')
print(' A name the source never mentions is compiler-generated -- '
'clang lowering a\n struct assignment or bulk copy into memcpy. '
'Write it as an explicit loop\n over words instead.')
sys.exit(1)
checks += 1
for blob in (b'', bytes(run.OUT_AT-run.CODE_AT+1)):
try:
run.run(object(), blob, 'xnuboot', 'must not touch hardware')
except ValueError:
checks += 1
else:
raise AssertionError('invalid code size was admitted')
print(f'PASS: {checks} probe build/link checks '
f'({len(PROBES)} probes, no hardware)')