关于 android-tombstone-symbolication
用于将 .NET Android 应用的原生崩溃回溯解析为函数名和源代码位置的技能,涵盖符号下载,并提供 PowerShell 脚本自动执行解析与符号化。
主要功能
- 01解析 tombstone 和 logcat 输出
- 02识别 .NET 运行时帧与 BuildId
- 03下载运行时调试符号
- 04用 llvm-symbolizer 解析帧
- 05根据 BuildId 识别运行时版本
适用场景
- 01调查 MAUI 或 Xamarin 应用的原生崩溃
- 02将 Mono 或 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 偏移量(十六进制,已相对于库)、库名称和 BuildId(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- 至少有一个 .NET 帧解析出函数名(而不是
??) - 解析出的路径包含可识别的 .NET 运行时结构(例如
mono/metadata/、mono/mini/)
停止信号
- 未找到 .NET 帧:报告已解析的帧并停止。
- 所有帧均已解析:提供符号化的回溯。不要继续追踪源代码,也不要尝试构建或调试运行时。
- 符号不可用(404):每个 BuildId 只尝试一次,然后停止。报告未符号化的帧及其 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>/)。优先拉取原始 tombstone 文件(adb shell cat /data/tombstones/tombstone_XX),其中始终包含 BuildId。 - 未找到符号(404):预发布版或内部构建可能不会发布符号。请检查构建产物或 NuGet 运行时包中是否有本地未剥离的
.so/.so.dbg文件。 - NativeAOT:tombstone 中没有运行时
.so——运行时位于应用二进制文件中。libSystem.*.soBCL 库仍可使用符号服务器;应用二进制文件需要自己的调试符号。 - llvm-symbolizer 版本错误:为获得最佳 DWARF 兼容性,请使用 LLVM 14+。
- 多个 BuildId:每个 .NET 库都有自己的 BuildId——请分别下载各自的符号。