SIGSEGV in art::JNI::GetObjectClass (dangling jobject) at fleet scale — race between AOT managed-to-native wrapper's GC-safe transition and handle invalidation
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 38/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- android, csharp
- Domain
- compilers, mobile-dev
Research direction
Start by inspecting the shared native_indirect wrappers in libaot-Java.Interop.dll.so, especially the transition around mono_threads_enter_gc_safe_region_unbalanced and the GetObjectClass call. Trace the listed peer-creation callers, then use the proposed debuggable build with CheckJNI, GC stress, and UI soak to validate a candidate fix. Done means the crash is resolved or reproducibly narrowed, with fleet-scale telemetry available for staged validation.
Written by the indexing model from the issue text.
Description
Android framework version
net10.0-android
Affected platform version
.NET 10, Android runtime pack Microsoft.Android.Runtime.Mono.36.android-arm64 36.1.30 (verified by SHA-256 of libmonodroid.so in shipped AAB); crash signature observed since at least May 2026 on earlier versions too. Devices: Samsung/Pixel/others, Android 12-16, arm64-v8a.
Description
Production .NET MAUI app (net10.0-android, Mono runtime, full AOT with RunAOTCompilation=true, assembly store), ~8,000-device fleet. We collect ApplicationExitInfo records (incl. native-crash tombstones). One native crash signature dominates everything else:
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 // sometimes 0x4
Crashed thread: <main thread> // or ".NET TP Worker"
#00 libart.so +0x69ecf0 art::JNI<false>::GetObjectClass(_JNIEnv*, _jobject*)
dotnet/android#1 split_config.arm64_v8a.apk +0x59f50 // earlier app build: +0x58be0
Frequency: ~330 crash events from ~290 distinct devices per week (~4% of the fleet weekly, ~40-60/day in steady state). It occurs on both the UI thread and thread-pool threads, across unrelated screens, elevated under GC pressure (e.g. heavy allocation right after app update: DB migration + full data sync) but persistent during completely normal use.
Frame dotnet/android#1 identified — shared AOT transition wrapper, race window visible
We symbolized frame dotnet/android#1 by recompiling Java.Interop.dll with mono-aot-cross keeping symbols (the Android SDK's AOT step passes -s in ld-flags, so shipped AOT images are always stripped) and byte-matching against the stripped image from our shipped AAB (code regions are byte-identical at the same file offsets between runtime 36.1.30 and 36.1.43):
- Both offsets are inside Mono's shared managed-to-native
native_indirectsignature wrappers inlibaot-Java.Interop.dll.so(wrapper_managed_to_native ... wrapper_native_indirect_...).+0x59f50is exactly the wrapper's indirect call instruction (blr x2) for the 2-argument signature matchingGetObjectClass(env, jobject)— unwinders report caller frames as LR-4. Because the wrappers are shared per signature, every caller funnels through the same address — hence exactly one offset per app build across the whole fleet. - The race window: the wrapper calls
mono_threads_enter_gc_safe_region_unbalancedimmediately before the indirect JNI call. The rawjobjectwas extracted from theJniObjectReferencebefore entering GC-safe mode; a collection (and GC-bridge handle processing) running concurrently in that window can invalidate the handle, so ART dereferences a danglingjobject→ SIGSEGV with fault addr 0x0/0x4. - IL scan of the linked assemblies shows the managed
GetObjectClasscallers funneling into this wrapper:JniRuntime.JniValueManager.CreatePeer,AndroidTypeManager/JavaTypeManager.CreateInstance(the Java→managed peer-creation path),JniEnvironment.Types.GetJniTypeNameFromInstance,Android.Runtime.JNIEnv.GetObjectClass/InvokeConstructor/AssertCompatibleArrayTypes,JavaConvert.GetTypeMapping. - Ruled out on the native side: the exact shipped
libmonodroid.so(36.1.30) contains fiveJNIEnv->GetObjectClasscall sites (init + 4 inOSBridge), none matching the crash offsets;libmonosgen-2.0.soand the crypto PAL contain none.
Possibly related: dotnet/runtime#123495 (SIGSEGV inside sgen-tarjan-bridge.c on .NET 10 Android), dotnet/runtime#112109.
Related diagnosability gap
The crashing thread's backtrace always ends at frame dotnet/android#1 because Mono's Android AOT emits no OS-readable unwind info — no .eh_frame/PT_GNU_EH_FRAME in the AOT images, and the LLVM AOT backend likewise generates assembly without any .cfi directives (verified with mono-aot-cross ... llvm). Consequently no Mono-based Android app can symbolize past the first managed frame in ApplicationExitInfo / Play Console / Crashlytics native crash reports. If AOT could emit CFI (even optionally), issues like this one would arrive with full stacks.
We can attach full tombstones (all threads + memory maps) and the exact shipped libmonodroid.so / libaot-*.so binaries on request, and we can measure any candidate fix at fleet scale within days via a staged rollout.
Steps to Reproduce
No deterministic repro — statistical at fleet scale (hundreds of events/week, ~4% of devices weekly). Elevated under GC pressure (first launch after app update: database migration + full sync), but occurs steadily during normal use on both UI and thread-pool threads. We are setting up a local repro rig (debuggable build + CheckJNI + small nursery GC stress + UI soak) and can run experimental builds.
Did you find any workaround?
Not yet. Evaluating MONO_GC_PARAMS=bridge-implementation=old and the latest servicing runtime (36.1.69) as mitigations; happy to report fleet-scale telemetry for either.
Relevant log output
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 580
- Avg merge
- 2d 19m
- Merged PRs (30d)
- 228
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/android
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Area: Debugger enhancement needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Area: Mono.Android
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
agentic-workflows needs-triage
-
automated needs-triage skill-runner
Difficulty 5/5 Over a week Newbie friendliness 10/100
Similar issues
-
type/automation type/tech-debt
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
t/bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
ci-failure-cause test-failure
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
area:auth FE mvp P3
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
klasolsson81/jobbliggaren#1788 ·