出处:http://www.iteye.com转自 http://www.raywenderlich.com/zh-hans/30818/ios应用崩溃日志揭秘
本文作者是 Soheil Moayedi Azarpour, 他是一名独立iOS开发者。作为一名应用开发者,你是否有过如下经历?Learn how to make sense of crash logs! 为确保你的应用正确无误,在将其提交到应用商店之前,你必定进行了大量的测试工作。它在你的设备上也运行得很好,但是,上了应用商店后,还是有用户抱怨会闪退 ! 如果你跟我一样是个完美主义者,你肯定想将应用做到尽善尽美。于是你打开代码准备修复闪退的问题……但是,从何处着手呢? 这时iOS崩溃日志派上用场了。在大多数情况下,你能从中了解到关于闪退的详尽、有用的信息。 通过本教程,你将学习到一些常见的崩溃日志案例,以及如何从开发设备和iTunes Connect上获取崩溃日志文件。你还将学习到符号化( symbolication),从日志追踪到代码 。你还将学习调试一个在待定情况下会闪退的应用。 让我们开始动手吧!什么是崩溃日志,从哪里能得它?iOS设备上的应用闪退时,操作系统会生成一个崩溃报告,也叫崩溃日志,保存在设备上。 崩溃日志上有很多有用的信息,包括应用是什么情况下闪退的。通常,上面有每个正在执行线程的完整堆栈跟踪信息,所以你能从中了解到闪退发生时各线程都在做什么,并分辨出闪退发生在哪个线程上。 有几种方法可以从设备上获取崩溃日志。 设备与电脑上的iTunes Store同步后,会将崩溃日志保存在电脑上。根据电脑操作系统的不同,崩溃日志将保存在以下位置: Mac OS X: ~/Library/Logs/CrashReporter/MobileDevice/ Windows XP: C:Documents and SettingsApplication DataApple ComputerLogsCrashReporterMobileDeviceWindows Vista or 7:C:UsersAppDataRoamingApple ComputerLogsCrashReporterMobileDevice当用户抱怨闪退时,你可以要求他让设备与iTunes同步,并根据操作系统的不同,到上述位置把崩溃日志下载下来,然后通过电子邮件发送给你。
你必需尽量获取用户设备生成的所有崩溃日志。因为崩溃日志越多,就越容易诊断问题所在!
另外,如果你装了Xcode,也能很容易通过Xcode从你的设备上获得崩溃日志。将iOS设备连接到电脑上,然后打开Xcode。从菜单栏上选择 Window 菜单, 然后选择 Organizer (快捷方式是 Shift-CMD-2).
在 Organizer 窗口上, 选中 Devices 标签栏. 在左侧的导航面板上,选中 Device Logs, 如下图所示:
Devices tab in Xcode Organizer
看看上图,左侧有好几个 Device Logs 菜单项. LIBRARY 下面的Device Logs是你所有设备(曾经连接到Xcode的)的日志 。每个设备下面的 Device Logs 是对应设备的日志。
应用提交到App Store后,你也能从 iTunes Connect 获取到用户的崩溃日志. 登录到 iTunes Connect 上, 选择 Manage Your Applications, 点击相应的应用, 点击应用图标下面的 View Details 按钮, 然后点击右栏Links部分的 Crash Reports 。
Crash Reports in iTunes Connect
如果没有崩溃日志,试试点击 Refresh 按钮刷新一下。如果你的应用还卖得不多,或者刚上架不久,iTunes Connect账号上也可能还没有任何崩溃日志。
如果iTunes Connect上有崩溃日志,你将看到如下图:
Crash Reports in iTunes Connect
有时,尽管有用户报告闪退,你仍然看不到崩溃报告。这时,最好让用户直接把崩溃报告发送给你。
什么情况下会产生崩溃日志?
两种主要情况会产生崩溃日志:
应用违反操作系统规则。
应用中有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的应用,学习如何找到问题所在并进行修复!
崩溃日志的实例
让我们看看一个崩溃日志的实例,以使你在处理一些实际问题之前心里有谱。
事不宜迟,见见你的新朋友吧:
[objc] view plain copy
// 1: 进程信息
Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
Process: Rage Masters [4155]
Path: /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters
Identifier: Rage Masters
Version: ??? (???)
Code Type: ARM (Native)
Parent Process: launchd [1]
// 2: 基本信息
Date/Time: 2012-10-17 21:39:06.967 -0400
OS Version: iOS 6.0 (10A403)
Report Version: 104
// 3: 异常
Exception Type: 00000020
Exception Codes: 0x000000008badf00d
Highlighted Thread: 0
// 4: 线程回溯
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x327f2eb4 mach_msg_trap + 20
1 libsystem_kernel.dylib 0x327f3048 mach_msg + 36
2 CoreFoundation 0x36bd4040 __CFRunLoopServiceMachPort + 124
3 CoreFoundation 0x36bd2d9e __CFRunLoopRun + 878
4 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352
5 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100
6 CFNetwork 0x32ac343e CFURLConnectionSendSynchronousRequest + 330
7 Foundation 0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242
8 Rage Masters 0x000d4046 0xd2000 + 8262
Thread 1:
0 libsystem_kernel.dylib 0x32803d98 __workq_kernreturn + 8
1 libsystem_c.dylib 0x3a987cf6 _pthread_workq_return + 14
2 libsystem_c.dylib 0x3a987a12 _pthread_wqthread + 362
3 libsystem_c.dylib 0x3a9878a0 start_wqthread + 4
// 5: 线程状态
Thread 0 crashed with ARM Thread State (32-bit):
r0: 0x00000000 r1: 0x00000000 r2: 0x00000001 r3: 0x39529fc8
r4: 0xffffffff r5: 0x2fd7d301 r6: 0x2fd7d300 r7: 0x2fd7d9d0
r8: 0x2fd7d330 r9: 0x3adbf8a8 r10: 0x2fd7d308 r11: 0x00000032
ip: 0x00000025 sp: 0x2fd7d2ec lr: 0x001bdb25 pc: 0x30301838
cpsr: 0x00000010
// 6: 二进制映像
Binary Images:
0xd2000 - 0xd7fff +Rage Masters armv7 /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters
0x2fe41000 - 0x2fe61fff dyld armv7 /usr/lib/dyld
0x327f2000 - 0x32808fff libsystem_kernel.dylib armv7 /usr/lib/system/libsystem_kernel.dylib
0x328a8000 - 0x328bdfff libresolv.9.dylib armv7 /usr/lib/libresolv.9.dylib
0x32a70000 - 0x32b35fff CFNetwork armv7 /System/Library/Frameworks/CFNetwork.framework/CFNetwork
0x32b7a000 - 0x32cc3fff libicucore.A.dylib armv7 /usr/lib/libicucore.A.dylib
0x32cc4000 - 0x32cc5fff CoreSurface armv7 /System/Library/PrivateFrameworks/CoreSurface.framework/CoreSurface
0x32f65000 - 0x32f8afff OpenCL armv7 /System/Library/PrivateFrameworks/OpenCL.framework/OpenCL
这报告看起来像天书。:) 我们分几部分来解读吧:
(1) 进程信息
第一部分是闪退进程的相关信息。
Incident Identifier是崩溃报告的唯一标识符。
CrashReporter Key 是与设备标识相对应的唯一键值。虽然它不是真正的设备标识符,但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不同的CrashReport值,说明这不是一个普遍的问题,只发生在一个或少数几个设备上。
Hardware Model 标识设备类型。 如果很多崩溃日志都是来自相同的设备类型,说明应用只在某特定类型的设备上有问题。上面的日志里,崩溃日志产生的设备是iPhone 4s。
Process 是应用名称。中括号里面的数字是闪退时应用的进程ID。
接下来几行不言自明,无需赘述。
(2) 基本信息
这部分给出了一些基本信息,包括闪退发生的日期和时间,设备的iOS版本。如果有很多崩溃日志都来自iOS 6.0,说明问题只发生在iOS 6.0上。
(3) 异常
在这部分,你可以看到闪退发生时抛出的异常类型。还能看到异常编码和抛出异常的线程。根据崩溃报告类型的不同,在这部分你还能看到一些另外的信息。
(4) 线程回溯
这部分提供应用中所有线程的回溯日志。 回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单。看下面这行日志:
2 XYZLib 0x34648e88 0x83000 + 8740
它包括四列:
帧编号—— 此处是2。
二进制库的名称 ——此处是 XYZLib.
调用方法的地址 ——此处是 0x34648e88.
第四列分为两个子列,一个基本地址和一个偏移量。此处是0×83000 + 8740, 第一个数字指向文件,第二个数字指向文件中的代码行。
(5) 线程状态
这部分是闪退时寄存器中的值。一般不需要这部分的信息,因为回溯部分的信息已经足够让你找出问题所在。
(6) 二进制映像
这部分列出了闪退时已经加载的二进制文件。
符号化Symbolication
第一次看到崩溃日志上的回溯时,你或许会觉得它没什么意义。我们习惯使用方法名和行数,而非像这样的神秘位置:
6 Rage Masters 0x0001625c 0x2a000 + 3003
将这些十六进制地址转化成方法名称和行数的过程称之为符号化。
从Xcode的Organizer窗口获取崩溃日志后过几秒钟,崩溃日志将被自动符号化。上面那行被符号化后的版本如下 :
6 Rage Masters 0x0001625c -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35)
Xcode符号化崩溃日志时,需要访问与App Store上对应的应用二进制文件以及生成二进制文件时产生的 .dSYM 文件。必需完全匹配才行。否则,日志将无法被完全符号化。
所以,保留每个分发给用户的编译版本非常重要。提交应用前进行归档时,Xcode将保存应用的二进制文件。可以在Xcode Organizer的Archives标签栏下找到所有已归档的应用文件。
在发现崩溃日志时,如果有相匹配的.dSYM文件和应用二进制文件,Xcode会自动对崩溃日志进行符号化。如果你换到别的电脑或创建新的账户,务必将所有二进制文件移动到正确的位置,使Xcode能找到它们。
注意: 你必需同时保留应用二进制文件和.dSYM文件才能将崩溃日志完整符号化。每次提交到iTunes Connect的构建都必需归档。
.dSYM文件和二进制文件是特定绑定于每一次构建和后续构建的,即使来自相同的源代码文件,每一次构建也与其他构建不同,不能相互替换。
如果你使用Build 和 Archive 命令,这些文件会自动放在适当位置。 如果不是使用Build 和 Archive命令,放在Spotlight能够搜索到的位置(比如Home目录)即可。
低内存闪退
因为低内存崩溃日志与普通崩溃日志略有不同,所以本教程特别分开说明一下。:]
iOS设备检测到低内存时,虚拟内存系统发出通知请求应用释放内存。这些通知发送到所有正在运行的应用和进程,试图收回一些内存。
如果内存使用依然居高不下,系统将会终止后台线程以缓解内存压力。如果可用内存足够,应用将能够继续运行而不会产生崩溃报告。否则,应用将被iOS终止,并产生低内存崩溃报告。
低内存崩溃日志上没有应用线程的堆栈回溯。相反,上面显示的是以内存页数为单位的各进程内存使用量。(在撰写本文的时候,一个内存页的大小是4KB。)
被iOS因释放内存页终止的进程名称后面你会看到 jettisoned 字样。如果看到它出现在你的应用名称后面,说明你的应用因使用太多内存而被终止了。
低内存崩溃日志看起来像这样:
Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
OS Version: iPhone OS 3.1.3 (7E18)
Date/Time: 2012-10-17 21:39:06.967 -0400
Free pages: 96
Wired pages: 10558
Purgeable pages: 0
Largest process: Rage Masters
Processes
Name UUID Count resident pages
Rage Masters 9320 (jettisoned) (active)
mediaserverd 255
dataaccessd 505
syslogd 71
apsd 171
securityd 243
notifyd 2027
CommCenter 189
SpringBoard 2158 (active)
accessoryd 91
configd 371
fairplayd 93
mDNSResponder 292
lockdownd 1204
launchd 72
当应用发生低内存闪退时,你必需看看应用中内存使用的方式,以及是如何处理低内存警告的。你可以使用Instruments工具中使用Allocations 和 Leaks来发现内存分配问题和内存泄漏问题。如果你不知道如何利用 Instruments 检查内存问题,可以看看 这个教程 。
还有,别忘记虚拟内存! Instruments工具的Leaks 和 Allocations 不能跟踪显存使用情况。必需使用 VM Tracker 才能查看显存使用情况。
VM Tracker 默认是关闭的。打开Instrument,手动 选中 Automatic Snapshotting 标志或者按下 Snapshot Now 按钮。
本教程后面将会学习如何研究低内存崩溃日志。