iOS 崩溃常见类型 以及 日志收集处理(整理自互联网)

一、崩溃的类型

APP的崩溃可以分为两类:信号可捕捉崩溃 和 信号不可捕捉崩溃。

信号可捕捉的崩溃

  • 数组越界:取数据时候索引越界,APP发生崩溃。给数组添加nil会崩溃。
  • 多线程问题:多个线程进行数据的存取,可能会崩溃。例如有一个线程在置空数据的同时另一个线程在读取数据。
  • 野指针问题:指针指向一个已删除的对象访问内存区域时,会出现野指针崩溃。野指针问题是导致 App 崩溃的最常见,也是最难定位的一种情况。
  • NSNotification线程问题:NSNotification 有很多种线程实现方式,同步、异步、聚合,所以不恰当的线程发送和接收会出现崩溃问题。
  • KVO问题:‘If your app targets iOS 9.0 and later or OS X v10.11 and later, you don't need to unregister an observer in its deallocation method。’ 在9.0之前需要手动remove 观察者,如果没有移除会出现观察者崩溃情况。

信号不可捕捉的崩溃

  • 后台任务超时
  • App超过系统限制的内存大小被杀死
  • 主线程卡顿被杀死

二、崩溃日志 

1、什么情况下会产生崩溃日志?

两种主要情况会产生崩溃日志:
1.应用违反操作系统规则。
2.应用中有Bug。
违反iOS规则包括在启动、恢复、挂起、退出时watchdog超时、用户强制退出和低内存终止。让我们详细了解一下吧。
Watchdog 超时机制
从iOS 4.x开始,退出应用时,应用不会立即终止,而是退到后台。但是,如果你的应用响应不够快,操作系统有可能会终止你的应用,并产生一个崩溃日志。这些事件与下列UIApplicationDelegate方法相对应:
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:
上面所有这些方法,应用只有有限的时间去完成处理。如果花费时间太长,操作系统将终止应用。
注意: 如果你没有把需要花费时间比较长的操作(如网络访问)放在后台线程上就很容易发生这种情况。
 
2、如何获取崩溃日志就不说了, 下面我们讲获取到了崩溃日志怎么看?
通过 symbolicatecrash 工具来解析崩溃日志。
你可以通过以下命令找到本机的 symbolicatecrash 工具位置
find /Applications/Xcode.app -name symbolicatecrash -type f

可以把它拷贝出来, 然后通过以下命令来解析崩溃日志。

./symbolicatecrash ./xxx.crash ./xxx.app.dSYM > symbol.crash

3、解析符号化后崩溃报告

 1、头部关键信息

Incident Identifier: 一个唯一的标识. 不会存在共用一个标识的崩溃报告.
CrashReporter Key:是与设备标识相对应的唯一键值。虽然它不是真正的设备标识符,但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不同的CrashReport值,说明这不是一个普遍的问题,只发生在一个或少数几个设备上。
Process: 是应用名称。中括号里面的数字是闪退时应用的进程ID。
Version: 崩溃进程的版本号. 这个值包含在 CFBundleVersion and CFBundleVersionString中.
Code Type: 崩溃日志所在设备的架构. 会是ARM-64, ARM, x86-64, or x86中的一个.
OS Version: 崩溃发生时的系统版本

2、异常信息中的关键字段

Exception Codes: 处理器的具体信息有关的异常编码成一个或多个64位进制数。通常情况下,这个区域不会被呈现,因为将异常代码解析成人们可以看懂的描述是在其它区域进行的。
Exception Subtype: 供人们可读的异常代码的名字
Exception Message: 从异常代码中提取的额外的可供人们阅读的信息.
Exception Note: 不是特定于一个异常类型的额外信息.如果这个区域包含SIMULATED (这不是一个崩溃)然后进程没有崩溃,但是被watchdog杀掉了
Termination Reason: 当一个进程被终止的时的原因。
Triggered by Thread: 异常所在的线程.

3、其他常见的异常

Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
进程试图访问无效的内存,或试图以内存的保护级别所不允许的方式去访问内存(例如,写入到只读存储器)。异常类型字段(Exception Subtype)包含一个kern_return_t描述错误,和错误的访问的内存地址。这里是调试一个Bad Memory Access的一些小技巧:
(1).如果objc_msgSend或者objc_release在回溯(Backtraces)的顶部附近,这个进程可能是尝试给一个释放的对象发送消息。你应该用Zombies instrument(调试僵尸对象的工具)来更好的理解这个崩溃。

(2).如果gpus_ReturnNotPermittedKillClient在回溯的顶部附近,这个进程是由于在后台尝试用OpenGL ES 或者 Metal来渲染,而被杀掉的。See QA1766: How to fix OpenGL ES application crashes when moving to the background.

(3).用 Address Sanitizer (xcode7引入的新特性)来跑程序。
The address sanitizer adds additional instrumentation around memory access in your compiled code. As your application runs, Xcode will alert you if memory is accessed in a way that could lead to a crash.

Abnormal Exit (异常退出)[EXC_CRASH // SIGABRT]
进程异常退出。该异常类型崩溃最常见的原因是未捕获的Objective-C和C++异常和调用abort()。
如果他们需要太多的时间来初始化,程序将被终止,因为触发watchdog。如果是因为启动的时候被挂起,所产生的崩溃报告异常类型(Exception Subtype)将是launch_hang。

Trace Trap (追踪捕获)[EXC_BREAKPOINT // SIGTRAP]
类似于异常退出,这个异常是为了给附加的调试器中断的过程的机会在其执行一个特定的点。您可以通过主动调用__builtin_trap()函数引发此异常使用,如果没有调试器连接,进程将被终止并生成崩溃报告。

Illegal Instruction(非法指令) [EXC_BAD_INSTRUCTION // SIGILL]
进程试图执行非法或未定义指令。这个进程可能试图通过一个配置错误的函数指针,跳到一个无效的地址。

Resource Limit [EXC_RESOURCE]
这个进程超出了资源消耗的限制。这是一个从操作系统通知,进程是使用太多的资源。这虽然不是崩溃但也会生成崩溃日志。

其它的异常信息

0x8badf00d: 读做 “ate bad food”! (把数字换成字母,是不是很像 :p)该编码表示应用是因为发生watchdog超时而被iOS终止的。通常是应用花费太多时间而无法启动、终止或响应用系统事件。

0xbad22222: 该编码表示 VoIP 应用因为过于频繁重启而被终止。
0xdead10cc: 读做 “dead lock”!该代码表明应用因为在后台运行时占用系统资源,如通讯录数据库不释放而被终止 。
0xdeadfa11: 读做 “dead fall”! 该代码表示应用是被用户强制退出的。根据苹果文档, 强制退出发生在用户长按开关按钮直到出现 “滑动来关机”, 然后长按 Home按钮。强制退出将产生 包含0xdeadfa11 异常编码的崩溃日志, 因为大多数是强制退出是因为应用阻塞了界面。

4、线程回溯 如以下 线程帧 记录了 这些信息

帧编号—— 此处是2。(数子从大到小为发生的顺序)
二进制库的名称 ——此处是 XYZLib.
调用方法的地址 ——此处是 0x34648e88.
第四列分为两个子列,一个基本地址和一个偏移量。此处是0×83000 + 8740, 第一个数字指向文件,第二个数字指向文件中的代码行。

4、崩溃日志中常见的信号

Defined in header <signal.h>

#define SIGTERM /*implementation defined*/
#define SIGSEGV /*implementation defined*/
#define SIGINT /*implementation defined*/
#define SIGILL /*implementation defined*/
#define SIGABRT /*implementation defined*/
#define SIGFPE /*implementation defined*/
SIGTERM
程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。
iOS中一般不会处理到这个信号

SIGSEGV
invalid memory access (segmentation fault)
无效的内存地址引用信号(常见的野指针访问)
非ARC模式下,iOS中经常会出现在 Delegate对象野指针访问
ARC模式下,iOS经常会出现在Block代码块内 强持有可能释放的对象
 
SIGINT
external interrupt, usually initiated by the user
通常由用户输入的整型中断信号
在iOS中一般不会处理到该信号
 
SIGILL
invalid program image, such as invalid instruction
不管在任何情况下得杀死进程的信号
由于iOS应用程序平台的限制,在iOS APP内禁止kill掉进程,所以一般不会处理
 
SIGABRT
abnormal termination condition, as is e.g. initiated by abort()
通常由于异常引起的中断信号,异常发生时系统会调用abort()函数发出该信号
iOS平台,一种是由于方法调用错误(调用了不能调用的方法)
iOS平台,一种是由于数组访问越界的问题
 
SIGFPE
erroneous arithmetic operation such as divide by zero
浮点数异常的信号通知
一般是由于 除数为0引起的

5、自定义代码来获取崩溃日志信息

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSSetUncaughtExceptionHandler(&caughtExceptionHandler);
    /*
     Changes the top-level error handler.   Sets the top-level error-handling function where you can perform last-minute logging before the program terminates */ return YES; } void caughtExceptionHandler(NSException *exception){ /** * 获取异常崩溃信息 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========异常错误报告========\\nname:%@\\nreason:\\n%@\\ncallStackSymbols:\\n%@",name,reason,[callStack componentsJoinedByString:@"\\n"]]; //把异常崩溃信息发送至开发者邮件 NSMutableString *mailUrl = [NSMutableString string]; [mailUrl appendString:@"mailto:xxx@qq.com"]; [mailUrl appendString:@"?subject=程序异常崩溃信息,请配合发送异常报告,谢谢合作!"]; [mailUrl appendFormat:@"&body=%@", content]; // 打开地址 NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]]; }

三、野指针导致崩溃

1、对象释放后内存没被改动过,原来的内存保存完好,可能不Crash或者出现逻辑错误(随机Crash)。
2、对象释放后内存没被改动过,但是它自己析构的时候已经删掉某些必要的东西,可能不Crash、Crash在访问依赖的对象比如类成员上、出现逻辑错误(随机Crash)。
3、对象释放后内存被改动过,写上了不可访问的数据,直接就出错了很可能Crash在objc_msgSend上面(必现Crash,常见)。
4、对象释放后内存被改动过,写上了可以访问的数据,可能不Crash、出现逻辑错误、间接访问到不可访问的数据(随机Crash)。
5、对象释放后内存被改动过,写上了可以访问的数据,但是再次访问的时候执行的代码把别的数据写坏了,遇到这种Crash只能哭了(随机Crash,难度大,概率低)!!
6、对象释放后再次release(几乎是必现Crash,但也有例外,很常见)。

 

posted @ 2021-01-20 23:03  lesten  阅读(2486)  评论(0编辑  收藏  举报