1.获取崩溃日志(Crash log)
1.1获取开发环境的Log
方法a.
方法b:在~/Library/Logs/CrashReporter/MobileDevice/<DEVICE_NAME>
1.2获取发布环境的Log
注意:app已经上架AppStore
作为开发者,你想要获取到你的用户的崩溃日志的话就得通过 iTunes Connect ,在 iTunes Connect 上的 Manage Your Applications -> View Details -> Crash Reports
这种方式有个前提,就是用户设备同意上传相关信息,打开了诊断与用量这个选项设置->隐私->诊断与用量
2.
Crash Log的符号化
2.1认识Log文件
2.2解析Log文件
目前有三种方法:
a.使用Xocde自带可视化解析功能
b.使用Xcode提供的解析命令symbolicatecrash
c.使用系统命令atos
2.2.1 XCode自带可视化解析功能(适合开发环境下做符号还原)
要使用Xcode符号化 crash log,你需要下面所列的3个文件:
- crash报告(.crash文件)
- 符号文件 (.dsymb文件)
- 应用程序文件 (appName.app文件,把IPA文件后缀改为zip,然后解压,Payload目录下的appName.app文件), 这里的appName是你的应用程序的名称。
这里先介绍一下dsymb文件:
DWARF(DebuggingWith Arbitrary Record Formats),是ELF和Mach-O等文件格式中用来存储和处理调试信息的标准格式,.dSYM中真正保存符号表数据的是DWARF文件。DWARF中不同的数据都保存在相应的section(节)中,ELF文件里所有的section名称都以".debug_"开头,Mach-O中关于section的命名和ELF稍有区别,把名称前的.换成了_,例如.debug_info变成了_debug_info
符号表文件.dSYM实际上是从Mach-O文件中抽取调试信息而得到的文件目录,实际用于保存调试信息的文件符号表文件目录中的DWARF文件
.dSYM文件的生成方式有两种:
- Xcode自动生成,在与AppName同一目录下
- 使用dsymutil命令从Mach-O中提取
dsymutil /Users/<username>/Library/Developer/Xcode/DerivedData/Test-bympsyamgkietlhflbioakqjyemv/Build/Products/Release-iphoneos/AppName.app/Test -o /Users/<user-name>/Desktop/myDevice.dSYM/
保存在DAWARF中的信息是高度压缩的,可以通过dwarfdump命令从中提取出可读信息。
把这3个文件放到同一个目录下,打开Xcode的Window菜单下的organizer,然后点击Devices tab,然后选中左边的Device Logs。
然后把.crash文件拖到Device Logs或者选择下面的import导入.crash文件。
这样你就可以看到crash的详细log
2.2.2 使用Xcode提供的解析命令symbolicatecrash
首先找到symbolicatecrash可执行文件,执行以下命令:
sudo find / -name "symbolicatecrash"
得到结果:
Password:
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
/Applications/Xcode7.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
这里是因为本地安装了多个版本的Xcode,所以找到了多个路径,具体使用哪一个,要看App的编译版本
将对应版本的symbolicatecrash命令拷贝到系统命令文件夹下,执行以下命令:
cp /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash /usr/local/bin
命令使用crash文件,应用的dSYMcrash文件中崩溃地址对应的方法符号表, 在处理之前,请依然将“.app“, “.dSYM”和 ".crash"文件放到同一个目录下,然后执行以下命令:
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
symbolicatecrash Test.crash Test.app.dSYM/ > crash.log
或者(symbolicatecrash) -d Test.app.dSYM/ -o
结果就会输出在crash.log,如图:
2.2.3 使用系统命令atos
如果你有多个“.ipa”文件,多个".dSYMB"文件,你并不太确定到底“dSYMB”文件对应哪个".ipa"文件,那么,这个方法就非常适合你。
特别当你的应用发布到多个渠道的时候,你需要对不同渠道的crash文件,写一个自动化的分析脚本的时候,这个方法就极其有用。
UUID:
每一个可执行程序都有一个build UUID来唯一标识。Crash日志包含发生crash的这个应用(app)的 build UUID以及crash发生的时候,应用加载的所有库文件的[build UUID]。
那如何知道crash文件的UUID呢?
可以用:
grep "appName armv" *crash
可以得到类似如下的结果:
0x10003c000-0x100043fff AppName arm64 <b282de86d259354d8965dae67f535817> /var/mobile/Containers/Bundle/Application/5B6BF767-65A2-4293-872B-11418E1C8113/AppName.app/AppName
(请注意这里的0x10003c000,是模块的加载地址,后面用atos的时候会用到)
如何找到App的UUID
进入App所在的目录,然后执行命令:
xcrun dwarfdump --uuid appName.app/appName
结果如下:
UUID: 2915A551-2818-3288-981B-7F5E1933FF64 (armv7) AppName.app/AppName
UUID: B282DE86-D259-354D-8965-DAE67F535817 (arm64) AppName.app/AppName
这个app有2个UUID,表明它是一个fat binnary,它能利用最新硬件的特性,又能兼容老版本的设备。对比上面crash文件和app文件的UUID,发现它们是匹配的(这一点很重要)
用atos命令来符号化某个特定模块加载地址
命令是:
atos [-o AppName.app/AppName] [-l loadAddress] [-arch architecture]
下面3种都可以:
// atos命令使用指定模块(-o参数) 模块加载地址(-l参数) 函数栈地址来解析出符号
atos -o UIKit -arch arm64 -l 0x18b4cc000 0x100040584(还原系统符号)
atos -o UIKit -arch arm64 -l 0x18b4cc000 0x18b4dcc84(还原系统符号)
atos -o dysm文件路径 -l 模块load地址 -arch cpu指令集种类 调用方法的地址(还原App符号)
例如执行命令:
atos -o Test.app.dSYM/Contents/Resources/DWARF/Test -l 0x10003c000 0x100040884
得到结果:
注意,关于系统模块,需要根据运行环境,指定在Xcode安装的目录的对应的iOS版本的库文件,例如UIKit:
3.
总结
以上三种方法适用于不同的场景,需要根据情况使用
4.补充
Exception Type
1)EXC_BAD_ACCESS
此类型的Excpetion是我们最长碰到的Crash,通常用于访问了不改访问的内存导致。一般EXC_BAD_ACCESS后面的"()"还会带有补充信息。
SIGSEGV: 通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。
SIGABRT: 收到Abort信号退出,通常Foundation库中的容器为了保护状态正常会做一些检测,例如插入nil到数组中等会遇到此类错误。
SEGV:(Segmentation Violation)代表无效内存地址,比如空指针,未初始化指针,栈溢出等;
SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)
SIGILL:尝试执行非法的指令,可能不被识别或者没有权限
2)EXC_BAD_INSTRUCTION
此类异常通常由于线程执行非法指令导致
3)EXC_ARITHMETIC
除零错误会抛出此类异常
Exception Code
0xbaaaaaad 此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发
0xbad22222 当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序
0x8badf00d 程序启动或者恢复时间过长被watch dog终止
0xc00010ff 程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止
0xdead10cc 程序退到后台时还占用系统资源,如通讯录被系统终止
0xdeadfa11 前面也提到过,程序无响应用户强制关闭
获取崩溃代码所在文件以及行号
上述介绍dsym文件的时候,所述的那些section中,定位CrashLog只需要用到.debug_info和.debug_line。由于解析出来的数据量较大,为了方便查看,就将其保存在文本中。两个section的数据提取方式如下:
_debug_info:
dwarfdump -e --debug-info YourPath/YourApp.dSYM/Contents/Resources/DWARF > info-e.txt
_debug_line:
dwarfdump -e --debug-line YourPath/YourApp.dSYM/Contents/Resources/DWARF > line-e.txt
(命令中的-e可以增加解析结果的可读性;其它section的提取方式类似)
举例:例如我们的文件是:
那看看在info-e.txt文件的信息:
0x00034ed7: function [113] *
low pc( 0x0000000100004848 ) // 方法在栈中的低地址
high pc( 0x0000000100004894 ) // 方法在栈中的高地址
frame base( reg29 )
object pointer( {0x00034ef6} )
name( "-[Model showModelName]" ) // 方法名
decl file( "/Users/uwei/WorkSpace/Demo/Objective-C/iOS/Test/Test/Model.m" ) // 方法所在文件
decl line( 14 ) // 方法在文件中的起始行号
prototyped( 0x01 )
APPLE optimized( 0x01 )
那看看在line-e.txt文件的信息:
Address Line File
------------------ ------ ------------------------------
0x0000000100004848 14 /Users/uwei/WorkSpace/Demo/Objective-C/iOS/Test/Test/Model.m
0x0000000100004854 15
0x000000010000486c 17
0x0000000100004884 18
这里的每一个行号都对应着代码在编译执行的时候的内存中的地址,可以从CrashLog中找到对应的地址信息,就可以对应上代码在文件中的行号