- 跨平台
- 图形学
- 前端
【免费下载链接】engine
The Flutter engine
导读:当 Flutter 应用发生原生崩溃时,Android tombstone 与 iOS 崩溃日志中的十六进制地址,往往无法直接看出是引擎哪一行代码出了问题。本文以 Flutter Engine 仓库的 docs/Crashes.md 为骨架,系统讲解如何将引擎崩溃堆栈符号化(Symbolicate):覆盖 Android 下
ndk-stack、addr2line与libflutter.so构建 ID 校验的完整链路,iOS / macOS 上 dSYM 符号的获取方式,以及 Dart AOT 代码崩溃的调试流程。读完本文,你将掌握从一份崩溃报告反查到引擎源码具体行号(如dart_api_impl.cc:1366)的完整方法,也能为自己的本地引擎构建开启带符号的产物。
一、何时需要手动符号化
在 Flutter 3.24 及之后版本产生的 iOS 应用归档包(archive)中,已经内嵌了引擎调试符号,因此这类崩溃默认即可自动符号化。但对于Android以及3.24 之前的旧版 iOS应用,崩溃堆栈默认是十六进制地址,需要额外处理。
最省力的方式是运行dart_ci symbolizer工具在本地进行符号化。如果该方案不可用,则按本文后面的步骤手动完成。手动符号化的核心思路是:
- 从崩溃报告拿到Flutter Framework 或 Flutter Engine 的 revision(提交哈希);
- 以该哈希为索引,从 Flutter 官方产物存储中下载与崩溃应用构建模式(debug / release / profile)严格匹配的符号文件;
- 使用 NDK 自带的
ndk-stack/addr2line(Android)或 Xcode 的atos(iOS/macOS)等工具,把地址翻译为源文件:行号。
二、Android 平台符号化
2.1 获取符号文件
第一步:确定 Engine revision。如果崩溃报告中直接给出了 Engine revision,可直接跳到第三步;否则需要先从 Framework revision 反查 Engine revision。Flutter 仓库中的bin/internal/engine.version文件记录了与某个 Framework 版本对应的 Engine 提交哈希——把该文件 URL 中的main替换为你报告的 Framework 哈希,即可看到对应内容。
第二步:确认完整哈希。报告中的提交哈希可能是短哈希,需要先按本文「2.5 扩展 Git Revisions」一节补齐为完整的 40 位哈希,例如cea5ed2b9be42a981eac762af3664e4a17d0a53f。
第三步:下载符号压缩包。有了完整 engine revision,可以打开产物浏览页面(把其中的 engine 哈希替换为你的哈希):
- 产物浏览页面:
https://console.cloud.google.com/storage/browser/flutter_infra_release/flutter/<ENGINE_HASH>
下载符号文件则使用另一个主机名(storage,而非 console)的 URL,且必须使用浏览器访问,因为它需要登录认证:
https://storage.cloud.google.com/flutter_infra_release/flutter/<ENGINE_HASH>/android-arm/symbols.zip
⚠️关键:符号类型必须与应用的发布类型匹配。上例对应的是 android-arm 的debug构建。如果应用是release或profile构建,URL 中对应目录名分别是
android-arm-release与android-arm-profile:
https://storage.cloud.google.com/flutter_infra_release/flutter/<ENGINE_HASH>/android-arm-release/symbols.ziphttps://storage.cloud.google.com/flutter_infra_release/flutter/<ENGINE_HASH>/android-arm-profile/symbols.zip选错模式会导致地址无法正确解析——因为不同构建模式下 Dart 运行时与引擎的代码布局完全不同。
解压symbols.zip后,即可得到带调试信息的libflutter.so等符号文件。
2.2 使用 ndk-stack 符号化
ndk-stack随 Android NDK 一起分发。假设stack.txt中保存了完整的崩溃堆栈(必须包含开头的*** *** ***标记行,它是 tombstone 的起始标志):
# Linux .../ndk/prebuilt/linux-x86_64/bin/ndk-stack -sym .../path/to/downloaded/symbols < stack.txt# macOS .../ndk/prebuilt/darwin-x86_64/bin/ndk-stack -sym .../path/to/downloaded/symbols < stack.txtndk-stack会解析 stdin 中的日志,把每一帧的地址与-sym指定的符号目录进行匹配并输出对应源文件与行号。
注意日志完整性:某些调试工具(如pidcat)可能不会展示完整的 tombstone 日志,这种情况下请直接使用adb logcat抓取并复制完整输出,否则符号化结果会缺失帧。
2.3 使用 addr2line 符号化
另一种方式是使用同样打包在 NDK 中的addr2line工具:把上文下载的.so文件路径传给-e参数,然后手动把堆栈中的地址逐一喂给它。例如在 macOS 上:
% $ANDROID_HOME/ndk/20.0.5594570/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line -e ~/Downloads/libflutter.so此时工具进入等待输入状态,把崩溃地址粘贴进去(注意地址中的前导0x需保留):
0x00000000006a26ec /b/s/w/ir/cache/builder/src/out/android_release_arm64/../../third_party/dart/runtime/vm/dart_api_impl.cc:1366这个例子清晰地展示了成果:地址0x00000000006a26ec对应的是 Dart VM 源码dart_api_impl.cc的第 1366 行。addr2line的优势是不依赖完整 tombstone,可以逐帧灵活处理,但需要手动复制每个地址。
2.4 确认拿到的是正确的 libflutter.so
符号化失败的最常见原因,是下载的.so与崩溃设备上实际运行的libflutter.so不是同一个构建产物。构建系统会为每个libflutter.so打上一个唯一的Build ID,在 tombstone 中可以看到:
#00 pc 000000000062d6e0 /data/app/com.app-tARy3eLH2Y-QN8J0d0WFog==/lib/arm64/libflutter.so!libflutter.so (offset 0x270000) (BuildId: 34ad5bdf0830d77a)上例中该.so的 Build ID 为34ad5bdf0830d77a。用file命令可以验证下载到的符号文件:
% file ~/Downloads/libflutter.so /Users/user/Downloads/libflutter.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[xxHash]=34ad5bdf0830d77a, with debug_info, not stripped务必确认两者的 Build ID 完全一致,否则无法完成符号化。顺带说明,输出中的not stripped表示该文件保留了调试信息,这也是它能被符号化的前提(引擎的 release 构建符号包正因如此才能解析出源码行号)。
2.5 扩展 Git Revisions
崩溃报告中出现的往往是短提交哈希(如2a13567)。要去掉这个前缀,只需在浏览器中打开对应仓库的 commit 页面,并把短哈希作为 URL 最后一段(例如 Flutter 仓库的.../commit/9cb914df1或 Engine 仓库的.../commit/2a13567),页面中会展示完整的 40 位 revision,直接复制即可用于构造符号下载 URL。
2.6 符号化本地构建的引擎
如果你使用的是自己编译的引擎,则无需下载符号——构建产物本身就带符号,直接用ndk-stack指向构建输出目录即可:
# dev/engine 是你的引擎源码目录(包含 .gclient 文件的位置) # android_debug_unopt 是你正在使用的引擎构建变体 adb logcat | ~/dev/engine/src/third_party/android_tools/ndk/prebuilt/linux-x86_64/bin/ndk-stack -sym ~/dev/engine/src/out/android_debug_unopt这条命令把adb logcat的实时输出通过管道直接交给ndk-stack,崩溃发生时即可即时得到符号化结果,非常适合本地调试循环。
三、iOS 平台符号化
3.1 Flutter 3.24 及之后:从 artifact cache 获取
自 Flutter 3.24 起,iOS 引擎符号已经包含在 Flutter framework 的artifact cache中,即 xcframework 包内:
bin/cache/artifacts/engine/ios-release/Flutter.xcframework其中:
- 真机(device)构建的符号位于
ios-arm64/dSYMs/Flutter.framework.dSYM包内; - 模拟器(simulator)构建的符号位于
ios-arm64_x86_64-simulator/dSYMs/Flutter.framework.dSYM包内。
对于 3.24 及之后的 release,这些符号不再作为独立压缩包单独上传,必须从上述 artifact cache 获取。artifact cache 本身也可以通过 URL 直接下载(把 engine 哈希替换为你的哈希):
https://storage.googleapis.com/flutter_infra_release/flutter/<ENGINE_HASH>/ios-release/artifacts.zip3.2 3.24 之前:从 Google Cloud Storage 下载
对于 Flutter 3.24 之前的版本,需要参照本文 Android 一节的流程,只是最后一步的下载 URL 换成如下模式(替换 engine 哈希):
https://storage.cloud.google.com/flutter_infra_release/flutter/<ENGINE_HASH>/ios-release/Flutter.dSYM.zip四、macOS 平台符号化
4.1 Flutter 3.27 及之后:从 artifact cache 获取
自 Flutter 3.27 起,macOS 引擎符号包含在 artifact cache 的 xcframework 中:
bin/cache/artifacts/engine/darwin-x64-release/Flutter.xcframework- 设备构建的符号位于
macos-arm64_x86_64/dSYMs/FlutterMacOS.framework.dSYM包内。
3.27 及之后的 release 同样不再上传独立符号压缩包,而是通过 artifact cache 获取,可直接用如下 URL 下载(替换 engine 哈希):
https://storage.googleapis.com/flutter_infra_release/flutter/<ENGINE_HASH>/darwin-x64-release/framework.zip4.2 3.27 之前:从 Google Cloud Storage 下载
参照 Android 一节的流程,下载 URL 使用如下模式(替换 engine 哈希):
https://storage.cloud.google.com/flutter_infra_release/flutter/<ENGINE_HASH>/darwin-x64-release/FlutterMacOS.dSYM.zip4.3 符号化本地构建
如果你在debug 或 profile Dart 模式下构建了本地引擎,framework 的 dylib 符号不会被剥离,开箱即可符号化,无需额外下载任何文件。
五、调试 Dart AOT 代码中的崩溃(iOS)
如果崩溃发生在AOT Dart 代码中(即--release或--profile构建),且你能够自行构建引擎,以下步骤将产出对 Dart VM 团队修复 bug 非常有用的信息:
- 准备一个最小复现用例(reduced test case)。
- 以 profile 模式编译引擎并关闭优化,以获得可符号化的堆栈:
sky/tools/gn --ios --unopt --runtime-mode profile; ninja -C out/ios_profile_unopt -j800- 该命令位于仓库的 sky/tools 目录;
--unopt关闭优化、--runtime-mode profile指定运行时模式(下文「六」会结合源码解释这两个参数的底层映射)。
- 通过 Xcode 工程启动应用,并在调试器中使其崩溃。
- 记录寄存器状态:在
lldb中执行register read,把输出粘贴进 bug。 - 记录回溯:在
lldb中执行thread backtrace(默认显示当前线程;如果不在崩溃线程上,先用thread select n切换)。 - 反汇编最后一帧:
frame select 0后执行disassemble --frame,把结果粘贴进 bug。 - 用
gen_snapshot反汇编以获取更详细信息:- 在回溯中找出导致崩溃的预编译函数名;
- 在 Xcode 中打开
SnapshotterInvoke,在RunCommand ... Snapshotter调用处加上--disassemble标志; - 修改
RunCommand函数,把输出转储到文件; - 重新构建,结果会写入该文件;在文件中按函数名(子串匹配)搜索并复制对应信息到 bug 中。
- 在 dart-lang/sdk 上提交 issue,并联系相关人员(ping)跟进。
六、Android 本地引擎构建保留符号的两种手段
当使用本地引擎构建运行时,符号化流程可能显得繁琐。更直接的做法是让引擎产物本身携带符号,并阻止 Gradle 自动剥离符号——这同时也是在 Android Studio 的 CPU Profiler 中看到符号名称的前提条件。
6.1 让 gn 输出未剥离的 libflutter.so
在 Android 引擎配置中为 gn 传入--no-stripped参数:
gn --android --android-cpu=arm64 --unopt --no-stripped这一参数在仓库的构建脚本 tools/gn 中定义:--stripped默认值为true(帮助文本明确注明 "Strip debug symbols from the output. This defaults to true and has no effect on iOS."),--no-stripped将其置为false,最终写入 GN 参数gn_args['stripped_symbols'] = args.stripped(见 tools/gn)。
该参数的实际效果可以在 Android shell 的构建定义 shell/platform/android/BUILD.gn 中看到:当stripped_symbols为真时,android_jar目标把已剥离的lib.stripped/libflutter.so打入产物;为假时则直接使用未剥离的libflutter.so。
6.2 阻止 Gradle 二次剥离
即使引擎产物带符号,Gradle 打包时仍可能再次剥离.so的符号。在 Flutter 项目的android/app/build.gradle的android块中加入:
packagingOptions{ doNotStrip "**/*.so" }这样 APK 内所有架构的.so都会保留符号,ndk-stack、Android Studio CPU Profiler 以及系统 tombstone 都能直接解析出函数名。
6.3 补充:runtime-mode 与 unopt 的底层映射
结合 tools/gn 与 tools/gn 的源码可以理解调试构建的深层关系:
--unoptimized会直接映射到 GN 的is_debug(见 tools/gn),即"未优化"对应调试级构建;--runtime-mode debug被映射为 Dart 运行时的develop模式(dart_runtime_mode = 'develop'),jit_release映射为release,其余模式(profile/release)则原样传递;- 这些参数组合决定了 Dart VM 的快照类型与优化等级,也因此决定了崩溃地址能否与下载的符号包对上——再次印证了"符号必须与构建模式严格匹配"这一原则。
此外,仓库的 common/exported_symbols.sym 展示了引擎在动态符号表中必须导出的符号(如kDartVmSnapshotData、kDartIsolateSnapshotInstructions等),这些符号用于运行时查找,与静态调试符号是两套体系,不会被--no-stripped影响,读者在排查符号缺失时可作区分。
七、总结:符号化决策速查
| 平台 / 场景 | 符号来源 | 符号化工具 |
|---|---|---|
| Android(任意 Flutter 版本) | 按 Engine revision 下载对应模式的symbols.zip(需浏览器认证) | ndk-stack/addr2line |
| iOS ≥ 3.24 | artifact cache 内Flutter.xcframework的 dSYM | Xcode /atos |
| iOS < 3.24 | GCS 下载Flutter.dSYM.zip | Xcode /atos |
| macOS ≥ 3.27 | artifact cache 内FlutterMacOS.framework.dSYM | Xcode /atos |
| macOS < 3.27 | GCS 下载FlutterMacOS.dSYM.zip | Xcode /atos |
| 本地引擎构建(Android) | gn --no-stripped+ GradledoNotStrip "**/*.so" | ndk-stack直接指向 out 目录 |
| 本地引擎构建(iOS/macOS debug/profile) | 符号默认不剥离 | 调试器直接可用 |
实操中的三个铁律:符号构建模式必须与崩溃应用一致、Build ID 必须匹配(用file校验)、日志必须完整(优先adb logcat原始输出)。遵循本文流程,即可把一段pc 0x000000000062d6e0式的原始崩溃帧,还原为可读的源码位置,为定位与上报引擎级崩溃问题提供坚实依据。
- 跨平台
- 图形学
- 前端
【免费下载链接】engine
The Flutter engine
相关推荐
刘海屏菜单栏图标遮挡:用 Ice 快速找回清爽的 Mac 菜单栏
刘海屏菜单栏图标遮挡:用 Ice 快速找回清爽的 Mac 菜单栏 打开 MacBook,一排图标挤到刘海后面,电池和时间只剩半截。Ice 是一款 macOS 菜
桌面应用如何在iOS设备上实现KSCrash符号化:解密崩溃堆栈的完整指南
如何在iOS设备上实现KSCrash符号化:解密崩溃堆栈的完整指南 KSCrash作为一款强大的iOS崩溃报告工具,能够帮助开发者捕获和分析应用崩溃信息。本文将
CLI开发工具Apple 平台 .ips 崩溃日志解析与 macOS .NET 符号获取全指南
Apple 平台 .ips 崩溃日志解析与 macOS .NET 符号获取全指南 导读 本文围绕 dotnet diag 插件 https://link.git
人工智能AI 技能AI 评测Benchmark开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考