在调试自己和别人的IOS App时,发生Crash是非常正常的情况,分析这些Crash的主要手段之一就是分析Crash发生时产生的错误日志。对于未越狱的IOS设备,获取错误日志主要通过Xcode自带的日志获取功能,但是这种方式有以下两点限制:
1.只能获取开发者自己开发的App的日志,无法获取第三方App的日志。
2.自动化工作难度较高(错误日志收集,错误日志整理等)。
文中,笔者首先简单介绍了一下使用Xcode抓取和分析错误日志的方法,然后为了克服上面的两个缺陷,笔者调研了一套可自动化的第三方App在本地测试的错误日志抓取方法。最后,笔者简单介绍了一下IOS错误日志的基本构成,供读者进行日志分析和分类收集参考。
错误日志的获取
方法1: 使用Xcode获取错误日志
对于自己研发并在测试机上测试的应用,我们可以通过Xcode来分析测试过程中的错误日志。但是App一但发布到AppStore,那么我们如何获取用户在使用过程中产生的错误日志呢?为了解决这一问题,苹果官方提供了一套供App开发者使用的基于Xcode工具的解决方案。
对于App在测试机上运行的情况,Xcode支持获取Apple机器的Log的情况。打开Xcode并打开自己开发的AppProject,在Window->Devices中,找到对应的设备
点击View Device Logs
在此处可以查看设备的日志信息,确认测试机连接到Mac,即可看到已经符号化完成的错误日志
对于在AppStore已经发布的App产生的错误日志,在Window->Organizer中,找到对应的App,在Crashes目录中找到AppStore下载的其他使用者使用过程中产生的错误日志。
方法2: 使用命令行获取错误日志
苹果官方提供的方案虽然能够成功的获取错误日志,但是有一个前提,那就是用户必须是App的开发者,如果我们需要获取第三方开发者开发的App的错误日志,那么上述方法就不能正确的工作了。适用于这一场景的可行方案之一,是使用libimobiledevice库中的idevicecrashreport命令来获取。
libimobiledevice库:libimobiledevice 是Github上的一个开源库,主要提供和未越狱的苹果IOS设备通信的服务。包括App安装,系统日志获取,设备信息获取和错误日志获取等能力(其他具体的服务能力可以参考libimobiledevice的官网:http://www.libimobiledevice.org/)。
本文具体主要使用idevicecrashreport和ideviceinstaller命令。
假设你获取的第三方App的Ipa已经发布到App Store,那么你可以直接从App Store上下载并测试。如果未发布到App Store,则需要使用ideviceinstaller命令安装该App到指定的手机。具体的command如下:
出现如下日志后安装成功(IOS > 9的设备使用ideviceinstaller会出现Segmentation fault,但是不影响App本身的安装)
然后就可以愉快地测试App了:)
App出现Crash后,ios设备本身保留了CrashReport。为了将设备上的CrashReport拷贝到本机,就需要使用idevicecrashreport命令了。具体的command如下:
如果需要在设备上保留错误日志,使用-k选项;如果需要将crashreport单独存放,使用-e选项。
导出后的文件结构如下所示:
其中wifi为IOS的wifi信息,此处我们并不关心。我们关注其中CrashDemo开头的两个后缀为ips的文件(CrashDemo是我用来测试的App名)。使用Xcode打开一个ips文件。如下所示:
这样我们就成功得到了App的错误日志。
该方法的优点:
-
可以获取第三方App的错误日志。
-
方法全部通过命令行方式进行获取,方便编写shell进行自动化获取
该方法的缺点:
无法获得AppStore上使用者相关的错误日志。
错误日志的符号化
我们可以注意到,上述第二种错误日志的获取方法所获取到的日志,日志中的堆栈信息并不像Android等错误日志中的堆栈信息那样打印出了调用的堆栈,而是使用段基址 + 偏移地址的格式显示。除此之外,对于错误信息,也进行了相关的处理。为了能够正确的分析错误,我们需要对这些地址进行符号化,从而将日志翻译成能够处理的信息。一般来说,符号化日志需要开发者提供编译App后生成的dsym文件,该文件包含了App编译后的所有类和函数的地址信息。但是如果我们需要分析第三方开发者的App,那么我们是无法获得该App的dsym文件的。下面给出了无dsym文件场景下符号化日志信息的方法。
不带dsym文件的日志符号化
使用symbolicate符号化错误日志文件
symbolicate:symbolicate是github开源的三方脚本,该脚本使用xcode自带的符号化工具symbolicatecrash进行符号化。symbolicate支持两种符号化的方法。使用App的dsym文件和使用App的ipa解压后的App文件夹。
那么为什么不直接使用xcode的symbolicatecrash命令进行符号化呢?这一点我们可以通过分析symbolicate.sh的源码来了解。
通过分析源码,我们可以看出xcode自带的symbolicatecrash工具的主要缺陷有两点:
1. 工具随着Xcode版本有着比较大的变更。
2. 缺少对错误日志UUID和App有效性的校验。
在我们的场景中,我们没有App的dsym文件,所以我们主要依赖App的ipa解压后的文件夹来进行调试,解压ipa可以使用mac自带的unzip命令
解压后的app目录在appdirpath/Payload目录下,在Finder中显示如下:
symbolicate脚本的使用方法如下:
符号化后的错误日志文件如下:
可以看见,堆栈信息的部分的已经完成了符号化,现在我们可以开始错误日志的分析了。
错误日志的分析
在完成了错误日志的符号化之后,接下来就是对错误日志的分析了,苹果官方提供的错误日志格式还是非常详细的。下面简单介绍错误日志(CrashReport)的基本格式。
CrashReport基本格式
IOS在以下两种情况下会产生错误日志:
1.应用违反操作系统规则。
2.应用中有Bug。
1中“违反操作系统规则“主要包括watchdog终止,用户强制结束和低内存终止三种情况。
2则是应用本身导致的问题。
1和2的日志格式不同,本文主要介绍2的情况下产生的错误日志的基本格式。
下图是一个错误日志的例子
整个错误日志分为进程信息、Crash信息、Crash异常、线程回溯、线程状态和二进制映像6个部分。
进程信息:包括Crash的ID(Incident Identifier),设备的Model和标识符(CrashReporter Key和Hardware Model),App信息(Path、Identifier、Version和CodeType),以及启动的进程信息(Process、ParentProcess)。
Crash信息:包括Crash发生的时间和系统号
Crash异常:包括异常类型(Exception Type)和异常码(Exception Codes)。此处对经常出现的异常码汇总如下(苹果的异常码还是很生动的)
0x8badf00d:(ate bad food)这个异常码表示该异常是由watchdog终止引发的。
0xbad22222:这个异常码表示该异常是由VoIP被过于频繁重启引发的。
0xdeadl0cc:(dead lock)这个异常码表示该异常是由于App长期占用系统资源而被系统强行终止引发的异常。
0xdeadfa11:(dead fail)这个异常码表示该异常是由用户强制终止引发的。
线程回溯信息:
回溯信息包括四列:
帧编号
二进制库的名称
调用方法的地址
第四列分为两个子列,一个基本地址和一个偏移量。
对于未符号化的日志,这一列的内容比较难以理解
对于符号化后的日志,该列内容如下:
分别为该进程调用的函数名称和对应的代码行号
线程状态:Crash中寄存器中的值(不常用,堆栈信息足以完成大部分的Crash分析)
二进制映像:Crash发生时已经载入的二进制文件
以上就是本文的全部内容了,文中涉及到的开源库的Github地址如下:
libimobiledevice: https://github.com/libimobiledevice/libimobiledevice
symbolicate: https://github.com/JohnWong/symbolicate