[Feature] dotnet-symbol: symbolicate managed Android/iOS crash traces (IL offsets -> file:line), like ndk-stack
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Tranquilla
- Stack tecnologico
- android, csharp, ios
- Ambito
- cli, mobile-dev, tooling
Direzione di ricerca
Inizia esaminando la dotnet-symbol CLI esistente e il relativo percorso di download di Microsoft.SymbolStore, quindi esamina l’algoritmo di risoluzione di System.Reflection.Metadata descritto qui. Usa le tracce di crash di Maui.Diagnostics.Playground come fixtures e copri il parsing di Android/Apple, la corrispondenza dei PDB e la mappatura da offset IL a sequence point; il lavoro è completo quando una traccia viene simbolicata senza supposizioni o mancati abbinamenti.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
Add a "symbolicate a stack trace" mode to dotnet-symbol (or a sibling tool) that takes a managed mobile crash trace whose frames carry only Type.Method + 0x<ILoffset>(Unknown Source) and rewrites them with real in File.cs:line N locations.
Think of it as ndk-stack for managed .NET (Android AndroidRuntime/JavaProxyThrowable traces today, Apple .ips/NSException traces too). The user points the tool at:
- a folder containing their own
*.dll+*.pdb(their app/library code), and - lets the tool auto-download any missing .NET / Microsoft symbols (exactly what
dotnet-symbolalready does for dumps/modules),
and it prints the symbolicated trace.
This is essentially mono-symbolicate reimagined for CoreCLR, with automatic framework-symbol download.
Motivation
On .NET-for-Android (both Mono and the new CoreCLR-on-Android), an unhandled managed exception that crosses into Java is logged by AndroidRuntime with frames like:
at Maui.Diagnostics.Playground.Features.Scenarios.ManagedCrashScenarioRunner.RunAsync + 0x2db(Unknown Source)
The + 0x2db is the method-relative IL offset (hex). (Unknown Source) means no PDB was consulted to translate it. In default Release builds (and many Debug configs), no managed file:line is emitted, so production crash logs are very hard to read - but the information needed to resolve them is fully present in the assembly + portable PDB.
I verified this end-to-end by hand (details below): given the exact shipped assembly and its portable PDB, each + 0xNNN resolves deterministically to a sequence point -> file:line. A tool that automates this - and downloads framework PDBs on demand - would make managed mobile crash logs as usable as native ones are with ndk-stack.
Worked examples (how the offsets map)
All from a real .NET MAUI app crash on a physical device. The crash is a simulated unhandled InvalidOperationException on the UI thread. Repro app (usable as a test corpus): https://github.com/Redth/Maui.Diagnostics.Playground
Example 1 - Release, CoreCLR, android-arm64
Crash frames:
at ...ManagedCrashScenarioRunner.ThrowUnhandledOnCurrentThread + 0xa(Unknown Source)
at ...ManagedCrashScenarioRunner.RunAsync + 0x2db(Unknown Source)
at ...ScenarioDetailPage+<RunScenarioAsync>d__31.MoveNext + 0xb3(Unknown Source)
Decompiling the shipped assembly (obj/Release/net*-android/android-arm64/linked/MyApp.dll) with ilspycmd -il:
RunAsyncIL code size =941(0x3ad), so offset0x2dbis in range. The instruction at exactlyIL_02dbis:
-> maps to theIL_02d6: br IL_0390 IL_02db: call Task ManagedCrashScenarioRunner::ThrowUnhandledOnCurrentThread() <-- + 0x2db IL_02e0: stloc.0switch-arm"managed-ui-unhandled" => ThrowUnhandledOnCurrentThread()atManagedCrashScenarioRunner.cs:line 38.ThrowUnhandledOnCurrentThread + 0xa-> that method isldstr ... / newobj InvalidOperationException::.ctor / throw, so0xais thethrowatManagedCrashScenarioRunner.cs:line 63.
Example 2 - same crash, Debug build
Same logical crash, but the offset differs because Debug IL is unoptimized:
at ...ManagedCrashScenarioRunner.RunAsync + 0x30b(Unknown Source)
RunAsyncDebug IL code size =997(0x3e5); instruction atIL_030bis againcall ThrowUnhandledOnCurrentThread()(Debug keepsnops and a string-equality chain, so the same call sits at a higher offset).
Key takeaway for the tool: IL offsets are build-specific. The same crash is +0x30b (Debug) vs +0x2db (Release), and the Release frame only resolves against the trimmed/linked assembly that actually shipped (the android-arm64/linked copy), not the pre-link compile output. So matching must be against the exact build, validated by PDB id / MVID.
Notes that fell out of the investigation
DebugType=embeddedmakes CoreCLR-on-Android emit managedfile:linefor the app's own frames at runtime (PDB embedded in the assembly), but framework frames still lack lines. Offline symbolication is the general solution and covers framework frames too once their PDBs are downloaded.- Even R2R Release frames report the managed IL offset in the Java-side trace (not a native PC), so IL-offset symbolication is the right primitive across configs.
Proposed CLI
dotnet-symbol --symbolicate <trace.txt>
[--assemblies <dir>] # folder(s) with your shipped *.dll (+ sidecar/embedded *.pdb)
[--symbols <dir>...] # extra local symbol dirs
[--no-download] # offline / my-code only
[--strict] # fail (don't guess) on PDB-id mismatch or ambiguous overloads
[--output <file>] [--format text|json]
Pipe-friendly, like ndk-stack:
adb logcat -d | dotnet-symbol --symbolicate - --assemblies ./bin/Release/net*-android
"Point it at your build output" (no APK parsing required for v1)
APK/AAB extraction would be ideal (binds to exactly what shipped), but it is not required to be useful. For a first version, let users point --assemblies at their bin//obj/ build output directory and have the tool recurse to find the matching assembly + PDB. For trimmed Release apps the relevant copy lives under obj/<Config>/<tfm>/<rid>/linked/; the tool can prefer that, and use the PDB-id/MVID check to confirm it picked the right one (and warn loudly if only a mismatched/pre-trim assembly is available). APK/AAB and Apple .app/.xcarchive extraction can be a later enhancement.
Resolution algorithm (per managed frame)
- Parse
Type.Method + 0x<hex>; handle nested types (Outer+<Async>d__31), compiler-generated names (<>c.<X>b__N_M), and pass through pure-Java/native frames unchanged. - Find the defining assembly - the trace does not include the assembly name, MethodDef token, or MVID, so build a
fullTypeName -> (assembly, TypeDefinitionHandle)index over the provided dirs (+ runtime/framework packs for BCL types). - Find the method - match by simple name; disambiguate overloads using the IL offset itself (a candidate is only valid if
offset < method IL sizeand lands at/after a real sequence point). - Map offset -> line via the portable PDB:
MethodDebugInformation-> largest sequence point with IL offset<= NNNthat isn'thidden (0xFEEFEE)->{document, startLine}. - Validate the PDB matches the PE (PDB id / debug-directory GUID+age) and warn / fail-in-
--stricton mismatch.
Most of this is System.Reflection.Metadata (MetadataReader, MethodDebugInformation, SequencePointCollection), and the download half is the existing dotnet-symbol / Microsoft.SymbolStore path.
iOS / Apple
The same approach applies to Apple platforms: managed frames in NSException/.ips crash reports carry the managed IL offset and resolve the same way against the app assemblies + portable PDBs. The only platform-specific work is the front-end parser for Apple crash-report format vs Android AndroidRuntime format; the metadata/PDB core is shared.
Hard cases / mitigations
| Problem | Mitigation |
|---|---|
| No signature/token in trace -> overload ambiguity | Disambiguate by IL-range + sequence-point validity; expose confidence; --strict refuses to guess |
| Wrong build supplied -> silent mis-resolution | Validate PDB-id/MVID against the PE; prefer the trimmed/linked assembly |
| Trimming rewrites IL & MVID | Use the post-trim (linked) assembly + its PDB |
| Inlining (Release/R2R) drops callee frames | Inherent; optionally annotate using PDB inline data if present |
| Embedded PDBs | Detect EmbeddedPortablePdb debug-dir entry; read in place, skip download |
Prior art
ndk-stack(Android NDK) - the UX model.mono-symbolicate- same idea, Mono/.msym-only, no auto-download.- Sentry's managed-frame symbolication from
{MVID, MethodDef token, IL offset}- the robust server-side variant; this tool would do the same lookup but reconstruct the token from(type, method, IL range)since the device trace omits it.
Test corpus
https://github.com/Redth/Maui.Diagnostics.Playground contains a gallery of managed/native crash scenarios and captured logcat traces (Mono/CoreCLR x Debug/Release) that can serve as fixtures for this feature.
- Lingua principale
- C++
- Stelle
- 1.3k
- Fork
- 407
- Merge medio
- 21h 5m
- PR unite (30g)
- 78
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di dotnet/diagnostics
-
Fix StackAndOtherTests.script line number verification for .NET 11 and Windows x64Forse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
dotnet/diagnostics#5884 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 25/100
dotnet/diagnostics#6225 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
dotnet/diagnostics#6213 ·
I maintainer di solito rispondono entro 1 giorno
-
Isolate SOS hosting from other debugger extensions that use managed codeForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertabug
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
dotnet/diagnostics#6207 ·
I maintainer di solito rispondono entro 1 giorno
-
Known Build Error
Difficoltà 4/5 3-5 giorni Idoneità per principianti 49/100
dotnet/diagnostics#6190 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di dotnet/diagnostics
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
AutoHotkey/AutoHotkey#380 ·
-
documentation
Difficoltà 1/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
Lua File: add readWhole()Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 69/100
grumpycoders/pcsx-redux#2667 ·
-
GCC 15.2: -Wcast-qual warning - cast discards const qualifier from pointer target type (H5trace.c)Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 73/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
google/tensorstore#305 ·