符号化与反混淆
本文档描述如何将采集的 trace 中的原始指令地址和混淆的 Java/Kotlin 名称转换为人类可读的函数名、源代码位置和类/方法名。
正确的方法取决于你拥有哪种类型的 trace,因此本页按这一问题组织。本文档中使用的两个定义:
- 符号化(Symbolization):使用被 profile 进程中加载的未剥离 ELF 二进制文件(或等效的 Breakpad 符号文件),将 native 指令地址映射回函数名、源文件和行号。
- 反混淆(Deobfuscation):使用构建时生成的
mapping.txt,将 R8/ProGuard 发出的混淆 Java/Kotlin 名称(例如fsd.a)映射回原始标识符。
使用 debuginfod 获取调试文件
如果你有一个带 native build ID 的 trace 并且可以访问 debuginfod 服务器,就可以在不提供本地二进制文件的情况下创建符号化的 bundle。安装 curl 和 llvm-symbolizer,然后运行:
trace_processor bundle --debuginfod \
--debuginfod-urls "https://your-debuginfod-server.example" \
input.pftrace output.tar如果已经配置了 DEBUGINFOD_URLS,则只需要 --debuginfod。在 UI 中打开生成的 bundle。检查报告中未解析帧的数量;使用 --verbose 调查未成功的查找。下载会被缓存以供后续运行使用。使用 --debuginfod-cache-path PATH 选择不同的缓存。
有关优先级、超时、缓存布局和输出控制,请参阅 CLI 参考。
你需要哪种工作流?
根据你的 trace 匹配以下类别之一并点击链接。选择错误的工作流是符号"不起作用"的最常见原因。关键经验法则:用户空间符号在主机上离线解析(trace_processor bundle),而内核符号始终在设备上录制时解析(Perfetto 故意不存储绝对内核地址,以避免泄露
KASLR)。
| 你的 trace 包含… | 示例 | 你需要什么 |
|---|---|---|
| 调用栈 | Native heap profiler、traced_perf / Linux perf CPU 采样、ART 堆转储 |
符号化与反混淆。用户空间帧离线解析(trace_processor bundle);内核帧在设备上自动符号化。 |
| 内核 ftrace 事件 | function_graph 追踪、sched_blocked_reason、kprobes |
录制时 symbolize_ksyms。这些地址无法事后符号化。 |
| 用户空间事件名称 | atrace slice 名称、ART 方法追踪 | 目前不支持离线反混淆;在插桩时发出可读名称。 |
调用栈:符号化和反混淆
这适用于任何采集调用栈的 DataSource:native heap profiler、基于 perf 的 CPU profiler(traced_perf 和导入的 Linux perf 数据)以及 ART 分配 profiler。
这些数据源记录原始的用户空间指令地址(在 Android 上还包括混淆的 Java/Kotlin 帧),你可以在录制 后使用以下步骤在主机上解析。只要你仍然有匹配的二进制文件和 mapping 文件,你不需要重新采集即可获得用户空间符号或反混淆名称。
调用栈还可能包含内核帧,它们的处理方式不同;请参阅本节末尾的调用栈中的内核帧。
方式 1:trace_processor bundle(推荐)
trace_processor bundle 是一个一键命令,它接受一个 trace 并生成一个丰富化的 trace:原始 trace 加上分析它所需的所有符号和反混淆数据,打包在单个文件中。
trace_processor bundle input.perfetto-trace enriched-trace丰富化的 trace 可以像任何其他 trace 一样在 Perfetto UI 或 trace_processor_shell 中打开,符号和反混淆名称已自动应用。
NOTE: 作为实现细节,丰富化的 trace 目前被打包为 TAR 归档文件,包含原始 trace、native 符号 Packet 和 Java/Kotlin 反混淆 Packet。UI 和 trace_processor_shell 透明地读取此格式,因此你通常不需要自行解包。
要求:
$PATH上需要有llvm-symbolizer以生成函数名和行号的 native 符号化(在 Debian/Ubuntu 上使用sudo apt install llvm)。- 输入和输出必须是文件路径;不支持 stdin/stdout。
- 磁盘上有匹配的未剥离二进制文件 / Breakpad 符号(Build ID 必须与设备上采集的匹配)。
- 对于 Java/Kotlin:需要设备上运行的构建所产生的
mapping.txt。
自动路径发现
相比方式 2 的主要优势是,bundle 会在所有常见位置查找符号和 mapping 文件,无需配置。它搜索:
- 在
lunch过的 AOSP 检出中运行时的 AOSP 构建输出($ANDROID_PRODUCT_OUT/symbols)。 - 标准系统调试目录(
$HOME/.debug、/usr/lib/debug)。 - trace 的
stack_profile_mapping中记录的绝对库路径(当在你用于分析的同一台机器上进行 profile 时很有用)。 - ProGuard/R8 mapping 文件的标准 Android Gradle 项目布局(
./app/build/outputs/mapping/<variant>/mapping.txt)。
使用标志补充发现
当自动发现不够时:
trace_processor bundle \
--symbol-paths /path/to/symbols1,/path/to/symbols2 \
--proguard-map com.example.app=/path/to/mapping.txt \
--verbose \
input.perfetto-trace enriched-trace将 --symbol-paths 指向包含匹配的未剥离二进制文件或 native 调试文件的目录,例如你构建产物的 symbols 目录。bundle 会递归搜索并按 Build ID 匹配,因此无需重建设备的目录布局。Java/Kotlin 的 mapping.txt 文件请使用 --proguard-map 单独传递。
使用 --verbose 诊断缺失的符号。要禁用自动发现,请添加 --no-auto-symbol-paths 和 --no-auto-proguard-maps。来自 PERFETTO_BINARY_PATH 的 native 路径仍然生效;取消设置该变量可将查找范围限制为 --symbol-paths。
有关选项语义、颜色控制、输出替换和退出状态,请参阅 bundle 命令参考。
方式 2:传统 trace_processor util symbolize / util deobfuscate
NOTE: 此流程是为了与已有的脚本和 CI 流水线向后兼容而保留的。对于新使用场景,请始终优先使用方式 1——它更简单,具有自动发现功能,并且适用于非 Perfetto trace 格式。
较旧的 trace_processor util symbolize 和 trace_processor util deobfuscate 子命令生成独立的符号和反混淆文件,完全由环境变量驱动,然后必须手动拼接到 trace 上。
Native 符号化
所有工具(trace_processor、heap_profile 脚本)都遵循 PERFETTO_BINARY_PATH 环境变量:
PERFETTO_BINARY_PATH=somedir tools/heap_profile android --name ${NAME}为已采集的 trace 生成独立的符号文件:
PERFETTO_BINARY_PATH=somedir trace_processor util symbolize raw-trace > symbols或者,设置 PERFETTO_SYMBOLIZER_MODE=index,符号化器将按 Build ID 递归索引目录中的 ELF 文件,因此文件名不需要匹配。
Java/Kotlin 反混淆
通过 PERFETTO_PROGUARD_MAP 提供 ProGuard/R8 mapping,使用格式 packagename=map_filename[:packagename=map_filename...]:
PERFETTO_PROGUARD_MAP=com.example.pkg1=foo.txt:com.example.pkg2=bar.txt \
./tools/heap_profile android -n com.example.app为现有 trace 生成独立的反混淆文件:
PERFETTO_PROGUARD_MAP=com.example.pkg=proguard_map.txt \
trace_processor util deobfuscate ${TRACE} > deobfuscation_map将输出附加到 trace
上面的 symbols 和 deobfuscation_map 都是序列化的 TracePacket proto,因此对于 Perfetto protobuf trace,你可以简单地将它们拼接:
cat ${TRACE} symbols > symbolized-trace
cat ${TRACE} deobfuscation_map > deobfuscated-trace
# 或者两者都加:
cat ${TRACE} symbols deobfuscation_map > enriched-tracetools/heap_profile 脚本在设置了 PERFETTO_BINARY_PATH 时,会在其输出目录中自动执行此操作。
限制:
- 拼接技巧仅适用于 Perfetto protobuf trace。其他 trace 格式(Chrome JSON、systrace、Firefox profile 等)不能以这种方式追加
TracePacket字节。对于这些格式,请使用方式 1并通过trace_processor_shell加载符号。 - 你必须手动管理
PERFETTO_BINARY_PATH/PERFETTO_PROGUARD_MAP;方式 1 中的自动发现不适用。
符号查找顺序
对于 trace 中的每个 native mapping,符号化器查找具有匹配 Build ID 的文件。对于每个搜索路径 P,它按以下顺序尝试:
- 库文件相对于
P的绝对路径。 - 同上,但去掉文件名中的
base.apk!。 - 库文件相对于
P的基本名称。 - 基本名称,去掉
base.apk!。 P/.build-id/<前 2 个十六进制数字>/<其余部分>.debug(标准的 Fedora Build ID 布局)。
例如,带有 Build ID abcd1234... 的 /system/lib/base.apk!foo.so 在符号路径 P 下查找:
P/system/lib/base.apk!foo.soP/system/lib/foo.soP/base.apk!foo.soP/foo.soP/.build-id/ab/cd1234...debug
第一个具有匹配 Build ID 的文件胜出。如果磁盘上的 Build ID 与 trace 中记录的不同,则跳过该文件。
从 C++ 库使用符号化/反混淆
目前没有稳定的公共 C++ API 用于在进程内执行符号化或反混淆。底层实现存在(src/traceconv/trace_to_bundle.h 中的 TraceToBundle,由 src/trace_processor/util/trace_enrichment/trace_enrichment.h 中的 EnrichTrace 支持),但它位于 src/ 而非 include/ 下,不属于公共 API 接口。
如果你需要此功能,请在 GitHub issue #5534 上 +1,以便我们评估需求并确定优先级。
故障排除
trace_processor bundle 始终会生成一个至少包含原始 trace 的 bundle。当它无法添加所有想要的丰富化内容时,会打印缺失的内容及修复方法的摘要,然后仍然成功退出——因此即使命令成功也要检查其输出。只有在真正失败时(输入不可读、输出不可写,或显式提供的 --proguard-map 无法读取)才会以非零值退出。
常见消息及其含义:
N frames from M mappings: no usable symbols in the searched paths,后面跟着 mapping 名称:工具搜索了自动发现的路径(加上你给出的任何--symbol-paths),但没有为这些 mapping 找到 Build ID 匹配的二进制文件,或只找到已剥离的版本。其下方的To fix this块取决于二进制文件的来源。如果你自己构建它们,请将--symbol-paths指向未剥离的构建产物。如果它们来自你的操作系统,请安装其调试符号:Debian/Ubuntu 上使用apt install <package>-dbgsym(用dpkg -S <path>找到包名),Fedora 上使用dnf debuginfo-install <package>(用rpm -qf <path>找到包名),两者都会安装到/usr/lib/debug并被自动发现;在 Android 上,则是匹配平台构建的symbols目录。使用--verbose重新运行可查看 Build ID 和尝试过的每个路径。N frames from M mappings: kernel frames, no vmlinux in the searched paths:安装内核调试包(Debian/Ubuntu 上是linux-image-$(uname -r)-dbg,Fedora 上是dnf debuginfo-install kernel),或将--symbol-paths指向你内核构建产物的vmlinux。N frames from M mappings: no build ID recorded, so symbols cannot be looked up:trace 的 mapping 没有 Build ID,因此即使有正确的二进制文件也无法匹配符号。使用带 Build ID 的二进制文件重新构建(链接器标志-Wl,--build-id)并重新录制。N frames from M mappings: no backing file to read symbols from (JIT, anonymous or [vdso]-style mappings):这些帧来自没有二进制文件支撑的内存。离线工具无法为它们命名。Kernel function names: this trace contains function_graph events ...:trace 包含来自function_graph(或类似 ftrace 事件)的内核地址,录制时未启用symbolize_ksyms。这些无法离线符号化;请启用symbolize_ksyms: true重新录制。参见内核 ftrace 事件。no symbol paths were searched:自动发现被禁用(--no-auto-symbol-paths)且没有给出显式路径。传入带待搜索目录的--symbol-paths。cannot create output file ...:无法创建输出路径(例如父目录不存在或不可写)。检查该路径。
找不到库
使用 --verbose 对 Profile 进行符号化时,你可能会看到如下消息:
Could not symbolize 12 frames from 1 mapping:
/data/app/invalid.app-wFgo3GRaod02wSvPZQ==/lib/arm64/somelib.so (12 frames)
build ID: 44b7138abd5957b8d0a56ce86216d478
no binary with a matching build ID in:
/path/to/symbols/somelib.so (file not found)
To fix this:
If you build these binaries yourself, pass --symbol-paths DIR1,DIR2,... pointing at the unstripped build outputs.
If they come from your OS, install its debug symbols; see https://perfetto.dev/docs/learning-more/symbolization检查 somelib.so 是否存在于某个搜索路径下(--symbol-paths 或自动发现的位置)。然后使用 readelf -n /path/to/somelib.so 比较磁盘上的 Build ID 与消息中报告的 Build ID。如果它们不匹配,磁盘上的副本是不同于设备上的构建,无法使用。
使用 --verbose 重新运行 trace_processor bundle 会打印尝试的每个路径,这通常可以清楚地表明文件是完全缺失还是找到了但 Build ID 不匹配。
调用栈中的内核帧
采样的调用栈可能包含内核帧(例如使用 callstack_sampling { kernel_frames: true } 进行 perf 采样)。与上述用户空间帧不同,这些帧在设备上录制时自动从 /proc/kallsyms 符号化 — 本节中的离线工具不会处理它们。
为了使内核帧具有名称,录制必须能够读取 /proc/kallsyms,这需要以 root 身份运行或降低 kptr_restrict:
echo 0 | sudo tee /proc/sys/kernel/kptr_restrict如果内核帧显示为十六进制地址,这是录制时的权限问题,你必须重新录制。这与下面的内核 ftrace 事件具有相同的 KASLR 限制,但请注意两者使用不同的机制:调用栈内核帧不使用 symbolize_ksyms ftrace 选项 — 该标志仅影响 ftrace 事件。
内核 ftrace 事件:symbolize_ksyms
如果你正在进行系统 Tracing 并在预期出现内核函数名的地方看到原始十六进制地址 — 例如在
函数图 Tracing 中,在 blocked_function 字段(来自不可中断休眠的
调度阻塞)中,或在 kprobe 事件中 — 修复方法不是离线符号化。
这些内核地址会在录制时通过在 ftrace 配置中启用 symbolize_ksyms 来解析:
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
symbolize_ksyms: true
# ... 你的 ftrace_events / function_graph 配置 ...
}
}
}这会读取设备上的 /proc/kallsyms,并将(偏移后的)符号表嵌入 trace 中。它要求 traced_probes 以 root 身份运行或手动降低 kptr_restrict。
WARNING: trace_processor bundle 和上述离线符号器无法恢复内核符号。Perfetto 故意不在 trace 中存储绝对内核地址,因为这样做会破坏
KASLR 并泄露内核内存布局。符号名称在设备上进行了偏移处理,因此可以在不泄露绝对地址的情况下工作。如果你忘记设置 symbolize_ksyms,必须重新录制。
此标志仅适用于 ftrace 事件。在采样调用栈中捕获的内核帧另有处理方式;参见调用栈中的内核帧。
用户空间事件名称:atrace 和 ART 方法追踪
某些数据源记录的是人类可读的名称字符串而非地址或栈帧。当这些字符串被混淆时(例如 R8 混淆的类名),没有离线机制可以反混淆它们 — 名称必须在插桩时以可读形式发出。这与 Java/Kotlin 栈帧反混淆(见调用栈部分)不同,后者仅适用于堆转储和采样调用栈。
目前影响两种情况:
- atrace / 用户空间 slice 名称:atrace slice 名称(以及出现在
TRACE_EVENT字面值中的其他字符串)会逐字记录。没有事后映射步骤。 - ART 方法追踪:ART 方法追踪捕获的方法名称不会经过 ProGuard/R8 反混淆路径,因此混淆的构建将显示混淆的方法名称。
基于 mapping.txt 的反混淆路径原则上可行但目前尚未实现。相关支持正在讨论中;参见
GitHub issue #6391 了解背景并表达兴趣。