Files
UniXNU/scripts/build-virtio-mmio-bindings.py

95 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Bind the concrete block driver to the qualified original ordinary iOS class."""
import argparse
import hashlib
import json
from pathlib import Path
import re
import struct
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'kits/XnuPrelink'))
import native_prelink as np
sys.path.insert(0, str(Path(__file__).resolve().parent))
from kernel_target import TRIPLE
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--component', choices=['VirtioMmioBlock', 'RamBlock', 'NativeVirtioBlock', 'NativeRamBlock'], default='VirtioMmioBlock')
parser.add_argument('--directory', type=Path, default=ROOT / 'build/virtio-mmio-block')
args = parser.parse_args()
out = args.directory.resolve()
audit = json.loads((ROOT / 'build/storage-abi/interface-audit.json').read_text())
canonical = {'NativeVirtioBlock': 'VirtioMmioBlock', 'NativeRamBlock': 'RamBlock'}.get(args.component, args.component)
component = {'VirtioMmioBlock': 'NativeVirtioBlock', 'RamBlock': 'NativeRamBlock'}[canonical]
mangled = str(len(component)) + component
obj_path = out / (component + '.o')
obj = obj_path.read_bytes()
image = np.macho(obj)
if not audit['live_native_4k'] or audit['method_slots'] != 190 or not audit['abstract_complete_destructors_trap']:
raise SystemExit('First qualify original live storage metadata and its real base functions')
offset = next(at for cmd, at, size in image['commands'] if cmd == 2)
sy, count, st, _ = struct.unpack_from('<4I', obj, offset + 8)
symbols = []
for i in range(count):
ix, kind, _, _, value = struct.unpack_from('<IBBHQ', obj, sy + i * 16)
symbols.append((obj[st + ix:obj.index(b'\0', st + ix)].decode(), value, kind))
vtable = next(v for name, v, kind in symbols if name == '__ZTV' + mangled)
relocations = {}
for segment in image['segments']:
for section in segment['sections']:
if section['va'] <= vtable < section['va'] + section['size']:
ro, rc = struct.unpack_from('<II', obj, section['command_offset'] + 56)
for i in range(rc):
address, word = struct.unpack_from('<II', obj, ro + i * 8)
if word >> 27 & 1 and word >> 28 == 0 and word >> 25 & 3 == 3:
relocations[section['va'] + address] = symbols[word & 0xffffff][0]
compiled = [relocations.get(vtable + 16 + i * 8) for i in range(190)]
if not all(compiled) or vtable + 16 + 190 * 8 in relocations or '___cxa_pure_virtual' in compiled:
raise SystemExit('Concrete block class has missing/additional/pure virtual methods')
all_names = list(dict.fromkeys(compiled + [r['reference_name'] for r in audit['methods']]))
demangled = subprocess.check_output(['xcrun', 'c++filt'], input='\n'.join(n[1:] for n in all_names) + '\n', text=True).splitlines()
readable = dict(zip(all_names, demangled))
overrides = 0
for row, actual in zip(audit['methods'], compiled):
expected = row['reference_name']
own = actual.startswith(('__ZN' + mangled, '__ZNK' + mangled))
if expected == '___cxa_pure_virtual':
if not own:
raise SystemExit('A required transport operation is not implemented')
elif actual != expected:
if not own or (row['slot'] not in (0, 1) and readable[actual].split('::', 1)[1] != readable[expected].split('::', 1)[1]):
raise SystemExit('Concrete subclass dispatch mismatch at slot ' + str(row['slot']))
overrides += own
kernel_path = ROOT / 'build/xnu-prelink/native-apfs-load-positive.macho'
# The binding values come from the recorded audit; the candidate only proves the audit's identity. Build products are pruned, so a
# missing candidate is accepted (the audit JSON is the pinned record) while a present one must still match.
if kernel_path.exists() and hashlib.sha256(kernel_path.read_bytes()).hexdigest() != audit['candidate_sha256']:
raise SystemExit('Original binding input identity changed')
possible = {r['reference_name']: int(r['candidate_target'], 16) for r in audit['methods'] if not r['kernel_names']}
possible.update({'__ZN20IOBlockStorageDeviceC2EPK11OSMetaClass': int(audit['original_protected_constructor'], 16),
'__ZN20IOBlockStorageDeviceD2Ev': int(audit['original_base_destructor'], 16),
'__ZN20IOBlockStorageDevice10gMetaClassE': int(audit['original_metaclass'], 16),
'__ZTV20IOBlockStorageDevice': int(audit['vtable'], 16)})
undefined = [name for name, value, kind in symbols if kind == 1 and ('IOBlockStorageDevice' in name)]
if any(name not in possible for name in undefined):
raise SystemExit('Unqualified original storage import')
# Abstract D1/D0 are BRK traps. Only actual base D2 is appropriate for this subclass.
if any(re.search(r'IOBlockStorageDeviceD[01]Ev$', n) for n in undefined):
raise SystemExit('Concrete driver must not invoke abstract complete/deleting destruction')
bindings = {name: possible[name] for name in undefined}
assembly = out / 'original-bindings.S'
assembly.write_text(''.join(f'.globl {name}\n.set {name}, {value:#x}\n' for name, value in sorted(bindings.items())))
subprocess.run(['xcrun', 'clang', '-target', TRIPLE, '-Werror', '-Wno-incompatible-sysroot',
'-c', assembly, '-o', out / 'original-bindings.o'], check=True)
archive = out / ('lib' + canonical + '.a')
archive.unlink(missing_ok=True)
subprocess.run(['xcrun', 'ar', 'rcs', archive, obj_path, out / 'original-bindings.o'], check=True)
(out / 'bindings.json').write_text(json.dumps(dict(original_candidate_sha256=audit['candidate_sha256'],
compiled_object_sha256=hashlib.sha256(obj).hexdigest(), virtual_methods_checked=190,
concrete_overrides=overrides, unresolved_original_imports=0,
bindings={name: hex(value) for name, value in bindings.items()}, original_code_modified=False,
actual_io_qualified=False), indent=2) + '\n')
print(f'PASS: 190 concrete slots, {overrides} overrides and {len(bindings)} genuine original storage imports')
print('Actual device I/O and filesystem integration remain unqualified by this compile/ABI audit.')