努力创造价值

iOS日志分析

在研发的时候,通过NSLog,断点等,都能够很好的发现和定位bug。但是如果已经提交到AppStore,或者在安装到设备上后运行崩溃,而且无法再次重现时,只能通过日志来发现和定位bug。现在说的就是怎么通过日志来定位bug。

1.日志的产生官方文档

两种主要情况会产生崩溃日志:

1.应用违反操作系统规则。
2.应用中有Bug。

违反iOS规则包括在启动、恢复、挂起、退出时watchdog超时、用户强制退出和低内存终止。让我们详细了解一下吧。

Watchdog 超时机制

从iOS 4.x开始,退出应用时,应用不会立即终止,而是退到后台。但是,如果你的应用响应不够快,操作系统有可能会终止你的应用,并产生一个崩溃日志。这些事件与下列UIApplicationDelegate方法相对应:
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:

上面所有这些方法,应用只有有限的时间去完成处理。如果花费时间太长,操作系统将终止应用。

注意: 如果你没有把需要花费时间比较长的操作(如网络访问)放在后台线程上就很容易发生这种情况。关于如果避免这种情况的信息,请参考我们的另外两篇教程: Grand Central Dispatch 和 NSOperations。

用户强制退出

iOS 4.x开始支持多任务。如果应用阻塞界面并停止响应, 用户可以通过在主屏幕上双击Home按钮来终止应用。此时,操作应用将生成一个崩溃日志。

注意: 双击Home按钮后,你将看到运行过的所有应用。那些应用不一定是正在运行,也不一定是被挂起。

通常,用户点击Home按钮时,应用将在后台保留约10分钟,然后操作系统自动将其终止。 所以双击Home按钮显示的应用列表只是表明那是一系列过去打开过的应用。删除那些应用的图标不会产生任何崩溃日志。

低内存终止

子类化UIViewController时,你或许已经注意到didReceiveMemoryWarning方法。

在前台运行的应用拥有访问和使用内存的最高优化级。然而,这并不意味着该应用能使用设备的所有可用内存 ——每个应用只能使用一部分可用内存。

当内存使用达到一定程度时,操作系统将发出一个 UIApplicationDidReceiveMemoryWarningNotification 通知。同时,调用 didReceiveMemoryWarning 方法。

此时,为了让应用继续正常运行,操作系统开始终止在后台的其他应用以释放一些内存。所有后台应用被终止后,如果你的应用还需要更多内存,操作系统会将你的应用也终止掉,并产生一个崩溃日志。而在这种情况下被终止的后台应用,不会产生崩溃日志。

注意: 根据 苹果文档, Xcode不会自动添加低内存日志。你必需手动获取日志。 然而,根据我的个人经验,使用 Xcode 4.5.2, 低内存日志也会自动导入,只是”Process”和”Type”属性都被标为Unknown(未知)。

另外,值得一提的是在极短时间内分配一大块内存将给系统内存带来巨大负担。这样,也会产生内存警告的通知。

应用中有Bug

如你所想,大多数闪退都是由于应用中有Bug,因此大多数崩溃日志的产生都是因为应用中的Bug。Bug的种类的有很多。

在本教程的后半部分,你将通过调试一个会产生崩溃日志的含有Bug的应用,学习如何找到问题所在并进行修复!

2.日志的获取

如果能够拿到崩溃的设备,Xcode》Window》Devices》选中设备》View Device Logs》选中,可以导出日志也可以不导出点击查看,不过如果没有导出,拔掉设备日志就没有了。

如果不能拿到设备,还可以用友盟也有收集查看崩溃日志的功能,但是友盟日志只保存15天,所以就需要新版本发布后及时的监视公司应用的数据情况,及时分析修改。

iTunes Connect 里面没有日志,只可以看到崩溃的统计。可以通过xcode获取到线上的崩溃日志xcode》window》organizer》(左侧选择)应用》crashes。organizer统计到的bug,已经指定了应用中对应的函数,比较方便分析。

3.分析日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//进程信息
Incident Identifier: 7741F73F-A81A-4F11-BA90-75B4EA2A83BD
CrashReporter Key: 63371fa4a7cc200b72c9e09788f6fa864cf2134a
Hardware Model: iPad5,1
Process: HomeHelper [1350]
Path: /private/var/containers/Bundle/Application/728599DA-8E22-4981-A9E9-A9E1461B1878/HomeHelper.app/HomeHelper
Identifier: cn.doucai.projectmanagerpsr
Version: 1.1.0 (1.1.0)
Code Type: ARM-64 (Native)
Parent Process: launchd [1]
//基本信息
Date/Time: 2016-07-08 09:33:44.44 +0800
Launch Time: 2016-07-08 08:27:08.08 +0800
OS Version: iOS 9.3.2 (13F69)
Report Version: 105
//异常
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0
Filtered syslog:
None found
//线程回溯
Last Exception Backtrace:
0 CoreFoundation 0x181112db0 __exceptionPreprocess + 124
1 libobjc.A.dylib 0x180777f80 objc_exception_throw + 56
2 CoreFoundation 0x181112cf8 +[NSException raise:format:] + 120
3 Foundation 0x181a06ac8 -[NSString rangeOfString:options:range:locale:] + 348
4 HomeHelper 0x100118874 0x1000b4000 + 411764
5 HomeHelper 0x100120464 0x1000b4000 + 443492
6 HomeHelper 0x100230d10 0x1000b4000 + 1559824
7 UIKit 0x1865bf030 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 692
8 UIKit 0x1865bf198 -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 80
9 UIKit 0x1865ae298 -[UITableView _updateVisibleCellsNow:isRecursive:] + 2360
10 UIKit 0x1865c3c64 -[UITableView _performWithCachedTraitCollection:] + 104
11 UIKit 0x1863548c4 -[UITableView layoutSubviews] + 176
12 UIKit 0x1862641e4 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 656
13 QuartzCore 0x183bf6994 -[CALayer layoutSublayers] + 148
14 QuartzCore 0x183bf15d0 CA::Layer::layout_if_needed(CA::Transaction*) + 292
15 QuartzCore 0x183bf1490 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32
16 QuartzCore 0x183bf0ac0 CA::Context::commit_transaction(CA::Transaction*) + 252
17 QuartzCore 0x183bf0820 CA::Transaction::commit() + 500
18 QuartzCore 0x183be9de4 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 80
19 CoreFoundation 0x1810c8728 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
20 CoreFoundation 0x1810c64cc __CFRunLoopDoObservers + 372
21 CoreFoundation 0x1810c68fc __CFRunLoopRun + 928
22 CoreFoundation 0x180ff0c50 CFRunLoopRunSpecific + 384
23 GraphicsServices 0x1828d8088 GSEventRunModal + 180
24 UIKit 0x1862d2088 UIApplicationMain + 204
25 HomeHelper 0x100259474 0x1000b4000 + 1725556
26 libdyld.dylib 0x180b8e8b8 start + 4
Global Trace Buffer (reverse chronological seconds):
13.769552 CFNetwork 0x0000000181797f18 TCP Conn 0x12eb69f60 complete. fd: 8, err: 0
13.770755 CFNetwork 0x0000000181799444 TCP Conn 0x12eb69f60 event 1. err: 0
13.773156 CFNetwork 0x0000000181797f18 TCP Conn 0x2ed32d10 complete. fd: 1, err: 4
13.773156 CFNetwork 0x0000000181799444 TCP Conn 0x12ed32d10 event 1. err: 0
13.796652 CFNetwork 0x0000000181797f18 TCP Conn 0x12ec68be0 complete. fd: 7, err: 0
13.796652 CFNetwork 0x0000000181799444 TCP NSURLConnection finished with error - code -1004
//线程状态
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x0000000180cac11c __pthread_kill + 8
1 libsystem_pthread.dylib 0x0000000180d78ef8 pthread_kill + 112
2 libsystem_c.dylib 0x0000000180c1ddac abort + 140
3 libc++abi.dylib 0x00000001807513f4 __cxa_bad_cast + 0
4 libc++abi.dylib 0x000000018076de98 default_unexpected_handler() + 0
5 libobjc.A.dylib 0x0000000180778248 _objc_terminate() + 124
6 libc++abi.dylib 0x000000018076af44 std::__terminate(void (*)()) + 16
7 libc++abi.dylib 0x000000018076ab10 __cxa_rethrow + 144
8 libobjc.A.dylib 0x0000000180778120 objc_exception_rethrow + 44
9 CoreFoundation 0x0000000180ff0cf8 CFRunLoopRunSpecific + 552
10 GraphicsServices 0x00000001828d8088 GSEventRunModal + 180
11 UIKit 0x00000001862d2088 UIApplicationMain + 204
12 HomeHelper 0x0000000100259474 0x1000b4000 + 1725556
13 libdyld.dylib 0x0000000180b8e8b8 start + 4
Thread 1 name: Dispatch queue: com.apple.libdispatch-manager
Thread 1:
0 libsystem_kernel.dylib 0x0000000180cad4d8 kevent_qos + 8
1 libdispatch.dylib 0x0000000180b707d8 _dispatch_mgr_invoke + 232
2 libdispatch.dylib 0x0000000180b5f648 _dispatch_source_invoke + 0
//二进制影像
Binary Images:
0x1000b4000 - 0x100447fff HomeHelper arm64 <639a066c17aa3f6ba90f6cb8b12e0e54> /var/containers/Bundle/Application/728599DA-8E22-4981-A9E9-A9E1461B1878/HomeHelper.app/HomeHelper
0x1200cc000 - 0x1200fbfff dyld arm64 <488b8b4696fb312db76da956e6f5aef5> /usr/lib/dyld
0x1806f8000 - 0x1806f9fff libSystem.B.dylib arm64 <77c873c418a6317f821f7b706d5b7dc6> /usr/lib/libSystem.B.dylib
0x1806fc000 - 0x18074efff libc++.1.dylib arm64 <9ec0d9dcf728349582c26a7da72f0364> /usr/lib/libc++.1.dylib
0x180750000 - 0x18076ffff libc++abi.dylib arm64 <aaa40b7f52513cf79c6f814b133556a7> /usr/lib/libc++abi.dylib
0x180770000 - 0x180adcfff libobjc.A.dylib arm64 <939f392022903f2da2858e676e4191ef> /usr/lib/libobjc.A.dylib
0x180ae0000 - 0x180ae4fff libcache.dylib arm64 <43424f4c7252330ca92c1a865da896e1> /usr/lib/system/libcache.dylib
(1) 进程信息

ncident Identifier是崩溃报告的唯一标识符。

CrashReporter Key 是与设备标识相对应的唯一键值。虽然它不是真正的设备标识符,但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不同的CrashReport值,说明这不是一个普遍的问题,只发生在一个或少数几个设备上。

Hardware Model 标识设备类型。 如果很多崩溃日志都是来自相同的设备类型,说明应用只在某特定类型的设备上有问题。上面的日志里,崩溃日志产生的设备是iPhone6,2。

Process 是应用名称。中括号里面的数字是闪退时应用的进程ID。

(2) 基本信息

这部分给出了一些基本信息,包括闪退发生的日期和时间,设备的iOS版本。如果有很多崩溃日志都来自iOS 6.0,说明问题只发生在iOS 6.0上。

(3) 异常

在这部分,你可以看到闪退发生时抛出的异常类型。还能看到异常编码和抛出异常的线程。根据崩溃报告类型的不同,在这部分你还能看到一些另外的信息。
Exception Type:异常类型的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
补充常见的Exception Type异常类型的信息:
1.EXC_BAD_ACCESS:此类型是最常见的crash, 通常用于访问了不该访问的内存导致的,一般 EXC_BAD_ACCESS后面的()还会带有补充信息
SIGSEGV:通常由于重复释放对象导致, 一般在ARC以后很少见到
SIGABRT: 收到Abort信号退出, 通常Foundtion库中的容器为了保护状态正常会做一些检测, 例如插入nil到数据中等会遇到此类错误.
野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。因为你访问了一块已经不属于你的内存。
SEGV(Segmentation Violation): 代表无效内存地址, 比如空指针, 未初始化指针, 栈溢出等.
SIGBUS:总栈错误, 与SIGSEGV不同的是, SIGSEGV访问的是无效的地址, 而SIGBUS访问的是有效的地址, 但是总栈访问异常(如地址对齐问题)
SIGILL: 尝试执行非法的指令, 可能不被识别或者没有权限
SIGFPE: 数学计算相关问题, 比如除零操作
SIGIPIPE: 管道另一端没有进程接手数据
2. EXC_BAD_INSTRUCTION:此类异常通常由于线程执行非法指令导致
3. EXC_ARITHMETIC:除零错误会抛出此类异常

Exception Codes:异常出错的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
补充常见的Exception Codes代码类型
0x8badf00d错误码:Watchdog超时,意为“ate bad food”。
0xdeadfa11错误码:用户强制退出,意为“dead fall”。
0xbaaaaaad错误码:用户按住Home键和音量键,获取当前内存状态,不代表崩溃。
0xbad22222错误码:VoIP应用(因为太频繁?)被iOS干掉。
0xc00010ff错误码:因为太烫了被干掉,意为“cool off”。
0xdead10cc错误码:因为在后台时仍然占据系统资源(比如通讯录)被干掉,意为“dead lock”

Triggered by Thread:判断几号线程闪退,

(4) 线程回溯

这部分提供应用中所有线程的回溯日志。 回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单,一般根据这个代码就能找到具体的crash问题。看下面这行日志:

1
0 CoreFoundation 0x181112db0 __exceptionPreprocess + 124

它包括四列:
帧编号—— 此处是0。
二进制库的名称 ——此处是 CoreFoundation.
调用方法的地址 ——此处是 0x181112db0.
第四列分为两个子列,一个基本地址和一个偏移量。此处是__exceptionPreprocess + 124, 第一个指向文件,第二个数字指向文件中的代码行。

(5) 线程状态

这部分是闪退时寄存器中的值。一般不需要这部分的信息,因为回溯部分的信息已经足够让你找出问题所在。

(6) 二进制映像

这部分列出了闪退时已经加载的二进制文件。

符号化

第一次看到崩溃日志上的回溯时,你或许会觉得它没什么意义。我们习惯使用方法名和行数,而非像这样的神秘位置:

4   HomeHelper                0x100118874 0x1000b4000 + 411764

Xcode符号化崩溃日志时,需要访问与App Store上对应的应用二进制文件以及生成二进制文件时产生的 .dSYM 文件。必需完全匹配才行。否则,日志将无法被完全符号化。

所以,保留每个分发给用户的编译版本非常重要。提交应用前进行归档时,Xcode将保存应用的二进制文件。可以在Xcode Organizer的Archives标签栏下找到所有已归档的应用文件。

友盟日志

xcode可以自动寻找.dSYM文件,然后自动对日志符号化。但是有事情其他日志,无法实现自动符号化,这个时候就需要自己找到.dSYM文件,把日志中的地址手动符号化。

.dSYM文件地址:xcode》window》Organizer》选择对应的应用,对应的版本号。》show in finder》show package contents》dSYMs文件夹》找到应用对应的.app.dSYM。

1.查看 xx.app 文件的 UUID,terminal 中输入命令 :

dwarfdump –uuid xx.app/xx (xx代表你的项目名)

2.查看 xx.app.dSYM 文件的 UUID ,在 terminal 中输入命令:

dwarfdump –uuid xx.app.dSYM
3.crash 文件内第一行 Incident Identifier 就是该 crash 文件的 UUID。
友盟日志的最后一部分的dSYM UUID,要和 xx.app 文件的 UUID相匹配,不然无法转化。
4.执行dwarfdump –arch=arm64 –lookup 0x100038a4c “$dSYMPath”,0x100038a4c日志中函数的地址,把”$dSYMPath”替换成你xx.app.dSYM文件的路径,不带双引号。
执行结果:

1
2
Line table dir : '/Users/***/Desktop/Git/***/***'
Line table file: '**DetailViewController.m' line 503, column 51 with start address 0x0000000100038a2c

可以分析道地址所代表的哪个文件中的哪一行代码。

低内存崩溃

低内存崩溃日志上没有应用线程的堆栈回溯。当应用发生低内存闪退时,你必需看看应用中内存使用的方式,以及是如何处理低内存警告的。你可以使用Instruments工具中使用Allocations 和 Leaks来发现内存分配问题和内存泄漏问题。如果你不知道如何利用 Instruments 检查内存问题,那就自己好好找找资料学习一下吧。

本文主要借鉴iOS应用崩溃日志分析