[Feature] dotnet-symbol: symbolicate managed Android/iOS crash traces (IL offsets -> file:line), like ndk-stack
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- android, csharp, ios
- 領域
- cli, mobile-dev, tooling
調査の方向性
まず既存の dotnet-symbol CLI とその Microsoft.SymbolStore のダウンロードパスを調査し、次にここで説明されている System.Reflection.Metadata の解決アルゴリズムを確認します。Maui.Diagnostics.Playground のクラッシュトレースを fixtures として使用し、Android/Apple の解析、PDB の照合、IL オフセットからシーケンスポイントへのマッピングをカバーします。完了とは、推測や不一致なしにトレースをシンボル化できることです。
索引モデルが issue の本文から書いたものです。
説明
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.
- 主要言語
- C++
- スター
- 1.3k
- フォーク
- 407
- 平均マージ
- 21時間 5分
- マージ済み PR(30日)
- 78
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
dotnet/diagnostics のほかの issue
-
Fix StackAndOtherTests.script line number verification for .NET 11 and Windows x64再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
dotnet/diagnostics#5884 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 25/100
dotnet/diagnostics#6225 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
dotnet/diagnostics#6213 ·
メンテナーはふだん 1 日以内に返信
-
Isolate SOS hosting from other debugger extensions that use managed code対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
dotnet/diagnostics#6207 ·
メンテナーはふだん 1 日以内に返信
-
Known Build Error
難易度 4/5 3〜5日 初心者へのやさしさ 49/100
dotnet/diagnostics#6190 ·
メンテナーはふだん 1 日以内に返信
dotnet/diagnostics の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
AutoHotkey/AutoHotkey#380 ·
-
documentation
難易度 1/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 69/100
grumpycoders/pcsx-redux#2667 ·
-
GCC 15.2: -Wcast-qual warning - cast discards const qualifier from pointer target type (H5trace.c)オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
google/tensorstore#305 ·