.NET 10 Android runtime libraries ship without .eh_frame, truncating native crash stacks to 1-2 frames
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 579
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 257
Description
### Android framework version
net10.0-android
### Affected platform version
.NET 10, Microsoft.Android.Runtime.Mono.36.android-arm64 and Microsoft.Android.Runtime.CoreCLR.36.android-arm64, both 36.1.69, android-arm64
### Description
## Summary
The .NET 10 Android runtime libraries contain **no unwind information at all** — no `.eh_frame`, no
`.eh_frame_hdr`, and no `PT_GNU_EH_FRAME` program header. The equivalent .NET 9 library has all three.
Consequently nothing can unwind out of these libraries. Google Play tombstones for a crash inside the
runtime stop at frame `#00` or `#01`, and no caller is recoverable. Uploading debug symbols does not
help, because what is missing is CFI, not symbol names.
Found whilst investigating #12696
## Evidence
```
net9 Runtime.35 / 35.0.61 PT_GNU_EH_FRAME=YES sections: .eh_frame_hdr, .eh_frame
net10 Runtime.Mono.36 / 36.1.69 PT_GNU_EH_FRAME=NO sections: NONE
net10 Runtime.CoreCLR.36 / 36.1.69 PT_GNU_EH_FRAME=NO sections: NONE
```
Verified independently from both the section header table and the program header table. `.symtab` is
still present in all three (1,430 symbols on net9, 1,594 on net10 Mono), so function names still
resolve — only the ability to walk the stack is gone.
## Impact
A real example. We have a production crash inside the runtime affecting ~1.6% of sessions. The best
of three Play tombstones has two frames; the other two have only frame `#00`. The calling function is
unrecoverable from any crash report, so the crash could not be attributed to a subsystem at all.
We eventually identified the caller by statically scanning `.text` for `BL` instructions targeting the
symbol and mapping call sites back to `.symtab`. That worked, but it should not be the supported route
for diagnosing a crash in a shipped runtime.
This affects anyone triaging a native crash in a .NET 10 Android app, and it silently degrades Play
vitals, Sentry and Crashlytics alike — none of them can do anything about it, because the information
is absent from the binary.
## Possibly related
`--gc-sections` and section-splitting work for Android native release builds landed in this window and
may be the cause, though we have not confirmed that. Whatever the cause, `.eh_frame` ought to survive
it: `--gc-sections` alone does not normally remove unwind tables, so this may be an unintended
consequence of an additional strip step, or of `-fno-asynchronous-unwind-tables`.
## Ask
Ship `.eh_frame`/`.eh_frame_hdr` in the release runtime libraries, as .NET 9 did. The size cost is
modest relative to making every native crash inside the runtime undiagnosable.
### Steps to Reproduce
No app is required. Against the installed workload packs:
readelf -lS /runtimes/android-arm64/native/libmono-android.release.so | grep -E "eh_frame|GNU_EH_FRAME"
1. Run it against Microsoft.Android.Runtime.35 / 35.0.61 — `.eh_frame`, `.eh_frame_hdr` and
`PT_GNU_EH_FRAME` are all present.
2. Run it against Microsoft.Android.Runtime.Mono.36 / 36.1.69 — all three are absent.
3. Same for Microsoft.Android.Runtime.CoreCLR.36 / 36.1.69 (`libnet-android.release.so`).
To see the consequence, take any native crash inside the runtime on a .NET 10 build and read the
tombstone in Play Console: it will stop at frame #00 or #01.
### Did you find any workaround?
None for the missing unwind tables themselves.
The only route we found to a caller was static analysis of the shipped binary: locate the faulting
symbol in `.symtab`, scan `.text` for AArch64 `BL` instructions (top 6 bits 0b100101, sign-extended
imm26 x 4) whose target is that symbol, and map each call site back to its containing function. That
recovers callers without unwinding, but only direct calls, and it cannot recover a runtime call stack.
### Relevant log output
```shell
Section and program header comparison across the three packs:
net9 Runtime.35 / 35.0.61 PT_GNU_EH_FRAME=YES unwind sections: .eh_frame_hdr, .eh_frame
net10 Runtime.Mono.36 / 36.1.69 PT_GNU_EH_FRAME=NO unwind sections: NONE
net10 Runtime.CoreCLR.36 / 36.1.69 PT_GNU_EH_FRAME=NO unwind sections: NONE
A representative truncated tombstone from Google Play (this is the FULL backtrace as reported):
pid: 0, tid: 23507 >>> com.example.app <<<
backtrace:
#00 pc 0x0000000000069c4c /apex/com.android.runtime/lib64/bionic/libc.so (__memcmp_aarch64+12)
#01 pc 0x00000000000afa54 .../split_config.arm64_v8a.apk!libmono-android.release.so
(...robin_hash<...>::find_impl<...>(...) const+208)
(BuildId: ae7ad8820dd75996ecdee692e377f8586298ceb0)
Two of the three sample reports for the same crash contain only frame #00.
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by running the provided readelf comparison against the .NET 9 and .NET 10 workload packs, focusing on libmono-android.release.so and libnet-android.release.so. Then trace the Android native release-build changes involving --gc-sections, section splitting, stripping, or unwind-table flags; done means both .NET 10 libraries contain .eh_frame, .eh_frame_hdr, and PT_GNU_EH_FRAME and native tombstones can unwind past runtime frames.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android
- Domain
- build-system, mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100