dotnet / dotnet/android

[mono] Intermittent startup SIGSEGV in mono_metadata_decode_value during domain init on 32-bit Android

Open
#12,785 5 comments 0 reactions 0 assignees View on GitHub
Area: App Runtime
Dominant language
C#
Stars
2.1k
Forks
579
Avg merge
1d 19h
Merged PRs (30d)
252

Description

### Description

On a 32-bit Android device (`armeabi-v7a`), a `net10.0-android` MAUI app crashes with `SIGSEGV` inside `mono_metadata_decode_value` during domain init, before any managed code runs.

The unusual part, and the reason I'm filing rather than working around it: **the outcome is determined at install time and then fixed for the life of that install.** A given install either crashes on 100% of launches or on 0% of them. Roughly half of installs are "bad". Reinstalling the *byte-identical* APK re-rolls the outcome.

The faulting pointer is `0xc5944385`. It is **identical in every tombstone I have collected** — across separate installs, separate launches, and two different builds of `libmonosgen-2.0.so` (10.0.8 and 10.0.10, different BuildIds) — while the code addresses in the same tombstones (`pc`, `lr`, `ip`) shift between launches as the library load base moves. So the bad pointer does not track the library load address.

### Reproduction Steps

1. Build a `net10.0-android` MAUI app for RID `android-arm`, Debug, `EmbedAssembliesIntoApk=true`, `AndroidUseAssemblyStore=false`.
2. `adb install -r -Signed.apk`
3. Launch. Observe either a clean start or an immediate native crash.
4. Repeat step 2 with the same APK file. The outcome re-rolls, roughly 50/50.

Once an install is "bad" it stays bad; once it's "good" it stays good (see evidence below).

### Expected behavior

The app starts. Whether it starts should not depend on which install of an identical APK is on the device.

### Actual behavior

```
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc5944385 in tid 8794 (12ns.paymentapp), pid 8794
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc5944385

r0 c5944385 r1 bef4c760 r2 00000001 r3 7e696a06
r4 174f3380 r5 96e6210c r6 96e61f90 r7 bef4c780
r8 00000058 r9 bef4c760 r10 96e6228c r11 96e61e10
ip 7e85c934 sp bef4c6e0 lr 7e79d3cb pc 7e7dac98

backtrace:
#00 pc 0019ac98 libmonosgen-2.0.so (mono_metadata_decode_value)
dotnet/runtime#1 pc 0015cb95 libmonosgen-2.0.so
dotnet/runtime#2 pc 0007c94b libmonosgen-2.0.so
dotnet/runtime#3 pc 000bc825 libmonosgen-2.0.so (mono_jit_init_version+6)
dotnet/runtime#4 pc 0005dcdd libmonodroid.so (xamarin::android::internal::MonodroidRuntime::create_domain(_JNIEnv*, jstring_array_wrapper&, bool, bool)+132)
dotnet/runtime#5 pc 0006125d libmonodroid.so (MonodroidRuntime::create_and_initialize_domain(...)+36)
dotnet/runtime#6 pc 0006b8bb libmonodroid.so (MonodroidRuntime::Java_mono_android_Runtime_initInternal(...)+3694)
dotnet/runtime#7 pc 00062299 libmonodroid.so (Java_mono_android_Runtime_initInternal+52)
dotnet/runtime#8 pc 000dc519 libart.so (art_quick_generic_jni_trampoline+40)
```

`r0` holds the faulting address, and `0xc5944385` is unmapped on 32-bit Android. Frames dotnet/runtime#1 (`+0x15cb95`) and dotnet/runtime#2 (`+0x7c94b`) are unsymbolized in my tombstones — I can't resolve them without symbols for the shipped pack, and they're likely the most informative frames here.

### Evidence that this is not a corrupted or mis-packaged APK

I initially assumed a bad install/packaging. Direct testing ruled that out:

- **APK bytes are identical.** The on-device `base.apk` sha256 matches the source APK on both good and bad installs. Good and bad installs run identical bytes.
- **Locked per-install, not per-launch.** One bad install crashed **29/29** launches; a good install ran **12/12**. At 0.5^29 this is not a per-launch coin flip.
- **Survives everything short of reinstall**: `pm clear` (8×), `cmd package compile -f` with every dex2oat filter, force-stop. Only `adb install -r` re-rolls the outcome.

### Evidence that it isn't a packaging or runtime-version knob

Bad-install rates, each measured over 14–16 installs (all ≈50% within sampling noise):

| Configuration | Bad installs |
|---|---|
| Default (`AndroidUseAssemblyStore=false`) | 7 / 15 |
| `AndroidUseAssemblyStore=true` | 7 / 14 |
| `AndroidExtractNativeLibs=true` | 5 / 16 |
| `AndroidEnableMarshalMethods=false` | 11 / 16 |
| Mono runtime pack **10.0.10** instead of 10.0.8 | 9 / 15 |

The 10.0.8 → 10.0.10 comparison was run in one sitting on the same device with an identical protocol (`install -r` → launch → check `pidof` after 6s). I verified the swap took effect by sha256'ing `lib/armeabi-v7a/libmonosgen-2.0.so` out of the APK against the runtime pack, and by the BuildId changing in the tombstone (`607030…` → `610a9c…`). The fault address stayed `0xc5944385`.

### The detail I think matters most

Between two crashing launches of the same install:

- `pc`, `lr`, `ip`, `r3` **differ** (`pc 7e7dac98` vs `7e7e7c98`) — the library load base moved.
- `r0` (the faulting pointer) is **identical**: `c5944385`.
- `r4`–`r11` are also identical, including what look like addresses into the mmap'd assembly data (`96e6xxxx`).

So the bad pointer is invariant of code layout, and invariant across two different mono builds. That reads to me like a value decoded from data — a length or offset read out of the assembly metadata blob and used as a pointer — rather than anything derived from where the library landed. I'd guess an out-of-bounds or unvalidated read while decoding metadata during domain init, where whether it faults depends on what happens to be mapped nearby, which in turn is seeded per-install on this device.

I want to be clear that the last paragraph is inference from register state, not something I've confirmed in the runtime source.

### Regression?

_No response_

### Known Workarounds

_No response_

### Configuration

| | |
|---|---|
| .NET SDK | 10.0.300 |
| Mono runtime pack | `Microsoft.NETCore.App.Runtime.Mono.android-arm` 10.0.8 (also reproduced on 10.0.10) |
| Android workload | 36.1.43 |
| MAUI | `Microsoft.Maui.Controls` 10.0.90 |
| TFM / RID | `net10.0-android` / `android-arm` |
| `SupportedOSPlatformVersion` | 29.0 |
| Configuration | Debug, `EmbedAssembliesIntoApk=true`, `AndroidUseAssemblyStore=false` |

**Device**

| | |
|---|---|
| Model | POS terminal, Qualcomm |
| Android | 10 (API 29) |
| ABI list | `armeabi-v7a,armeabi` — **32-bit only, no arm64** |
| Fingerprint | `:10/QKQ1.200407.002/eng.builde.20260128.201144:user/release-keys` |

### Other information

- Reproduces in two separate apps in the same solution (a MAUI client app and a MAUI background service app), so it isn't specific to one app's assembly set.
- The device is 32-bit only, so `android-arm64` — which I understand gets a more robust mono configuration — isn't available as a workaround here.
- I have only this device model to test on, so I can't say whether this is DX8000-specific or general to `armeabi-v7a`. The build fingerprint shows an `eng`-built image shipped as `user`, which may mean weaker ASLR than a normal retail device and may be why the per-install stickiness is so pronounced.
- Happy to collect more data on request — I have a scripted install/launch/check harness, and I can run any number of installs with whatever runtime flags or `MONO_LOG_LEVEL` settings are useful. If someone can point me at a build of `libmonosgen-2.0.so` with symbols, I can resolve frames dotnet/runtime#1 and dotnet/runtime#2.

Best guess at the right area label: `area-VM-mono`.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the reported entry points mono_metadata_decode_value, mono_jit_init_version, and MonodroidRuntime::create_domain, then reproduce using the scripted install/launch harness on the 32-bit Android device. Resolve the unsymbolized runtime frames and identify the cause of the install-dependent SIGSEGV. Done means the crash is fixed and repeated installs of the identical APK start reliably.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, csharp
Domain
mobile-dev, operating-systems
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.