android-tombstone-symbolication について
.NET Android アプリのネイティブクラッシュのバックトレースを、関数名やソース位置に解決するスキルです。シンボルの取得手順と、解析・シンボリケーションを自動化する PowerShell スクリプトを備えています。
主な機能
- 01tombstone と logcat 出力の解析
- 02.NET ランタイムフレームと BuildId の特定
- 03ランタイムデバッグシンボルの取得
- 04llvm-symbolizer によるフレーム解決
- 05BuildId によるランタイムバージョン特定
活用シーン
- 01MAUI または Xamarin アプリのネイティブクラッシュ調査
- 02Mono または CoreCLR フレームのソース位置特定
- 03付属の PowerShell スクリプトによる tombstone 処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill android-tombstone-symbolicationインストール後、Claude Code で/android-tombstone-symbolicationを入力して読み込まれたか確認します。
同梱ファイルscripts/Symbolicate-Tombstone.ps1
詳細
Android Tombstone .NET シンボリケーション
ELF BuildId と Microsoft のシンボルサーバーを使用して、.NET Android アプリ(MAUI、Xamarin、Mono)のクラッシュ時に出力されるネイティブバックトレースのフレームを、関数名、ソースファイル、行番号に解決します。
入力: Tombstone ファイルまたは logcat のクラッシュ出力、llvm-symbolizer(Android NDK または LLVM 14 以降の任意のツールチェーンに含まれています)、シンボルのダウンロードに必要なインターネット接続。
使用しないケース: クラッシュがマネージド .NET 例外の場合(マネージドスタックトレースとともに logcat に表示されます)、クラッシュしたライブラリが .NET コンポーネントではない場合(例:libart.so)、または Tombstone が iOS のものである場合。
ワークフロー
ステップ 1:Tombstone のバックトレースを解析する
各バックトレースフレームの形式は次のとおりです。
#NN pc OFFSET /path/to/library.so (optional_symbol+0xNN) (BuildId: HEXSTRING)
フレーム番号、PC オフセット(16 進数、すでにライブラリ相対)、ライブラリ名、BuildId(16 進数で 32~40 文字)を抽出します。
デフォルトでは、すべてのスレッドをシンボリケーションします(GC/ファイナライザーなどのバックグラウンドスレッドに、有用な .NET フレームが含まれていることがよくあります)。クラッシュしたスレッドのバックトレースが最初に記載され、追加のスレッドは --- --- --- マーカーの後に続きます。
形式に関する注意:
- スクリプトは、
backtrace:ヘッダーの有無にかかわらず#NN pcフレーム行を自動検出し、logcat のタイムスタンプ/タグのプレフィックスも自動的に削除します。 - logcat で取得した Tombstone では、BuildId が省略されていることがよくあります。
adb shell readelf -n、CI のビルド成果物、または .NET ランタイムの NuGet パッケージから取得してください。 - GitHub の Issue に貼り付けられた内容では、
#1 pcが Issue へのリンクに変換されることがあります。ファイルに保存する前に、org/repo#N pcを#N pcに置き換えてください。 - スクリプトで形式を解析できない場合は、
#NN pc OFFSET library.so (BuildId: HEX)の組を手動で抽出してください。
ステップ 2:.NET ランタイムライブラリを特定する
フレームを .NET ランタイムライブラリで絞り込みます。
| ライブラリ | ランタイム |
|---|---|
libmonosgen-2.0.so | Mono(MAUI、Xamarin、インタープリター) |
libcoreclr.so | CoreCLR(JIT モード) |
libSystem.*.so | .NET BCL のネイティブコンポーネント(Native、Globalization.Native、IO.Compression.Native、Security.Cryptography.Native.OpenSsl、Net.Security.Native) |
NativeAOT: libcoreclr.so と libmonosgen-2.0.so はありません。ランタイムはアプリのバイナリ(例:libMyApp.so)に静的にリンクされています。libSystem.*.so の BCL ライブラリは分離されたままなので、シンボルサーバーを通じてシンボリケーションできます。アプリのバイナリ自体には、アプリ独自のデバッグシンボルが必要です。
ユーザーから特に依頼がない限り、libc.so、libart.so、その他の Android システムライブラリはスキップします。
ステップ 3:デバッグシンボルをダウンロードする
.NET の一意な BuildId ごとに、デバッグシンボルをダウンロードします。
https://msdl.microsoft.com/download/symbols/_.debug/elf-buildid-sym-<BUILDID>/_.debug
curl -sL "https://msdl.microsoft.com/download/symbols/_.debug/elf-buildid-sym-1eb39fc72918c7c6c0c610b79eb3d3d47b2f81be/_.debug" \
-o libmonosgen-2.0.so.debug
file libmonosgen-2.0.so.debug で確認します。ELF 64-bit ... with debug_info, not stripped と表示されるはずです。ダウンロード結果が 404 または HTML の場合、そのビルドのシンボルは公開されていません。ライブラリのベースアドレスを加算または減算しないでください。Tombstone のオフセットはすでにライブラリ相対です。
ステップ 4:各フレームをシンボリケーションする
llvm-symbolizer --obj=libmonosgen-2.0.so.debug -f -C 0x222098
出力:
ves_icall_System_Environment_FailFast
/__w/1/s/src/runtime/src/mono/mono/metadata/icall.c:6244
/__w/1/s/ プレフィックスは CI のワークスペースルートです。意味のあるパスは src/runtime/ から始まり、dotnet/dotnet VMR に対応します。
ステップ 5:シンボリケーションしたバックトレースを提示する
元のフレーム番号と、解決された関数名およびソース位置を組み合わせます。
#00 libc.so abort+164
#01 libmonosgen-2.0.so ves_icall_System_Environment_FailFast (mono/metadata/icall.c:6244)
#02 libmonosgen-2.0.so do_icall (mono/mini/interp.c:2457)
#03 libmonosgen-2.0.so mono_interp_exec_method (mono/mini/interp.c)
未解決のフレーム(??)については、BuildId と PC オフセットを含む元の行を保持します。
自動化スクリプト
scripts/Symbolicate-Tombstone.ps1 は、ワークフロー全体を自動化します。
pwsh scripts/Symbolicate-Tombstone.ps1 -TombstoneFile tombstone_01.txt -LlvmSymbolizer llvm-symbolizer
フラグ:-CrashingThreadOnly(クラッシュしたスレッドのみに制限)、-OutputFile path(ファイルに出力)、-ParseOnly(ダウンロードせず、ライブラリ/BuildId/URL を報告)、-SkipVersionLookup(ランタイムのバージョン識別をスキップ)。
llvm-symbolizer の検索
まず Android NDK を確認してください。$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/*/bin/llvm-symbolizer または $ANDROID_HOME/ndk/*/toolchains/llvm/prebuilt/*/bin/llvm-symbolizer にあります。また、macOS では brew install llvm、apt install llvm、または xcrun --find llvm-symbolizer でも利用できます。
見つからない場合は、手順 1~3 を完了し、ユーザーが実行するダウンロードコマンドと llvm-symbolizer コマンドを提示してください。LLVM のインストールに時間をかけないでください。
出力の理解
CI のソースパスには、次のプレフィックスが使われます。
| パスのプレフィックス | 対応先 |
|---|---|
/__w/1/s/src/runtime/ | dotnet/dotnet VMR の src/runtime/ |
/__w/1/s/src/mono/ | VMR の src/mono/(古いビルド) |
/__w/1/s/ | VMR のルート |
ランタイムバージョンの特定
このスクリプトは、BuildId をローカルにインストールされたランタイムパックと照合し、正確な .NET ランタイムバージョンを特定します。検索先は SDK パック($DOTNET_ROOT/packs/)、NuGet キャッシュ(~/.nuget/packages/)、オンラインでのフォールバックとして NuGet.org です。見つかった場合は、.nuspec の <repository commit="..." /> 要素からバージョンとソースコミットを抽出します。無効にするには -SkipVersionLookup を指定してください。llvm-readelf が必要です(NDK から自動検出されます)。
検証
file <debug-file>の結果にELF ... with debug_info, not strippedと表示される- 少なくとも 1 つの .NET フレームが関数名(
??以外)に解決される - 解決されたパスに、認識できる .NET ランタイムの構造(例:
mono/metadata/、mono/mini/)が含まれる
停止条件
- .NET フレームが見つからない:解析されたフレームを報告して停止します。
- すべてのフレームが解決された:シンボリケーション済みのバックトレースを提示します。ソースをたどったり、ランタイムのビルドやデバッグを試みたりしないでください。
- シンボルが利用できない(404):BuildId ごとに 1 回だけ試行し、その後停止します。シンボリケーションされていないフレームを BuildId とオフセットとともに報告してください。
- llvm-symbolizer が利用できない:
-ParseOnlyを使用し、手動実行用のコマンドを提示してください。LLVM をインストールしないでください。
よくある落とし穴
- BuildId がない:Logcat の tombstone には BuildId が含まれないことがよくあります。次の方法で取得してください:
adb shell readelf -n /path/to/lib.so、CI のビルド成果物、またはランタイムの NuGet パッケージ(~/.dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.android-arm64/<version>/)。BuildId が必ず含まれる、生の tombstone ファイル(adb shell cat /data/tombstones/tombstone_XX)を取得する方法を優先してください。 - シンボルが見つからない(404):プレリリースビルドや内部ビルドでは、シンボルが公開されていないことがあります。ビルド成果物または NuGet ランタイムパックに、ストリップされていない
.so/.so.dbgがないか確認してください。 - NativeAOT:tombstone にランタイムの
.soがありません。ランタイムはアプリのバイナリに含まれています。libSystem.*.soの BCL ライブラリは引き続きシンボルサーバーを利用できますが、アプリのバイナリには独自のデバッグシンボルが必要です。 - llvm-symbolizer のバージョンが不適切:DWARF との互換性を高めるには、LLVM 14 以降を使用してください。
- BuildId が複数ある:.NET ライブラリごとに異なる BuildId があるため、それぞれ個別にシンボルをダウンロードしてください。