符号化你的iOS崩溃报告

符号化你的iOS崩溃报告

前言

本文翻译自Symbolicating Your iOS Crash Reports。这是一篇对于iOS崩溃报告符号化的比较全面的文章。我对Symbolicatecrash工具做了比较完善的封装,一句话就可以符号化你的崩溃报告,还集成了检查UUID匹配的功能。

sh symbolicate.sh Crasher.crash Example/Crasher.app > Crasher_Symbolicated.crash

或者

sh symbolicate.sh Crasher.crash Example/Crasher.app.dsym > Crasher_Symbolicated.crash

项目地址:https://github.com/JohnWong/symbolicate

准备开始

你一定已经处理过你的app的崩溃报告,但是其堆栈回溯包含难以辨认的内存地址。作为一个开发者该怎么办呢?简单说来,你需要将调试符号应用在堆栈追踪上使其可读,这个过程被称作符号化。

但是在我们准备开始之前,你可以使用 https://github.com/chaledoubleencore/Crasher,它提供了一个示例崩溃报告让你来解码。

你应该有.crash文件。如果没有,你可以从iTunes Connect上获取,通过Xcode(Window > Devices)直接从链接的设备上获取,从连接的设备(Settings > Privacy > Diagnostics & Usage),或者亲自动手使用PLCrashReporter框架。你可能已经使用第三方崩溃报告服务,它将会在你正确设置后为你符号化崩溃报告。

依赖于你的app的构建如何配置,你需要下列文件中一个或者两者都需要:

  • 崩溃的应用构建的.app。包里包含了app的二进制,可能包含了调试符号。(如果你有一个.ipa,你可以把它当作一个.zip文件来解压出其中的.app。)
  • 崩溃的应用构建的.dSYM。如果你的.app不包含调试符号,这将是你的应用的包含调试符号的副产品。

你需要哪一个?在Xcode里,在构建设置中寻找“Strip Debug Symbols During Copy”(COPY_PHASE_STRIP)。当启用时,调试符号将会在你的.app中省略,并放置在一个.dSYM文件中。否则你的.app包含了这些符号。(为了混淆,调试符号在release构建中默认剥离。你大概不应该修改release配置的设置。)

但是等一下,什么是调试符号?

在我们的用途下,一个调试符号是一个程序员给一个方法所取的可读的名字。编译器通过减少这些命名的调试符号到其自身的符号来混淆代码。你不能指望这些符号在两次构建后相同,即使是你构建相同的代码两次。

检查Crash

如果你通过Xcode的Organizer从设备拉取到崩溃日志,你的崩溃报告可能自动符号化UIKit或者其他iOS框架。如果你的Xcode仍然了解你的构建,它将会自动符号化你的崩溃报告。

如果不是这种情况,那么你需要自己来符号化。

使用“Symbolicatecrash”工具来符号化

幸运的是Apple提供给我们一个脚本来检索调试符号并应用到崩溃报告上。

对于Xcode 6来说,你可以在这里找到: /Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/Current/Resources/symbolicatecrash

或者如果你在使用Xcode 5: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/Current/Resources/symbolicatecrash

为了使用这个工具,你需要导出DEVELOPER_DIR环境变量到你的Xcode安装目录的合适的路径:

export DEVELOPER_DIR=”/Applications/Xcode.app/Contents/Developer”

将你的.crash.app.dSYM文件放在同一个目录下并运行:

symbolicatecrash ScaryCrash.crash > Symbolicated.crash

你可能需要制定app的二进制:

symbolicatecrash -v ScaryCrash.crash ./Crasher.app/Crasher > Symbolicated.crash

你或许需要明确指定app的二进制

symbolicatecrash ScaryCrash.crash ./Crasher.app/Crasher > Symbolicated.crash

验证你的符号化

如果符号化并不起效,再次检查你拿到的是正确的.dSYM.app。你可以通过对比崩溃报告中构建的UUID和app二进制中的UUID来再次检查:

dwarfdump –uuid Crasher.app/Crasher

UUID: B00CDF0C-2965-3095-B1E8-6078B12D79E5 (armv7) Crasher.app/Crasher UUID: 3F3BE3C6-DD2E-3E23-A603-A18097C9317F (arm64) Crasher.app/Crasher

并且在dSYM中:

dwarfdump –uuid Crasher.app.dSYM/Contents/Resources/DWARF/Crasher

UUID: B00CDF0C-2965-3095-B1E8-6078B12D79E5 (armv7) Crasher.app.dSYM/Contents/Resources/DWARF/Crasher UUID: 3F3BE3C6-DD2E-3E23-A603-A18097C9317F (arm64) Crasher.app.dSYM/Contents/Resources/DWARF/Crasher

对于崩溃报告中的UUID:

0xa8000 – 0xaffff Crasher armv7 <b00cdf0c29653095b1e86078b12d79e5> /var/mobile/Containers/Bundle/Application/956755E3-6C66-4E87-A8BC-352FD4BE3711/Crasher.app/Crasher

symbolicatecrash工具的verbose日志也列出了其发现的UUID。

“Symbolicatecrash”工具故障排除

如果你仍然感到困惑,小心检查符号化的日志。符号化工具试图通过匹配你的app和其他动态框架的UUID来定位到合适的文件。查找你的app名称或者UUID来看是否匹配。

…….fetching symbol file for Crasher[K–[undef] Searching []…– NO MATCH Searching in Spotlight for dsym with UUID of b00cdf0c29653095b1e86078b12d79e5 … Number of symbols in /Users/You/Workspace/Crasher.app/Crasher: 1 + 106 = 107 Found executable /Users/You/Workspace/Crasher.app/Crasher – MATCH

这里有一个你可能遇到的Spotlight无法定位到你的.dSYM的日志的示例:

Did not find executable for dsym Warning: Can’t find any unstripped binary that matches version of /private/var/mobile/Containers/Bundle/Application/956755E3-6C66-4E87-A8BC-352FD4BE3711/Crasher.app/Crasher

或者其他无效输入:

fatal error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: can’t figure out the architecture type of: ./Crasher.app.dSYM.zip ./Crasher.app.dSYM.zip doesn’t contain armv7 slice

Xcode 6版本的symbolicatecrash工具尝试修复一些Xcode 5版本所面对的Spotlight问题。如果你仍然有麻烦,它可能是一个Spotlight的文件索引问题。试试:

mdimport -g /Applications/Xcode.app/Contents/Library/Spotlight/uuid.mdimporter .

使用命令行工具链

我们可以更深入一步,使用开发者工具链来一行一行地符号化堆栈追踪的内存地址。如果你以前使用这种方法遇到了很大麻烦,可能是由于.crash格式在近几年变化了。

首先,我们再看一下崩溃报告中的堆栈追踪:

… 13 Crasher 0x000aeef6 0xa8000 + 28406 …

最左边的十六进制值0x000aeef6是堆栈地址。第二个十六进制值0xa8000时应用加载的地址。前面带加号的数字28406是堆栈地址和加载地址相减的十进制值。你将会注意到崩溃报告下方的“Binary Images”是一个带有一些内存地址的动态库的列表。二进制的起始地址与堆栈追踪的加载地址是匹配的。

Binary Images: 0xa8000 – 0xaffff Crasher armv7 /var/mobile/Containers/Bundle/Application/956755E3-6C66-4E87-A8BC-352FD4BE3711/Crasher.app/Crasher

接下来,为了好好测量,我们将会使用file或者lipo -info来验证我们的可执行文件包含我们崩溃的架构:

file Crasher.app/Crasher

Crasher.app/Crasher: Mach-O universal binary with 2 architectures Crasher.app/Crasher (for architecture armv7): Mach-O executable arm Crasher.app/Crasher (for architecture arm64): Mach-O 64-bit executable

现在我们已经拿到我们需要的所有东西。我们将会使用atos来将我们的地址转换成调试符号。注意看我们如何提供加载地址,紧跟着的是特定的崩溃的架构的堆栈地址:

atos -arch armv7 -o Crasher.app/Crasher -l 0xa8000 0x000aeef6

main (in Crasher) (main.m:14)

就是这样。如果你有兴趣深入钻研,可以阅读Mach-O对象文件格式并检查Mach-O的一系列命令行工具,也就是otoollipo

深入阅读

更全面的文档参见:

Comments

comments powered by Disqus