Established facts
Facts are grouped by the kind of evidence behind them. A specification says what should be true, an independent implementation catches a wrong reading of the specification, and hardware shows what survived the platform. A fact moves to a stronger group only when that group's evidence exists.
Specified (pinned sources in lib/)
- ELF constants, dynamic tags and relocation types are read from
ELF.h,DynamicTags.def,AArch64.def,x86_64.defandARM.def(Get-ElfConstants). - Android ARM32 uses soft-float argument passing: clang's
arm::getDefaultFloatABIreturns SoftFP for the Android environment (ARM.cpp). ARM32 relocates with REL, addend in place (ARM.def). - bionic's
VerifyElfHeaderchecks magic, class, byte order,e_type,e_version,e_machine,e_shentsizeande_shstrndx, and never readse_flags(linker_phdr.cpp, android-14.0.0_r1). - CoreCLR takes a host contract from the
HOST_RUNTIME_CONTRACTproperty as a number parsed with base 0 (exports.cpp), and asks itsexternal_assembly_probeforSystem.Private.CoreLib.dllbefore the file system (assemblybindercommon.cppBindToSystem), both at the runtime commit of v11.0.0-rc.1.26425.128. The contract's layout ishost_runtime_contract.h. - A 32-bit assembly store carries no 64-bit flag in its version word
(
xamarin-app.hhlines 14-19). Store name hashes are 32-bit on every target.
Implemented (checked by the build on every run)
setup.ps1emitsET_DYNshared libraries for three targets: ELF64EM_AARCH64andEM_X86_64, and ELF32EM_ARM.$script:Targetsholds each target's machine, ELF class, RELATIVE relocation, RID and ABI.libpsl-native.sohas 21 exports for every target, reaches libc through a GOT withDT_NEEDED libc.soandDT_FLAGS BIND_NOW, and every instruction is decoded back by an independent decoder (Test-ElfCodeLibrary*).- The store library reserves eight dynamic entries on ELF64 targets and seven
on ELF32 (
New-ElfPayloadLibrary). The difference is deliberate: both sizes reproduce bytes proven on hardware. Do not make them agree as part of any other change. - The payload is the names in
lib/minimal-assembly-order.txt, pinned by digest; its current length (98) is the assembly count every step checks. libpwsh-host.so(x86-64, arm64, and arm32 in Thumb-2) starts CoreCLR:DT_NEEDEDlibc, liblog, libcoreclr and the store library; three runtime properties as the pinned .NET for Android host sets them; anexternal_assembly_probethat walks a table read back from the store; thencoreclr_create_delegateforNativeHost.Admit. Its code passes the per-ISA decoder, a control-flow ABI checker (SysV AMD64, AAPCS64 or AAPCS32) and the emitter controls in Step 6. It requiresAdmitto return 0x50575348, then callsNativeHost.RunPowerShellthrough a second delegate and logs what it returns.-TraceAssemblyProbe(x86-64, diagnostic) logs each probe request exactly as CoreCLR spells it, and whether the store has it.- The NativeActivity store starts every image on a 16-byte boundary with zero padding; the Xamarin store keeps the upstream layout byte for byte. Step 6 reads the final store library as it is mapped and requires every image to start 16-byte aligned and every fat method header to fall 4-byte aligned (131,709 fat headers in the minimal payload).
- The arm32 host is Thumb-2, as the NDK builds the pinned arm32 .NET for Android
host (its exported functions carry the Thumb bit);
libpsl-nativestays A32. Pointer-sized fields take the target's size, andinternalDataPath's offset comes fromnative_activity.h. Exported function values and the relocatedexternal_assembly_probepointer carry bit 0; the build checks both. - After the gate 2c script,
RunPowerShellruns the gate 2d path: the product's case-insensitiveProfile.ps1lookup (FindProfile, one generator for the Xamarin program type andNativeHost) overinternalDataPath, then$PSScriptRoot,GetCommandas anExternalScript,Invokein the same FullLanguage,UseCurrentThreadrunspace, and the product'sHadErrorsrule. Its catch logs the exception's type and message before returning the HResult. -Debuggable(NativeActivity, diagnostic) addsandroid:debuggable="true"(0x0101000f,public-final.xml) soadb shell run-ascan place files in the app's private files directory. Without it the manifest carries no trace ofdebuggable, which the admission check enforces.- The NativeActivity APK packages the emitted
libpsl-native.so, so CoreCLR's default native probing finds it in the APK's library directory. - The current build candidate includes and imports the Utility, Management and
Security command assemblies from the in-memory store. Its arm64 build gate
passes; command execution is not established until the x86-64, arm64 and
arm32 device results exist. Roslyn (
Microsoft.CodeAnalysis.*) remains absent, soAdd-Type -TypeDefinitionand-MemberDefinitioncannot work on device.
Independently cross-checked (implementations this repository did not write)
- The predecessor arm32 APK (built by .NET for Android and NDK clang 17)
matches the emitted ELF32 header fields, including
e_flags0x05000200, and uses the same A32 encodings forbx lrand loads intopc. Itslibpsl-native.soexported eight stubs;GetCurrentThreadIdreturned 1. System.Linqin the pinned runtime referencesSystem.Numerics.VectorsfromEnumerable.Sum,AverageandFillIncrementing(metadata read from the built store), so that assembly must ship.Read-ElfImageresolves all 36 symbols of the predecessor arm32libxamarin-app.sothrough its LLD 18 SysV hash table: 37 buckets, 25 in use, six collision chains, the longest four entries. That exercises the reader's own hash function and chain walk against an independent writer.- The x86-64 encoder's forms (34 cases) and the A64 encoder's forms (23 cases,
with branch and
adrptargets resolved) disassemble as intended in MSVCdumpbin, used as a diagnostic only. - The T32 encoder's forms (35 cases, branch targets and PC-relative sequences
included) match the bytes LLVM 23.1.1's integrated assembler emits for the same
instructions.
dumpbinno longer supports ARM32, soclangis the diagnostic oracle here, kept out of tree and never used to produce output. - Until the .NET for Android path was removed (2026-09-26),
-Debugcompared the emitted type-map name andclasses.dexagainst a .NET SDK reference build.
Proven on a device or the emulator
- The x86_64 emulator (API 36) runs CellCanvas in-process.
- The arm64 phone (Samsung Galaxy S23, API 36) and the arm32 device (API 34)
start CoreCLR, load the store and reach the host, which stops at
START_MISSINGbecause noProfile.ps1is placed. - SMA calls
libpsl-nativeduring startup; the .NET for Android host waits for a Java-side load of it, so the activity callsJavaSystem.LoadLibrary("psl-native")(x86_64 emulator). - Gate 2a, x86_64 emulator (API 36): an APK with no DEX, no
MonoRuntimeProvider, nolibmonodroidand nolibxamarin-apploggedGATE2A Admit returned 0x50575348, before and after the ABI checker was made control-flow aware. CoreCLR accepted pointers into the read-only mapped store for the assemblies this gate needs, andPwsh.dllran a method that touches no Xamarin type without resolving itsMono.Androidreference. - Gate 2a, arm64 physical device (Samsung Galaxy S23): the same APK shape,
with an independently emitted A64 host, logged
GATE2A Admit returned 0x50575348; the process stayed alive and the crash buffer held nothing for it. The Xamarin arm64 build from the same script stayed byte-identical. - Gate 2a exercised only tiny method headers and ReadyToRun CoreLib code, so
it did not show that arbitrary IL runs from the original, unaligned store.
CoreCLR decodes a fat method header only when it is 4-byte aligned on a
64-bit host (
corhlpr.cppDecoderInitat v11.0.0-rc.1.26425.128); on a 32-bit host that check is a debug assert only. The .NET for Android host never met it because it copies each assembly intomalloced memory (lib/assembly-store.cc). Served in place from the unaligned store, a fat method failed asInvalidProgramExceptionon the x86_64 emulator, while ILVerify 10.0.12 and the Windows JIT accepted the same bytes. - Gates 2b and 2c, x86_64 emulator (API 36), with the aligned store: CoreCLR
loaded System.Management.Automation and 55 other assemblies in place from
the read-only mapped store (56 of the 96, those the tested startup path
requested); the packaged
libpsl-native.sosatisfied the native library thatOpenreaches;CreateDefault2,CreateRunspacewithUseCurrentThread,Open,DefaultRunspaceand the script0x50575348all ran on the main thread, andRunPowerShellreturned 0x50575348 afterAdmitheld in the same process. The process was alive 40 seconds later and the crash buffer was empty, with and without-TraceAssemblyProbe. The Xamarin x86_64 build stayed byte-identical. - Gates 2b and 2c, arm64 physical device (Samsung Galaxy S23): the same
sequence, with the A64 host and the aligned arm64 store, logged every marker
on the main thread (thread id equal to process id) and
RunPowerShell returned 0x50575348about half a second afterAdmit; the process was alive 40 seconds later and the crash buffer held nothing for it. The Xamarin arm64 build stayed byte-identical. - Gates 2a, 2b and 2c, arm32 device (API 34): the Thumb-2 host and the aligned
arm32 store logged every marker on the main thread and
RunPowerShell returned 0x50575348; the process was alive 40 seconds later and the crash buffer held nothing for it. The Xamarin arm32 build stayed byte-identical. Gate 2c holds on all three backends. - Gate 2d, profile execution substrate: proven on the x86_64 emulator, the
Samsung Galaxy S23 (arm64) and the arm32 device, with controlled profile
fixtures placed through
run-asin a-Debuggablebuild. The owned host performs the product's case-insensitiveProfile.ps1discovery, establishes$PSScriptRoot, resolves the file as an external script, executes it in the existing FullLanguage,UseCurrentThreadrunspace, applies the existingHadErrorsrule, preserves runspace state afterward, and handles the missing-profile case: no profile loggedSTART_MISSING;PROFILE.ps1ran and left state a second pipeline read back; a divide-by-zero profile took theHadErrorspath and returned 0x80131509. Each time the process was alive 40 seconds later with an empty crash buffer.$Activity, the recovery UI and the animation callback remain gate 2e concerns. The Xamarin builds and the release NativeActivity manifest stayed byte-identical. - Gate 2e admission prerequisite, 2026-09-26:
RunPowerShell(IntPtr)receives the borrowedANativeActivity*and places it in the runspace asNativeActivityHandle. Its first build threwNullReferenceExceptionon all three backends: anIntPtrconstant became a closure-bound constant that a persisted method cannot load (fixed; the build now rejects such constants). With the fix, gates 2a-2d pass again on the x86_64 emulator, the S23 and the onn 4K Plus (arm32, API 34), with the managed host assembly namedDev.MansfieldPlumbing.Pwsh: every marker on the main thread, the process alive 40 seconds later, the crash buffer empty. - IL-only store, 2026-09-26: with all 62 ReadyToRun images re-emitted IL-only,
CoreLib included, gates 2a-2d pass on the x86_64 emulator, the S23 and the
onn 4K Plus, every marker on the main thread, alive 40 seconds later, no
crash for the process. Startup (Admit to
RunPowerShell returned) rose from 0.51-0.55 s to 0.97-1.03 s on the S23 and from 3.8-4.0 s to 6.8-7.2 s on the onn; the JIT now compiles the CoreLib andSystem.*code that ran precompiled. - Without .NET for Android, 2026-09-26: the
-Admission Xamarinpath, the DEX andlibxamarin-appsteps, type maps, the-Debugreference build,classes.dex, andMono.Android,Mono.Android.Runtime,Java.Interop, the resource designer andProbe.dllare gone; the payload is 91 assemblies and the managed host assembly isDev.MansfieldPlumbing.Pwsh. The build emits the same manifest bytes as before. Gates 2a-2d pass on the x86_64 emulator, the S23 and the onn 4K Plus with no crash for the process; the arm64 APK is 16,547,569 bytes. CellCanvas does not run until gate 2e. - Pixels from PowerShell, 2026-09-26: with no rebuild,
scripts/ScreenProbe.ps1placed asProfile.ps1in a-Debuggablebuild declares thelibandroidwindow calls and a callback delegate type withReflection.Emitat run time, writes the delegate's function pointer intoonNativeWindowCreatedandonNativeWindowRedrawNeeded(slots 7 and 9 ofANativeActivityCallbacks,native_activity.h; the table is zeroed byNativeCode's constructor, frameworks/base299fe6f5android_app_NativeActivity.cpp:121), selectsWINDOW_FORMAT_RGBA_8888(1, frameworks/nativebfcf7507), and fills four colored quadrants withANativeWindow_lockandANativeWindow_unlockAndPost.screencapread red, green, blue and white at the four quadrant centers on the x86_64 emulator (1080x2400), the S23 (2340x1080, stride 2368) and the onn 4K Plus (1920x1080; black 2 s after the marker, correct on the next capture); the process was alive 20 seconds later with no crash for it. The callbacks reach script-block delegates on the main thread; the product's[UnmanagedCallersOnly]callbacks in the compatibility assembly (Layering) are not proven by this. - JNI and Android
Canvasfrom PowerShell, 2026-09-26, no rebuild, on the x86_64 emulator, the S23 and the onn 4K Plus under CheckJNI (on in-Debuggablebuilds).scripts/probes/jni/Jni.ps1binds theJNINativeInterfaceslots, all derived fromjni.h(libnativehelperaf5fd77f, SHA-256C88CE2CB…B601A, 233 entries), as delegates onactivity->env. The JNI probe readGetVersion0x00010006,SDK_INTequal toactivity->sdkVersion(36, 36, 34),getPackageNamethroughCallObjectMethodA, andInteger.toHexStringthroughCallStaticObjectMethodAwith ajvalue[]. The Canvas probe took the window'sSurfacewithANativeWindow_toSurface, drew withlockCanvas,Paint,Typeface.MONOSPACE,drawTextanddrawRect, and posted it;screencapread the eight Campbell ANSI colors exactly across the band and text pixels in the text area on every device. Each process was alive afterwards with no crash for it.modules/AndroidCanvas.psm1packages the same mechanism as one importable file (function pointers as delegates, NDK exports, the JNI table, Canvas, window callbacks);scripts/probes/moduledrew through it with the same color result on all three devices. An exception that escapes a window callback aborts the process (seen once, x86_64 emulator), so every callback catches everything, including failures of its own error logging. - QuickPS
src/Native.ps1at62747ebfdoes not run unchanged in this payload: its firstAdd-Memberfails, because that cmdlet is inMicrosoft.PowerShell.Commands.Utility(AddMember.cs), which is not shipped;ForEach-Objectis in SMA (InternalCommands.cs) and resolves. With eachAdd-Member ScriptMethodreplaced byPSObject.Methods.Add([PSScriptMethod])and nothing else changed, itsGetComCallonJNIEnv*(a pointer to a function table, taking the env as its first argument, like a COM object) returnedGetVersion0x00010006 on all three backends, 2026-09-26. - Console core on devices, 2026-09-27, no rebuild:
modules/Console.psm1replayed all 63 conformance vectors in the app (CONSOLE PASS 63 FAIL 0) on the x86_64 emulator, the S23 and the onn 4K Plus, parsing JSON withNewtonsoft.Jsonfrom the payload. Its frame, drawn op by op throughAndroidCanvas.psm1, showed the progress row's Yellow background (F9F1A5), palette 208 (FF8700), truecolor3A96DDand empty0C0C0Cinscreencapon all three. Without an input-queue reader the S23 reported the app not responding; withonInputQueueCreatedattaching the queue to the main looper and finishing every event, a session of 12 taps and 249 redraws logged no not-responding event. The grid is inset by the system bars (WindowInsets.Type.systemBars, S23 portrait 98 px top, 45 px bottom), and pinch changes the text size and reflows the grid within the visible area: 89 grid sizes from 63x66 to 25x26 cells, no module error, process alive. - The 2026-09-27 console results used the preceding 91-image payload without cmdlet modules, so their profile fixtures use the language and .NET only. They do not prove commands in the current payload.
- During those runs SMA also asked the probe for
System.Management.Automation.dllby its full path under the app's files directory. The probe has no entry by path, so it declined; execution continued, and the assembly was already loaded by name. A probe miss is not an assembly-resolution failure. UseCurrentThreadputs the runspace and its pipelines on the Android main thread; SMA still starts threads of its own.LocalConnectionkeeps a static named-pipe listener, whose thread starts duringOpenand logs throughlibpsl-native. An exception there escapes every managed entry the host calls and aborts the process, so acceptance requires the success marker, the process alive after it, and an empty crash buffer. Withoutlibpsl-native.soin the APK, PowerShell's own native resolver (NativeDllHandler) threw on that thread, because an assembly served from memory has an emptyLocation.- The arm32 host rejected a store whose version word carried the 64-bit flag; emitter and reader had agreed on it (arm32 device).
Other repositories (their READMEs)
- RyuJitDetach lifts leaf-only RyuJIT bodies into AMD64 Windows PE files; its shim owns every call. It does not produce ARM64 or Android code.
- PSPersistence persists selected SMA expression trees as reloadable assemblies. It does not produce native code.