前言
我们一般使用友盟等第三方平台来收集程序崩溃信息,但是一般情况下收集到的崩溃信息是我们无法看懂的,都是一些16进制的内存地址,无法准确定位到出错的代码。通常像这样的情况是由于我们没有上传符号文件到第三方平台,所以无法解析出具体的代码。
.dSYM为后缀的文件就是符号文件,里面存储着程序的符号表,也就是函数和内存地址的对应关系,有了这个符号文件,我们就可以通过崩溃信息中的内存地址解析出具体的出错代码,每次打包App生成的符号文件都可能不一样,所以说崩溃发生在哪个App版本就使用哪个版本的符号文件解析崩溃代码。
因为打包好之后的App安装包和符号文件是分开来的(出于安装包大小的考量),所以App包中没有符号文件,因此线上版本的App产生崩溃时只能得到崩溃地址,具体的解析工作还需拿到对应的dSYM符号文件之后才能进行。本地开发时符号文件就在本地,所以出现崩溃时Xcode打印出来的崩溃信息能看到具体的崩溃代码。
-
dSYM符号文件的位置
Xcode -> window -> Organizer -> 选择对应的包然后show in finder -> 显示包内容 -> dSYMs文件夹里存放的文件就是符号文件
-
自己收集崩溃信息
通过自己收集崩溃信息,我们能够理解更多问题
1.首先在AppDelegate文件的didFinishLaunchingWithOptions方法中设置崩溃回调函数,程序出现崩溃时会调用该函数,我们在该函数里面收集崩溃信息
2.实现该函数handleException()
3.获取程序slide偏移值 (现在的App在运行到内存以后,会在前面随机空出一部分内存,而slide值就是这个随机空白段的长度,由于我们打包生成的符号文件是不加这个空白段的,所以我们收集到的崩溃地址需要减去slide值,才能在符号文件中解析出正确的信息)
崩溃的收集代码如下
#import "AppDelegate.h"
#import <mach-o/dyld.h> //需要导入这个头文件 使用获取偏移值的api
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//1、设置异常回调函数
NSSetUncaughtExceptionHandler(handleException);
return YES;
}
//重要,用于获取偏移值
long calculate(void) {
long s = 0;
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
if (_dyld_get_image_header(i)->filetype == MH_EXECUTE) {
long slide = _dyld_get_image_vmaddr_slide(i);
s = slide;
break;
}
}
return s;
}
/**
* 2、异常回调函数实现
*/
void handleException(NSException *exception){
//2.设备型号
//3.系统版本
NSString *OSVersion = [NSString stringWithFormat:@"iOS %@",[UIDevice currentDevice].systemVersion];
//4.崩溃名
NSString *exceptionName = [exception name];
//5.崩溃原因
NSString *reason = [exception reason];
//6.函数调用栈信息(重要)
NSArray *array = [exception callStackSymbols]; //调用栈信息 拼接成字符串
NSString *callStackStr = @"";
for (NSString *str in array) {
callStackStr = [NSString stringWithFormat:@"%@\n%@",callStackStr,str];
}
//7.崩溃前画面app画面截图(可选,没有太多参考价值)
//8.崩溃时间
//9.app版本
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSString *AppVersion = [infoDict objectForKey:@"CFBundleShortVersionString"];
//10.偏移值(重要)
NSString *slide = [NSString stringWithFormat:@"%ld",calculate()];
//11.上传崩溃信息到自己的后台
...
exit(0);// 需要手动杀死程序
}
-
分析崩溃信息
上面就是我获得的函数调用栈callStackSymbols信息,第一列为调用顺序,第二列就是对应的库,第三列是对应方法函数对应的内存地址。从第二列可以看出,大部分都是系统库调用,这是我们无法修改的,所以不用管。我们只关注项目名所对应的那几行,那才是我们自己编写的代码,所以只需要关注5、6、22行的信息。由于栈的调用顺序,我们只需要分析最根层的方法即可,因为那才是崩溃发生的根源,也就是说只需要分析第5行。
由上面的崩溃信息可以看到第5行的内存地址是0x00000001012f9d90,我们可以通过命令行去dSYM符号文件中寻找该内存地址所对应的方法,这样就可以分析发生崩溃的方法了。
但是现在的iOS系统增加了一些安全措施,仅仅通过这个地址我们还无法从dSYM文件中解析出有用信息,因为每次运行程序时,系统都会在程序内存前面加上一段随机的空白段,所以我们需要用当前得到的地址0x00000001012f9d90减去随机空白段的长度才能得到最终的地址,只有这个地址才可以从dSYM文件中解析出正确的方法信息。这个偏移值我们可以通过系统的api获取,在上面的崩溃收集方法里已经收集了slide偏移值。
-
解析方法
将第5行需要解析的地址减去偏移值slide得到最终地址(地址是16进制表示的,而偏移值是十进制的,注意转换)
1️⃣使用命令行解析
- 打开终端,cd到dSYM文件所在的目录
- 在终端运行如下命令即可打印显示崩溃所在的类、方法和行数信息
$atos -o 你的项目名.app.dSYM/Contents/Resources/DWARF/你的项目名 最终地址
2️⃣使用DSYMTools工具解析
参考文章
没有dsym符号文件如何解析崩溃
用命令和.dSYM 文件查找错误日志
偏移值的获取和符号地址的计算
dSYMTools
iOS自己捕获异常定位错误代码