mcpField
A
android-tombstone-symbolication

android-tombstone-symbolication

github.com/dotnet开发5.8k3.4k 次安装1 天前更新
代码仓库

关于 android-tombstone-symbolication

用于将 .NET Android 应用的原生崩溃回溯解析为函数名和源代码位置的技能,涵盖符号下载,并提供 PowerShell 脚本自动执行解析与符号化。

主要功能

  1. 01解析 tombstone 和 logcat 输出
  2. 02识别 .NET 运行时帧与 BuildId
  3. 03下载运行时调试符号
  4. 04用 llvm-symbolizer 解析帧
  5. 05根据 BuildId 识别运行时版本

适用场景

  1. 01调查 MAUI 或 Xamarin 应用的原生崩溃
  2. 02将 Mono 或 CoreCLR 帧映射到源代码位置
  3. 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.soMono(MAUI、Xamarin、解释器)
libcoreclr.soCoreCLR(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 中查找)。


验证

  1. file <debug-file> 显示 ELF ... with debug_info, not stripped
  2. 至少有一个 .NET 帧解析出函数名(而不是 ??)
  3. 解析出的路径包含可识别的 .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.*.so BCL 库仍可使用符号服务器;应用二进制文件需要自己的调试符号。
  • llvm-symbolizer 版本错误:为获得最佳 DWARF 兼容性,请使用 LLVM 14+。
  • 多个 BuildId:每个 .NET 库都有自己的 BuildId——请分别下载各自的符号。

评论

支持 Markdown