android-tombstone-symbolication

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Android Tombstone .NET Symbolication

Android Tombstone .NET符号化

Resolves native backtrace frames from .NET Android app crashes (MAUI, Xamarin, Mono) to function names, source files, and line numbers using ELF BuildIds and Microsoft's symbol server.
Inputs: Tombstone file or logcat crash output,
llvm-symbolizer
(from Android NDK or any LLVM 14+ toolchain), internet access for symbol downloads.
Do not use when: The crash is a managed .NET exception (visible in logcat with a managed stack trace), the crashing library is not a .NET component (e.g.,
libart.so
), or the tombstone is from iOS.

通过ELF BuildIds和微软符号服务器,将.NET Android应用崩溃(MAUI、Xamarin、Mono)产生的原生回溯栈帧解析为函数名、源文件路径和行号。
输入要求: Tombstone文件或logcat崩溃输出、
llvm-symbolizer
(来自Android NDK或任意LLVM 14+工具链)、可访问互联网用于下载符号。
不适用场景: 崩溃为托管.NET异常(可在logcat中看到托管栈轨迹)、崩溃的库不是.NET组件(例如
libart.so
)、tombstone来自iOS系统。

Workflow

工作流

Step 1: Parse the Tombstone Backtrace

第一步:解析Tombstone回溯

Each backtrace frame has this format:
#NN pc OFFSET  /path/to/library.so (optional_symbol+0xNN) (BuildId: HEXSTRING)
Extract: frame number, PC offset (hex, already library-relative), library name, and BuildId (32–40 hex chars).
Symbolicate all threads by default (background threads like GC/finalizer often have useful .NET frames). The crashing thread's backtrace is listed first; additional threads appear after
--- --- ---
markers.
Format notes:
  • The script auto-detects
    #NN pc
    frame lines with or without a
    backtrace:
    header, and strips logcat timestamp/tag prefixes automatically.
  • Logcat-captured tombstones often omit BuildIds. Recover via
    adb shell readelf -n
    , CI build artifacts, or the .NET runtime NuGet package.
  • GitHub issue pastes may mangle
    #1 pc
    into issue links — replace
    org/repo#N pc
    with
    #N pc
    before saving to a file.
  • If the script fails to parse a format, fall back to manual extraction of
    #NN pc OFFSET library.so (BuildId: HEX)
    tuples.
每个回溯栈帧格式如下:
#NN pc OFFSET  /path/to/library.so (optional_symbol+0xNN) (BuildId: HEXSTRING)
提取信息:栈帧编号PC偏移量(十六进制,已为库相对偏移)、库名称BuildId(32-40个十六进制字符)。
默认符号化所有线程的栈帧(GC/终结器等后台线程通常包含有用的.NET栈帧)。崩溃线程的回溯会排在最前面,其余线程出现在
--- --- ---
标记之后。
格式说明:
  • 脚本可自动识别带或不带
    backtrace:
    头部的
    #NN pc
    栈帧行,自动去除logcat的时间戳/标签前缀。
  • logcat捕获的tombstone通常会缺失BuildIds,可通过
    adb shell readelf -n
    、CI构建产物或.NET runtime NuGet包恢复。
  • GitHub issue粘贴的内容可能会将
    #1 pc
    误转换为issue链接——保存到文件前请将
    org/repo#N pc
    替换为
    #N pc
  • 如果脚本无法解析格式,请手动提取
    #NN pc OFFSET library.so (BuildId: HEX)
    元组。

Step 2: Identify .NET Runtime Libraries

第二步:识别.NET Runtime库

Filter frames to .NET runtime libraries:
LibraryRuntime
libmonosgen-2.0.so
Mono (MAUI, Xamarin, interpreter)
libcoreclr.so
CoreCLR (JIT mode)
libSystem.*.so
.NET BCL native components (
Native
,
Globalization.Native
,
IO.Compression.Native
,
Security.Cryptography.Native.OpenSsl
,
Net.Security.Native
)
NativeAOT: No
libcoreclr.so
or
libmonosgen-2.0.so
— the runtime is statically linked into the app binary (e.g.,
libMyApp.so
). The
libSystem.*.so
BCL libraries remain separate and can be symbolicated via the symbol server. For the app binary itself, you need the app's own debug symbols.
Skip
libc.so
,
libart.so
, and other Android system libraries unless the user specifically asks.
筛选出属于.NET runtime库的栈帧:
库名称运行时类型
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系统库。

Step 3: Download Debug Symbols

第三步:下载调试符号

For each unique .NET BuildId, download debug symbols:
https://msdl.microsoft.com/download/symbols/_.debug/elf-buildid-sym-<BUILDID>/_.debug
bash
curl -sL "https://msdl.microsoft.com/download/symbols/_.debug/elf-buildid-sym-1eb39fc72918c7c6c0c610b79eb3d3d47b2f81be/_.debug" \
  -o libmonosgen-2.0.so.debug
Verify with
file libmonosgen-2.0.so.debug
— should show
ELF 64-bit ... with debug_info, not stripped
. If the download returns 404 or HTML, symbols are not published for that build. Do not add or subtract library base addresses — offsets in tombstones are already library-relative.
为每个唯一的.NET BuildId下载调试符号:
https://msdl.microsoft.com/download/symbols/_.debug/elf-buildid-sym-<BUILDID>/_.debug
bash
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中的偏移量已经是库相对偏移。

Step 4: Symbolicate Each Frame

第四步:符号化每个栈帧

bash
llvm-symbolizer --obj=libmonosgen-2.0.so.debug -f -C 0x222098
Output:
ves_icall_System_Environment_FailFast
/__w/1/s/src/runtime/src/mono/mono/metadata/icall.c:6244
The
/__w/1/s/
prefix is the CI workspace root — the meaningful path starts at
src/runtime/
, mapping to dotnet/dotnet VMR.
bash
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仓库。

Step 5: Present the Symbolicated Backtrace

第五步:展示符号化后的回溯

Combine original frame numbers with resolved function names and source locations:
#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)
For unresolved frames (
??
), keep the original line with BuildId and PC offset.
将原始栈帧编号与解析后的函数名、源码位置合并展示:
#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偏移量的原始行。

Automation Script

自动化脚本

scripts/Symbolicate-Tombstone.ps1 automates the full workflow:
powershell
pwsh scripts/Symbolicate-Tombstone.ps1 -TombstoneFile tombstone_01.txt -LlvmSymbolizer llvm-symbolizer
Flags:
-CrashingThreadOnly
(limit to crashing thread),
-OutputFile path
(write to file),
-ParseOnly
(report libraries/BuildIds/URLs without downloading),
-SkipVersionLookup
(skip runtime version identification).

scripts/Symbolicate-Tombstone.ps1可自动化完成全流程:
powershell
pwsh scripts/Symbolicate-Tombstone.ps1 -TombstoneFile tombstone_01.txt -LlvmSymbolizer llvm-symbolizer
可用参数:
-CrashingThreadOnly
(仅处理崩溃线程)、
-OutputFile path
(输出到指定文件)、
-ParseOnly
(仅输出库/BuildId/下载链接,不执行下载)、
-SkipVersionLookup
(跳过运行时版本识别)。

Finding llvm-symbolizer

查找llvm-symbolizer

Check the Android NDK first:
$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/*/bin/llvm-symbolizer
or
$ANDROID_HOME/ndk/*/toolchains/llvm/prebuilt/*/bin/llvm-symbolizer
. Also available via
brew install llvm
,
apt install llvm
, or
xcrun --find llvm-symbolizer
on macOS.
If unavailable, complete steps 1–3 and present the download commands and
llvm-symbolizer
commands for the user to run. Do not spend time installing LLVM.

优先从Android NDK中查找:路径为
$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/*/bin/llvm-symbolizer
$ANDROID_HOME/ndk/*/toolchains/llvm/prebuilt/*/bin/llvm-symbolizer
。也可通过包管理器安装:macOS上执行
brew install llvm
、Debian/Ubuntu上执行
apt install llvm
,或在macOS上通过
xcrun --find llvm-symbolizer
查找。
如果无法获取llvm-symbolizer,完成步骤1-3后向用户提供下载命令和
llvm-symbolizer
执行命令即可,无需花费时间安装LLVM。

Understanding the Output

理解输出内容

CI source paths use these prefixes:
Path prefixMaps to
/__w/1/s/src/runtime/
src/runtime/
in dotnet/dotnet VMR
/__w/1/s/src/mono/
src/mono/
in the VMR (older builds)
/__w/1/s/
VMR root
CI源码路径的前缀对应关系如下:
路径前缀对应路径
/__w/1/s/src/runtime/
dotnet/dotnet VMR仓库中的
src/runtime/
/__w/1/s/src/mono/
VMR仓库中的
src/mono/
(旧版本构建)
/__w/1/s/
VMR仓库根目录

Runtime Version Identification

运行时版本识别

The script identifies the exact .NET runtime version by matching BuildIds against locally-installed runtime packs. It searches: SDK packs (
$DOTNET_ROOT/packs/
), NuGet cache (
~/.nuget/packages/
), and NuGet.org as an online fallback. When found, it extracts the version and source commit from the
.nuspec
<repository commit="..." />
element. Pass
-SkipVersionLookup
to disable. Requires
llvm-readelf
(auto-discovered from the NDK).

脚本可通过将BuildId与本地安装的运行时包匹配,识别准确的.NET runtime版本。搜索范围包括:SDK包(
$DOTNET_ROOT/packs/
)、NuGet缓存(
~/.nuget/packages/
),以及NuGet.org作为在线 fallback。找到匹配后会从
.nuspec
文件的
<repository commit="..." />
元素中提取版本和源码提交哈希。可传入
-SkipVersionLookup
参数禁用该功能,该功能需要
llvm-readelf
(会从NDK中自动查找)。

Validation

验证标准

  1. file <debug-file>
    shows
    ELF ... with debug_info, not stripped
  2. At least one .NET frame resolves to a function name (not
    ??
    )
  3. Resolved paths contain recognizable .NET runtime structure (e.g.,
    mono/metadata/
    ,
    mono/mini/
    )
  1. file <debug-file>
    输出显示
    ELF ... with debug_info, not stripped
  2. 至少有一个.NET栈帧解析为有效函数名(不是
    ??
  3. 解析后的路径包含可识别的.NET runtime结构(例如
    mono/metadata/
    mono/mini/

Stop Signals

停止信号

  • No .NET frames found: Report parsed frames and stop.
  • All frames resolved: Present symbolicated backtrace. Do not trace into source or attempt to build/debug the runtime.
  • Symbols not available (404): One attempt per BuildId, then stop. Report unsymbolicated frames with BuildIds and offsets.
  • llvm-symbolizer not available: Use
    -ParseOnly
    , present manual commands. Do not install LLVM.
  • 未找到.NET栈帧:报告已解析的栈帧后终止流程。
  • 所有栈帧已解析:展示符号化后的回溯即可,无需深入追踪源码或尝试构建/调试运行时。
  • 符号不存在(返回404):每个BuildId尝试一次下载后终止,报告未符号化的栈帧及对应的BuildId和偏移量。
  • 无可用llvm-symbolizer:使用
    -ParseOnly
    参数输出手动执行命令即可,无需安装LLVM。

Common Pitfalls

常见问题

  • Missing BuildIds: Logcat tombstones often omit BuildIds. Recover via:
    adb shell readelf -n /path/to/lib.so
    , CI build artifacts, or the runtime NuGet package (
    ~/.dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.android-arm64/<version>/
    ). Prefer pulling raw tombstone files (
    adb shell cat /data/tombstones/tombstone_XX
    ) which always include BuildIds.
  • Symbols not found (404): Pre-release/internal builds may not publish symbols. Check for local unstripped
    .so
    /
    .so.dbg
    in build artifacts or the NuGet runtime pack.
  • NativeAOT: No runtime
    .so
    in the tombstone — runtime is in the app binary.
    libSystem.*.so
    BCL libraries still work with the symbol server; the app binary needs its own debug symbols.
  • Wrong llvm-symbolizer version: Use LLVM 14+ for best DWARF compatibility.
  • Multiple BuildIds: Each .NET library has its own BuildId — download symbols for each separately.
  • 缺失BuildIds:logcat输出的tombstone通常会缺失BuildIds,可通过以下方式恢复:
    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
    ),这类文件始终包含BuildIds。
  • 找不到符号(404):预发布/内部构建版本可能不会发布符号,可检查构建产物或NuGet运行时包中是否存在未剥离的
    .so
    /
    .so.dbg
    文件。
  • NativeAOT场景:tombstone中不存在独立的运行时
    .so
    文件,运行时被打包到应用二进制中。
    libSystem.*.so
    BCL库仍可通过符号服务器符号化,应用二进制需要自行提供调试符号。
  • llvm-symbolizer版本错误:建议使用LLVM 14+版本以获得最佳DWARF兼容性。
  • 多个BuildIds:每个.NET库都有独立的BuildId,需要分别下载对应的符号。