前言
崩溃问题修复是每一个客户端开发者治理的重难点,一般崩溃有三道程序监防:1、程序员代码编程规范化及Codereview ;2、程序员自测及测试部门同事测试;3、线上检测工具bugly、Firebase监测。崩溃修复是一个长期复杂的过程,程序员治理优化程序的过程中,同时也是提高自身能力和了解更广泛知识的过程。要想解决崩溃,首先就要学会定位线上崩溃问题,本文就总结分享了开发中定位崩溃问题的几种方法和大家做个分享。
崩溃问题定位
最重要的三个文件
- dSYM
- .crash / .ips 文件
- .app
并将三文件整理在一个文件夹中
流程:准备三文件 - > 符号化 -> 生成可直观的文件
dSYM文件获取
dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件,位于 /Users/<用户名>/Library/Developer/Xcode/Archives 目录下
一般开发者在完成一个版本后,就要迁出一个版本出来。选择真机或选择generic iOS Device
, Product -> Archive
,随后
XCode -> Window ->Organizer -> XXArchive文件 -> Show in Finder -> 显示包内容 -> dSYMs -> ***.app.dSYM
一般使用bugly线上监测,还应把该文件上传到bugly上帮助定位问题,上传脚本如下:
dsym文件和bugly文件放在同一路径下
java -jar buglyqq-upload-symbol.jar -appid *** -appkey **** -bundleid com...* -version 3.. -platform IOS -inputSymbol ..dSYM
若想上传app内引用的其他库的符号表,以方便查找是不是三方库的原因,可把..dSYM 替换成对应的三方库dsym,如:***.framework.dSYM 即可
|
.app文件获取
XCode -> Window ->Organizer -> XXArchive文件 -> Show in Finder -> 显示包内容 ->Products -> Applications -> *.app
.crash文件获取
1)手机连接Mac获取
XCode -> Window ->Devices and simulators -> view Device Logs
搜索你app的名称,即可找到真机产生的crash。
2)参与TestFight与上线共享crash
崩溃时可以直观的在XCode查看crash的代码行。
设备设置: 设置 -> 隐私 -> 与应用开发者共享。
获取报告:iTunes Connect ->Manage Your Applications -> View Details -> Crash Reports。导出即可。
定位方法
1、通过.dSYM符号表手动分析定位
下面是报错的堆栈信息:
10 **** 0x00000001052119b4 0x0000000104cd4000 + 5495220
如何分析出第7行的地址指向哪个方法呢?
终端执行如下指令
|
atos -arch arm64 -o ***.app.dSYM/Contents/Resources/DWARF/*** -l 0x0000000104cd4000
0x0000000105212c48
|
arm64:cpu架构,发生报错的APP的手机的cpu架构,可以通过手机型号去查询确认。
.app.dSYM/Contents/Resources/DWARF/:其中是APP的名字,导出符号表默认的,后面的路径是固定的,只要将替换成你的APP名字即可。(需要 cd 到符号表的文件目录下)
0x0000000104cd4000
+ 5499976:0x0000000104cd4000
表示默认Slide Address,5499976表示偏移量。
0x0000000105212c48:表示错误信息内存地址。
注意这两个地址的先后顺序。
执行结果如下:
|
MacBook`` test % atos -arch arm64 -o ***.app.dSYM/Contents/Resources/DWARF/*** -l 0x0000000104cd4000
0x0000000105212c48
-[take hold:] (socket:686)
|
得出-[socket onSocket:didReadData:withTag:]代码位置。
分析辅助工具
dSYM分析工具可以帮助定位
https://github.com/answer-huang/dSYMTools
[图片上传失败...(image-2f1457-1684396669921)]
2、通过hooper 反汇编定位
当发生Unix 信号的崩溃时不好定位问题,可借助hooper反编译来定位
10 *** 0x00000001052119b4 0x0000000104cd4000 + 5495220
由于是线上的崩溃,在拿到 xxx.xcarchive 包之后,把包里面的二进制文件拖进 hopper ,选择 Arm64 架构。接下来我们就可以分析跳转到具体的崩溃位置了,刚好崩溃的线程有包含我们的项目代码;
0x0000000104cd4000 是项目的基地址,5499976 是十进制的偏移量,而这个 0x0000000105212c48 则是崩溃地址,刚好是 0x0000000104cd4000 + 5499976 的结果。
所以,我们下面要做的就是跳转到 0x0000000105212c48 这个崩溃地址看看响应的汇编代码。
hopper -> Modify -> Change File Base Address... ,然后把基地址 0x0000000104cd4000 输入,重新 Rebase 一下。
hopper -> Navigate -> Go to Address or Symbol... ,输入 0x0000000105212c48 这个地址,然后 go 到对应的崩溃地址
通过上述步骤可以定位大概代码位置,然后根据业务推断问题出错点,再结合修改即可了。
常见崩溃问题
1)Mach 异常
最底层的内核级异常。用户态的开发者可以直接通过Mach API设置thread,task,host的异常端口,来捕获Mach异常。
新建一个监控线程,在监控线程中监听 Mach 异常并处理异常信息。主要的步奏如下图:
2)Unix信号
又称BSD 信号,如果开发者没有捕获Mach异常,则会被host层的方法ux_exception()将异常转换为对应的UNIX信号,并通过方法threadsignal()将信号投递到出错线程。可以通过方法signal(x, SignalHandler)来捕获signal。
Unix Signal 其实是由 Mach port 抛出的信号转化的,大致信号:
SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。SIGABRT
调用abort函数生成的信号。
SIGABRT is a BSD signal sent by an application to itself when an NSException or obj_exception_throw is not caught.
SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。-
SIGSYS
非法的系统调用。
-
SIGTRAP
由断点指令或其它 trap 指令产生. 由d ebugger 使用。
-
SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
3)应用级NSException
应用级异常,它是未被捕获的Objective-C异常,导致程序向自身发送了SIGABRT信号而崩溃,是app自己可控的,对于未捕获的Objective-C异常,是可以通过try catch来捕获的,或者通过NSSetUncaughtExceptionHandler()机制来捕获。
非主线程刷新UI
NSInvalidArgumentException
非法参数异常(NSInvalidArgumentException)是 Objective – C 代码最常出现的错误,所以平时在写代码的时候,需要多加注意,加强对参数的检查,避免传入非法参数导致异常,其中尤以nil参数为甚。NSRangeException
越界异常(NSRangeException)也是比较常出现的异常。NSGenericException
NSGenericException这个异常最容易出现在foreach操作中,在for in循环中如果修改所遍历的数组,无论你是add或remove,都会出错 “for in”,它的内部遍历使用了类似 Iterator进行迭代遍历,一旦元素变动,之前的元素全部被失效,所以在foreach的循环当中,最好不要去进行元素的修改动作,若需要修改,循环改为for遍历,由于内部机制不同,不会产生修改后结果失效的问题。NSInternalInconsistencyException
不一致导致出现的异常
比如NSDictionary当做NSMutableDictionary来使用,从他们内部的机理来说,就会产生一些错误
NSMutableDictionary *info = method return to NSDictionary type;
[info setObject:@“sxm” forKey:@”name”];
比如xib界面使用或者约束设置不当NSFileHandleOperationException
处理文件时的一些异常,最常见的还是存储空间不足的问题,比如应用频繁的保存文档,缓存资料或者处理比较大的数据:
所以在文件处理里,需要考虑到手机存储空间的问题。NSMallocException
这也是内存不足的问题,无法分配足够的内存空间
此外还有KVO Crash
移除未注册的观察者
重复移除观察者
添加了观察者但是没有实现-observeValueForKeyPath:ofObject:change:context:
方法
添加移除keypath=nil
添加移除observer=nilunrecognized selector send to instance
总结
本文也只是对崩溃问题做了浅显的总结和解决经验的分享,崩溃治理是开发者长期处理的过程,不仅要解决现有问题,还要防范崩溃问题出现并且做好兜底策略;在治理过程中对个人底层知识理解也有较大帮助。
参考:
https://juejin.cn/post/6937619147505270820
https://www.jianshu.com/p/3f6775c02257
https://www.cnblogs.com/ciml/p/7422872.html
https://zhuanlan.zhihu.com/p/420032439