#!/usr/bin/env python3 """Build the pthread kernel component of the selected OS profile and its real registration.""" import json import os import shutil from pathlib import Path import subprocess import sys ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / 'scripts')) from kernel_target import TRIPLE, check_object from xnu_symbols import kernel_names SOURCE = ROOT / 'refer/libpthread' # The OS profile pins the source; without it, the Darwin 23 revision. REVISION = os.environ.get('UNIXNU_LIBPTHREAD_COMMIT', 'c0e60fb5ecfc54cf379c261d3f9572cc22ded891') if subprocess.check_output(['git', '-C', SOURCE, 'rev-parse', 'HEAD'], text=True).strip() != REVISION: raise SystemExit('Unexpected libpthread source revision') if subprocess.check_output(['git', '-C', SOURCE, 'status', '--porcelain'], text=True): raise SystemExit('The libpthread reference must remain immutable') WORK = ROOT / '.cache/libpthread-work' if WORK.exists() and subprocess.check_output(['git', '-C', WORK, 'rev-parse', 'HEAD'], text=True).strip() != REVISION: shutil.rmtree(WORK) if not WORK.exists(): subprocess.run(['git', 'clone', '--shared', SOURCE, WORK], check=True) for patch in sorted((ROOT / 'patches/libpthread').glob('*.patch')): if subprocess.run(['git', '-C', WORK, 'apply', '--reverse', '--check', patch], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode: subprocess.run(['git', '-C', WORK, 'apply', patch], check=True) SOURCE = WORK OUT = ROOT / 'build/libpthread-kernel' OUT.mkdir(parents=True, exist_ok=True) recipe = ROOT / 'build/xnu/ios-obj/DEVELOPMENT_ARM64_BCM2837/osfmk/DEVELOPMENT/prng_random.o.json' record = json.loads(recipe.read_text().strip().rstrip(',')) sdk = subprocess.check_output(['xcrun', '--sdk', 'iphoneos', '--show-sdk-path'], text=True).strip() def compile_one(source, destination, namespace=None): if source.name == 'source_pthread_bootstrap.c': command = [] arguments = record['arguments'] index = 0 while index < len(arguments): value = arguments[index] if value == record['file']: command.append(str(source)) elif value == '-o': command.extend(['-o', str(destination)]) index += 1 else: command.append(value) index += 1 else: framework = ROOT / '.cache/fakeroot/System/Library/Frameworks/Kernel.framework/Versions/A' command = ['xcrun', '--sdk', 'iphoneos', 'clang', '-target', TRIPLE, '-isysroot', sdk, '-mkernel', '-ffreestanding', '-fno-builtin', '-fno-stack-check', '-std=gnu11', '-fblocks', '-O2', '-mno-implicit-float', '-DKERNEL=1', '-DKERNEL_PRIVATE=1', '-DXNU_KERNEL_PRIVATE=1', '-DMACH_KERNEL_PRIVATE=1', '-DMACH_ASSERT=1', '-DDEBUG=1', '-D__PTHREAD_EXPOSE_INTERNALS__=1', '-DABSOLUTETIME_SCALAR_TYPE=1', '-DNEEDS_SCHED_CALL_T=1', '-I', str(SOURCE / 'private'), '-I', str(SOURCE / 'include'), '-I', str(SOURCE), '-I', str(framework / 'PrivateHeaders'), '-I', str(framework / 'Headers'), '-Wno-deprecated-declarations', '-c', str(source), '-o', str(destination)] if namespace: command += ['-include', str(namespace)] result = subprocess.run(command, cwd=record['directory'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) destination.with_suffix('.log').write_text(result.stdout) if result.returncode: raise SystemExit(result.stdout[-4500:]) check_object(destination.read_bytes(), destination.name) def names(path, mode, global_only=True): result = subprocess.check_output(['xcrun', 'nm', *(['-g'] if global_only else []), mode, '-j', path], text=True) return {line.strip() for line in result.splitlines() if line.startswith('_')} sources = [SOURCE / 'kern' / name for name in ('kern_init.c', 'kern_support.c', 'kern_synch.c')] objects = [] for source in sources: destination = OUT / (source.stem + '.o') compile_one(source, destination) objects.append(destination) kernel_names = kernel_names(ROOT, global_only=False) definitions = set().union(*(names(path, '-U') for path in objects)) collisions = definitions & kernel_names if collisions: namespace = OUT / 'namespace.h' namespace.write_text(''.join(f'#define {name[1:]} pthread519_{name[1:]}\n' for name in sorted(collisions))) for source, destination in zip(sources, objects): compile_one(source, destination, namespace) definitions = set().union(*(names(path, '-U') for path in objects)) imports = set().union(*(names(path, '-u') for path in objects)) - definitions # The component is statically linked: os_log's DSO identity is the real # executable Mach-O header, synthesized by the Mach-O executable linker. missing = sorted(imports - kernel_names - {'___dso_handle'}) report = {'revision': REVISION, 'sources': 3, 'target': TRIPLE, 'target_audited': True, 'namespace_collisions_isolated': sorted(collisions), 'missing_kernel_names': missing, 'linker_synthesized_symbols': ['___dso_handle'], 'registration_observed': False} (OUT / 'report.json').write_text(json.dumps(report, indent=2) + '\n') if not {'_pthread_kern', '_current_uthread'} <= collisions: raise SystemExit('Missing expected pthread/XNU static symbol isolation') if missing: raise SystemExit(f'Unavailable real kernel imports: {missing}') archive = OUT / 'libpthread_kernel.a' archive.unlink(missing_ok=True) compile_one(ROOT / '.cache/xnu-work/bsd/kern/source_pthread_bootstrap.c', OUT / 'source_pthread_bootstrap.o') subprocess.run(['xcrun', 'ar', 'rcs', archive, *objects, OUT / 'source_pthread_bootstrap.o'], check=True) print('Matching libpthread: 3 real C sources + bootstrap, ARM64/iOS 17 audit, no unavailable kernel names')