- iOS Crash 流程化:一般的 Crash 日志解析方法
- TL;DR
- 一、手动解析 Crash 日志
- 1、需要的相关文件路径or获取途径
- Crash Log获取
- dSYM路径
- Symbolicatecrash的文件(已提供)路径:
- 2、UUID校验(确保文件之间彼此正确对应)
- .crash
- dSYM
- .App
- 3、.Crash文件分析过程
- 1、需要的相关文件路径or获取途径
- 二、通过Xcode查看设备崩溃信息
- 三、使用 Apple 提供的 Crash 崩溃收集服务
- 1、上报错误信息
- 2、查看崩溃次数
- 3、回到 Xcode 查看
- 查看具体崩溃信息
- 四、第三方崩溃分析工具 Bugly
- 冲突
- 补充:来自iOS知识小集
- Ref
默认 Build Settings -> Build Options -> Debug Information Format 中,置为了 DWARF,如果选为DWARF则不会产生dSYM文件,必须选择DWARF with dSYM File才会生成符号表文件。
TL;DR
1、校验Crash Log uuid
grep "ShanLinExample arm" *.crash
2、校验dSYM uuid
dwarfdump --uuid ShanLinExample.App.dSYM/Contents/Resources/DWARF/ShanLinExample
3、校验App uuid
dwarfdump --uuid Payload/ShanLinExample.app/ShanLinExample
4、将 Crash Log、dSYM、App 放在同一个文件夹下,符号化
./symbolicatecrash ShanLinExample-2016-06-14-133332.crash ShanLinExample.App.dSYM > report.log
一、手动解析 Crash 日志
1、需要的相关文件路径or获取途径
Crash Log 获取
- 将发生了Crash的手机连接电脑,打开Xcode-菜单栏
Window
-Devices and Simulators
- 打开相应的手机
View Device Logs
- 建议按时间倒序排列,选中相应App的CrashLog,右键
Export Log
,以桌面CrashReport文件夹为例拷贝到~/Desktop/CrashReport
dSYM路径
每一个打包版本都有个对应的 Archives 文件,路径为:
~/Library/Developer/Xcode/Archives
- Xcode-菜单栏
Window
-Archives
选中相应版本Archive-show in Finder
- 对选中
.xcarchive
右键显示包内容,在dSYMs
文件夹下找到#AppName#.app.dSYM
,拷贝到桌面CrashReport文件夹中
Symbolicatecrash的文件(已提供)路径:
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
Xcode不同版本间Symbolicatecrash
的位置可能会有差异,如DVTFoundation.framework部分文件名不一样。
若找不到可以通过命令来查找:
find /Applications/Xcode.App -name symbolicatecrash -type f
2、UUID校验(确保文件之间彼此正确对应)
Ps. ShanLinExample改为对应的AppName即可。
.crash
grep "ShanLinExample arm" *.crash
输出结果:
0x100054000 - 0x1008e3fff ShanLinExample arm64 <f960888bef2430e9b844732364819642> /var/containers/Bundle/Application/84FA640D-AC63-4848-9989-9C5D8FCA748A/ShanLinExample.app/ShanLinExample
dSYM
dwarfdump --uuid ShanLinExample.App.dSYM/Contents/Resources/DWARF/ShanLinExample
输出结果:
UUID: 271C3816-D14F-3FFE-93FA-D0A8F2912DF0 (armv7) ShanLinExample.App.dSYM/Contents/Resources/DWARF/ShanLinExample
UUID: F960888B-EF24-30E9-B844-732364819642 (arm64) ShanLinExample.App.dSYM/Contents/Resources/DWARF/ShanLinExample
.App
应用Archive生成的.ipa,将后缀改为.zip,解压生成Payload文件夹,里面有个与App同名文件,此时用命令行:
dwarfdump --uuid Payload/ShanLinExample.app/ShanLinExample
输出结果:
UUID: 271C3816-D14F-3FFE-93FA-D0A8F2912DF0 (armv7) Payload/ShanLinExample.app/ShanLinExample
UUID: F960888B-EF24-30E9-B844-732364819642 (arm64) Payload/ShanLinExample.app/ShanLinExample
对比UUID即可。
3、.Crash文件分析过程
将Symbolicatecrash、dSYM文件、.crash文件都放在一个文件夹中,例如~/Desktop/CrashReport
先运行:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
否则会报错:
Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 60.
输入命令:
./symbolicatecrash ShanLinExample-2016-06-14-133332.crash ShanLinExample.App.dSYM > report.log
则最终生成的report.log文件即为崩溃时的具体信息。
二、通过Xcode查看设备崩溃信息
可以将发生崩溃的设备连接Xcode,选择window-> devices -> 选择自己的手机 -> view device logs
就可以查看手机上所有的崩溃信息了。
只要手机上的应用是这台电脑安装打包的,这样的崩溃信息系统已经为我们符号化好了,只需要进去之后等一会就行。如果还是没有符号化完毕 ,可以选择文件,然后右击选择Re-Symbolicate
就可以。
如果是使用其他电脑进行的打包,可以在这里面将Crash
文件导出,自己通过命令行的方式进行解析。
三、使用 Apple 提供的 Crash 崩溃收集服务
1、上报错误信息
App上线以后苹果就会自动捕捉崩溃信息,当 App 出现 Crash 后iOS系统就会记录崩溃日志并上传到Apple的服务器。前提是需要用户同意 “与应用开发者共享”:
设置->隐私->诊断与用量->与应用开发者共享
2、查看崩溃次数
上线运行一段时间后就可以登录iTunes Connect查看。登录后点击 App 分析
--> 选择要查看的App --> 点击右下角的崩溃
,就可以根据筛选条件确定Crash的版本和日期。
确定完版本和日期后并不能在iTunes Connect中查看崩溃日志。具体信息还要回到Xcode中完成。
3、回到 Xcode 查看
查看具体崩溃信息
Xcode打开App,选择Window->Organizer,选择右边的Crashes
这里选择具体的线上版本,崩溃信息就出现了(此时需要联网)
UIKit 开头是应该是无法定位的错误,哪儿红儿哪一行就是错误信息。这里的错误实际是因为使用UIScrollview的Category进行键盘隐藏而产生的Crash。当使用中文手写键盘输入就会出现这种情况。
可定位的错误信息如下,点击箭头选择App打开就会打开Xcode,然后把当时的线程和堆栈呈现出来。
在项目中查看最好恢复到发布时候的版本。代码保持一致性。有时候项目中查看定位并不准确,但是有这些当时崩溃的线程,还是有助于我们定位错误的。
注意: 官方提供的崩溃信息并不是实时的,只能查看两天之前的崩溃信息。需要实时的可以使用第三方工具。
四、第三方崩溃分析工具 Bugly
有很多第三方工具可以进行崩溃统计分析,个人比较推荐 Bugly(不推荐友盟,网易云捕和国外的 Crashlytics 都没有用过)。它的优势在于,可以直接将崩溃信息分析出来并且做好分类和汇总。当然完整的解析崩溃需要上传dSYM
到Bugly后台,可以通过手动上传的方式,也可以按照 Bugly 的文档配置脚本进行上传。
这就是Bugly的页面,出错堆栈Bugly会帮我们解析好,并且会根据不同情况给一些解决建议。
Bugly有一个页面追踪功能,这是我认为非常有用的一个功能。这个功能会将用户在不同页面之间跳转的流程记录下来。这样对于复现bug
是很有用的,可以根据用户页面跳转推测出用户大概操作流程,根据这个流程复现bug
。
Bugly还有日报功能,可以每天汇总一篇日报,并且发到团队每个人的邮箱和微信号上。
冲突
如果多个 Crash 收集框架存在时,往往会存在冲突。
不管是对于 Signal 捕获还是 NSException 捕获都会存在 handler 覆盖的问题,正确的做法应该是先判断是否有前者已经注册了 handler,如果有则应该把这个 handler 保存下来,在自己处理完自己的 handler 之后,再把这个 handler 抛出去,供前面的注册者处理。这里给出相应的 Demo,Demo 由@zerygao提供。
typedef void (*SignalHandler)(int signo, siginfo_t *info, void *context);
static SignalHandler previousSignalHandler = NULL;
+ (void)installSignalHandler {
struct sigaction old_action;
sigaction(SIGABRT, NULL, &old_action);
if (old_action.sa_flags & SA_SIGINFO) {
previousSignalHandler = old_action.sa_sigaction;
}
LDAPMSignalRegister(SIGABRT);
// .......
}
static void LDAPMSignalRegister(int signal) {
struct sigaction action;
action.sa_sigaction = LDAPMSignalHandler;
action.sa_flags = SA_NODEFER | SA_SIGINFO;
sigemptyset(&action.sa_mask);
sigaction(signal, &action, 0);
}
static void LDAPMSignalHandler(int signal, siginfo_t* info, void* context) {
// 获取堆栈,收集堆栈
........
LDAPMClearSignalRigister();
// 处理前者注册的 handler
if (previousSignalHandler) {
previousSignalHandler(signal, info, context);
}
}
上面的是一个处理 Signal handler 冲突的大概代码思路,下面是 NSException handler 的处理思路,两者大同小异。
static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler;
static void LDAPMUncaughtExceptionHandler(NSException *exception) {
// 获取堆栈,收集堆栈
// ......
// 处理前者注册的 handler
if (previousUncaughtExceptionHandler) {
previousUncaughtExceptionHandler(exception);
}
}
+ (void)installExceptionHandler {
previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
NSSetUncaughtExceptionHandler(&LDAPMUncaughtExceptionHandler);
}
补充:来自iOS知识小集
1、自己设备上 Xcode 编译的包发生闪退:连上手机打开 Xcode,cmd+shift+2 呼出 Device 的 Window,如图1所示,然后点击 View Device Logs,然后选中对应时间段自己 app 的崩溃日志。如果此时对应的调用栈还没有符号化,可以选中日志后右键如图2所示 Re-Symbolicate Log 即可。
2、如果是打包服务器或者 Appstore 的包发生闪退:拷贝对应的包和 dSYM 到任意文件夹下,注意将 dSYM 解压以及 .ipa 里面的 .app 取出。然后按照情况1的方式处理即可,Xcode 会自动索引二进制及 dSYM。
- 如果APP是自己电脑编译生成的,Xcode会根据spotlight自动找到对应的符号文件
- 如果不是自己电脑编译生成的,只需要将.app和dSYM放入同一文件夹,然后手动生成索引,这样Xcode也能找到。在命令行中输入如下命令手动创建索引:
# mdimport ,导入文件到datastore(import file hierarchies into the metadata datastore)。
mdimport pathName
上面两种方式确保了Xcode能依据UUID找到地址对应的符号文件,这样,Xcode就能解析出崩溃日志。
3、如果拿到别的设备导出的未符号化的崩溃日志,可以将日志拖至下图所示的列表中,注意此时上面 tab 记得选 All Logs 而不是 This Device,然后参考情况2,找到崩溃日志对应的二进制包和 dSYM 文件,按照情况2处理即可。可能会遇到系统库的一些方法无法符号化的问题,只需要找到对应的设备连上电脑,让 Xcode 读取一遍该设备(同机型和系统版本的也可以)的符号表,然后再 Re-Symbolicate 一遍就行。
4、遇到线上用户崩溃,无法拿到完整崩溃日志,可以让用户到【设置->分析->分析数据】里面找到对应时间点的崩溃日志,然后截图,根据一个开源工具 dSYMTools,把崩溃栈的关键地址输入到文本框中即可解析出崩溃的那个方法,具体使用方法参考 answer-huang/dSYMTools: dSYM analyze