iOS crash 问题分析汇总

本文深入探讨iOS应用中常见的Crash原因,包括调用悬浮指针、数组越界、未实现方法调用等问题,提供了详尽的分析工具和解决策略,如使用符号表解析崩溃日志、启用调试特性检测野指针等。

在这里插入图片描述

iOS crash 问题分析

S crash 问题分析)

iOS crash 常用分析工具

IOS 崩溃日志
  • xcode中查看崩溃信息

xcode->Window->Organizer->Crashes

在这里插入图片描述

  • 根据符号表来监测奔溃位置

什么是符号表

符号表就是指在Xcode项目编译后,在编译生成的二进制文件.app的同级目录下生成的同名的.dSYM文件。
.dSYM文件其实是一个目录,在子目录中包含了一个16进制的保存函数地址映射信息的中转文件,所有Debug的symbols都在这个文件中(包括文件名、函数名、行号等),所以也称之为调试符号信息文件。

符号表有什么用

符号表就是用来符号化 crash log(崩溃日志)。crash log中有一些方法16进制的内存地址等,通过符号表就能找到对应的能够直观看到的方法名之类。

如何得到.dsYM文件

我们在Archive的时候会生成.xcarchive文件,然后显示包内容就能够在里面找到.dsYM文件和.app文件。

如何使用.dsYM

  1. 友盟.dsYM分析

如果是使用友盟的话,我们能在错误列表里看到一些错误,然后可以导出奔溃信息,导出的文件为.csv文件。友盟有一个分析工具,使用那个工具可以看到一些错误的函数,行号等。但是很容易分析失败,不知道为什么?
注意:使用的时候要确保你的.xcarchive在 ~/Library/Developer/Xcode/或该路径的子目录下。
.xcarchive里的.dsYM文件和.app文件是有对应的UUID的。然后你的错误详情里也是有UUID,只有当UUID相等时才能分析对。
我犯的错误:因为我们是两个人开发,Archive的时候都是在另一个人的电脑上Archive的,所以我的电脑里根本没有对应的.xcarchive文件。所以我在我电脑上用友盟的分析工具分析是时候是监测不出来错误的。

  1. 第三方小工具.dsYM分析

或者自己找到.xcarchive文件和错误内存地址(友盟错误详情里标绿色的为错误内存地址)。然后通过一个小应用来分析出对应的函数。应用下载地址,具体可参考文章dSYM 文件分析工具
下图是我友盟里的错误信息,可以分析的内存地址就是标绿的地方,图中zhefengle就是你的app名,这部分后面的地址就是可以解析符号化的地址:

在这里插入图片描述

下图是分析工具分析上面的错误内存地址:

在这里插入图片描述

  1. 命令行工具symbolicatecrash

symbolicatecrash是xcode的一个符号化crash log的命令行工具。使用方法也就是导出.crash文件(crash log)和找到.dsYM文件,然后进行分析。
如何使用查看[iOS]使用symbolicatecrash分析crash文件: 点击此处查看symbolicatecrash分析crash文件

  1. 还有命令行工具atos

如果你有多个“.ipa”文件,多个".dSYMB"文件,你并不太确定到底“dSYM”文件对应哪个".ipa"文件,那么,这个方法就非常适合你。
特别当你的应用发布到多个渠道的时候,你需要对不同渠道的crash文件,写一个自动化的分析脚本的时候,这个方法就极其有用。
简单使用方法命令行工具atos: 点击此处查看工具atos

  • 奔溃日志分析

参考 iOS应用崩溃日志分析里面有很详细的分析介绍。

在这里插入图片描述

崩溃日志
以上是一个完整的崩溃日志。其实友盟错误详情里的就是上图的第4部分。

如何得到崩溃日志

  1. 把设备连上电脑,得到自己设备的崩溃日志

崩溃日志可以从xcode里打开Devices看到对应手机的一些奔溃信息。点击下图的View Device Logs就能看到崩溃日志。

在这里插入图片描述

  1. 使用第三方崩溃管理工具

友盟听云腾讯bugly

  1. 自己截取崩溃日志
    自己写入代码,然后截取到崩溃日志,把崩溃日志发送到开发者邮箱里。
    iOS Crash(崩溃)调试技巧这篇文章中有介绍如何截取崩溃日志并发送到邮箱。

分析崩溃日志

崩溃日志中的(3)异常

Exception Type异常类型
通常包含1.7中的Signal信号和EXC_BAD_ACCESS。

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

0xbad22222: 该编码表示 VoIP 应用因为过于频繁重启而被终止。

0xdead10cc: 读做 “dead lock”!该代码表明应用因为在后台运行时占用系统资源,如通讯录数据库不释放而被终止 。

0xdeadfa11: 读做 “dead fall”! 该代码表示应用是被用户强制退出的。根据苹果文档, 强制退出发生在用户长按开关按钮直到出现 “滑动来关机”, 然后长按 Home按钮。强制退出将产生 包含0xdeadfa11 异常编码的崩溃日志, 因为大多数是强制退出是因为应用阻塞了界面。

崩溃日志中的(4)线程回溯

这部分提供应用中所有线程的回溯日志。 回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单。看下面这行日志:
在这里插入图片描述

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

  • 野指针分析方法(Enable Malloc Scribble)

因为野指针的原因发生崩溃是常常出现的事,而且比较随机。关于一些原因及概念后面我们会讲到。所以我们要提高野指针的崩溃率好来帮我们快速找到有问题的代码。

对象释放后只有出现被随机填入的数据是不可访问的时候才会必现Crash。

这个地方我们可以做一下手脚,把这一随机的过程变成不随机的过程。对象释放后在内存上填上不可访问的数据,其实这种技术其实一直都有,xcode的Enable Scribble就是这个作用。

在这里插入图片描述

更加详细的介绍可以参考:如何定位Obj-C野指针随机Crash。

DSCrashDemo这个demo:点击下载 里有上面这篇文章里写的关于提高野指针崩溃率的例子。

  • 僵尸模式(NSZombieEnabled)

启用了NSZombieEnabled的话,它会用一个僵尸来替换默认的dealloc实现,也就是在引用计数降到0时,该僵尸实现会将该对象转换成僵尸对象。僵尸对象的作用是在你向它发送消息时,它会显示一段日志并自动跳入调试器。
所以当启用NSZombieEnabled时,一个错误的内存访问就会变成一条无法识别的消息发送给僵尸对象。僵尸对象会显示接受到得信息,然后跳入调试器,这样你就可以查看到底是哪里出了问题。
所以这时一般崩溃的原因是:调用了已经释放的内存空间,或者说重复释放了某个地址空间。

如何找出问题

  1. NSZombieEnabled

打开NSZombieEnabled之后,如果遇到对应的崩溃类型既调用了已经释放的内存空间,或者说重复释放了某个地址空间。那么就能在GDB中看到对应的输出信息。
比如会出现如下这样的问题:
[__NSArrayM addObject:]: message sent to deallocated instance 0x7179910

  1. MallocStackLoggingNoCompact

如果崩溃是发生在当前调用栈,通过上面的做法,系统就会把崩溃原因定位到具体代码中。但是,如果崩溃不在当前调用栈,系统就仅仅只能把崩溃地址告诉我们,而没办法定位到具体代码,这样我们也没法去修改错误。这时就可以修改scheme,让xcode记录每个地址alloc的历史,这样我们就可以用命令把这个地址还原出来。
如图:(跟设置NSZombieEnabled一样,添加MallocStackLoggingNoCompact,并且设置为YES)

在这里插入图片描述

这样,当出现崩溃原因是message sent to deallocated instance 0x7179910,我们可以使用以下命令,把内存地址还原:

(gdb) nfo malloc-history 0x7179910

也可以使用下面的命令
(gdb) shell malloc_history {pid/partial-process-name} {address}

这篇文章中有介绍MallocStackLoggingNoCompact的使用:点击此处查看

还有官方文档Enabling the Malloc Debugging Features介绍了类似NSZombieEnabled和MallocStackLoggingNoCompact这类的环境变量的作用。

TODO:翻译Enabling the Malloc Debugging Features这篇文章,写对应的demo测试这类变量设置后如何找出内存出错问题。

  • Enable Address Sanitizer(地址消毒剂)

在这里插入图片描述

设置这个参数后就能看到一些更详细的错误信息提示,甚至会有内存使用情况的展示。

在这里插入图片描述

C语言是一门危险的语言,内存安全是一个主要的问题。C语言中根本没有内存安全可言。像下面的代码,会被正常的编译,而且可能正常运行:
char *ptr = malloc(5);
ptr[12] = 0;
对于内存安全的验证已经有一些解决方案了。如Clang的静态代码分析,可以从代码中查找特定类型的内存安全问题。如Valgrind之类的程序可以在运行时检测到不安全的内存访问。

Address Sanitizer是另外一种解决方案。它使用了一种新的方法,有利有弊。但仍不失为一个查找代码问题的有力工具。

这类工具的理论依据是:访问内存时,通过比较访问的内存和程序实际分配的内存,验证内存访问的有效性,从而在bug发生时就检测到它们,而不会等到副作用产生时才有所察觉。

malloc函数总是最少分配16个字节。为了储存针对标准malloc的内存的保护,需要分配内存到16字节的范围内,因此,若分配的内存大小不是16字节的整数倍,余出的几个字节将不受保护。

Address Sanitizer会追踪受限内存,使用了一种简单但是很巧妙的方法:它在进程的内存空间上保存了一个固定的区域,叫做“影子内存区”。用内存消毒剂的术语来说,一个被标记为受限的内存被称作“中毒”内存。“影子内存区”会记录哪些内存字节是中毒的。通过一个简单的公式,可以将进程中的内存空间映射到“影子内存区”中,即:每8字节的正常内存块映射到一个字节的影子内存上。在影子内存上,会跟踪这8字节的“中毒状态”。

Address Sanitizer这篇文章详细介绍了Enable Address Sanitizer,对应的中文翻译在 Xcode 7上直接使用Clang Address Sanitizer

  • Static Analyzer(静态分析)

Static Analyzer是一个非常好的工具去发现编译器警告不会提示的问题和一些个人的内错泄露和死存储(不会用到的赋了值的变量)错误。这个方法可能大大的提高内存使用和性能,以及提升应用的整体稳定性和代码质量。

打开方式:Xcode->Product-Analyze
然后我们就能看到如下蓝色箭头所示的一些有问题的代码。

在这里插入图片描述

unrecognized selector send to instancd 快速定位

在debug navigator的断点栏里添加Create Symbolic Breakpoint。

在这里插入图片描述

在Symbolic中填写如下方法签名:
-[NSObject(NSObject) doesNotRecognizeSelector:]

在这里插入图片描述

设置完成后再遇到类似的错误就会定位到具体的代码。

1.7.Signal和EXC_BAD_ACCESS错误分析

1.7.1.Signal

什么是Signal

在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
在iOS中就是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。

Signal信号的类型

SIGABRT–程序中止命令中止信号
SIGALRM–程序超时信号
SIGFPE–程序浮点异常信号
SIGILL–程序非法指令信号
SIGHUP–程序终端中止信号
SIGINT–程序键盘中断信号
SIGKILL–程序结束接收中止信号
SIGTERM–程序kill中止信号
SIGSTOP–程序键盘中止信号
SIGSEGV–程序无效内存中止信号
SIGBUS–程序内存字节未对齐中止信号
SIGPIPE–程序Socket发送失败中止信号
iOS异常捕获这篇文章中有对各种信号的解释。

SIGABRT

就crash而言,SIGABRT是一个比较好解决的,因为他是一个可掌控的crash。App会在一个目的地终止,因为系统意识到app做了一些他不能支持的事情。
通常, SIGABRT 异常是由于某个对象接收到未实现的消息引起的。 或者,用简单的话说,在某个对象上调用了不存在的方法。

SIGSEGV

SIGSEGV程序无效内存中止信号,一般是表示内存不合法,

SIGBUS

SIGBUS程序内存字节未对齐中止信号,
截取Signal和Exception从容的崩溃
DSSignalHandlerDemo:点击此处下载
这是一个防止奔溃的源码,可以使一些原本会奔溃的操作弹出UIAlertView。里面写了两种信号量的崩溃:SIGSEGV、SIGABRT,还有一些信号大家可以写上去提个PR给我。

下图为源码的运行图,其中Signal中的Signal(EGV)第一次点击的时候能弹出alert,如果第二次点击就没有崩溃和alert,感觉像卡死一样。

而Signal(BRT)中的这种信号错误多次点击也是没有问题的还是能继续下去。个人猜测可能是SIGSEGV这种信号错误会导致了整个进程挂了。

注意:测试的时候如果测试Signal类型的崩溃,不要在xcode下的debug模式进行测试。因为系统的debug会优先去拦截。应该build好应用之后直接点击运行app进行测试。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值