崩溃报告小谈

什么是崩溃报告

官方细详介绍了崩溃
官方细详介绍了如何查看崩溃日志

头(Header)

Incident Identifier: E6EBC860-0222-4B82-BF7A-2B1C26BE1E85
CrashReporter Key: 6196484647b3431a9bc2833c19422539549f3dbe
Hardware Model: iPhone6,1
Process: TheElements [4637]
Path: /private/var/mobile/Containers/Bundle/Application/5A9E4FC2-D03B-4E19-9A91-104A0D0C1D44/TheElements.app/TheElements
Identifier: com.example.apple-samplecode.TheElements
Version: 1.12
Code Type: ARM (Native)
Parent Process: launchd [1]
 
Date/Time: 2015-04-06 09:14:08.775 -0700
Launch Time: 2015-04-06 09:14:08.597 -0700
OS Version: iOS 8.1.3 (12B466)
Report Version: 105

大部分字段一看就知道是什么意思,这里就几个介绍:

  • Incident Identifier: A unique identifier for the report. Two reports will never share the same Incident Identifier.(报告标识符,两个报告永远都不会共享一个标识符)
  • CrashReporter Key: An anonymized per-device identifier. Two reports from the same device will contain identical values.(标识设备符,来自同一设备的报告,这个字段相同)
  • Process: The executable name for the process that crashed. This matches the value for the CFBundleExecutable key in the application's information property list.(崩溃进程执行名,和CFBundleExecutable相应)
  • Version: The version of the process that crashed. The value for this key is a concatenation of the values for the CFBundleVersion and CFBundleVersionString keys in the application's information property list.(崩溃进程的版本号,CFBundleVersionCF,BundleVersionString)
  • Code Type: The target architecture of the process that crashed. This will be one of ARM-64 or ARM.(处理器架构)
  • OS Version: The OS version, including the build number, on which the crash occurred.

Exception Codes

这里的Exception不要和Objective-C中的exceptions混淆(Objective-C中的exception也可能引起crash). 而这里的列举的是Mach Exception TypeException Subtype,处理器的Exception Codes, 和其它一些字段。
Listing 2 Excerpt of the Exception Codes section from a crash report.

Exception Type: EXC_CRASH (SIGABRT) //Mach Exception Type
Exception Codes: 0x0000000000000000, 0x0000000000000000 //processor-specific
Triggered by Thread: 0 //the index of the thread, on which the crash occurred

常见的exception类型

Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]

试图访问法非法的地直址。Exception Subtype字段包含了一个kern_return_t来描述错误。在Exception Subtype之后的Exception Sub-code值则是试图访问的内存地址。

如果objc_msgSend或者objc_release靠近Backtrace(见下文), 崩溃的原因可能就是试图对一个已经deallocated 对象发送消息。请用Zombies instrument对野指针作进一步分析。

Abnormal Exit(非正常退出) [EXC_CRASH // SIGABRT]

发生这种崩溃大多数是因为没有catch Objective-C/C++ exceptions.

App Extensions如果花费太多时间去初始化以此类Exception退出(a watchdog termination).而且如果extensions是因为在launch的时候因被hang(挂起)而被杀死的,那么生成的crash report的Exception Subtype的会是LAUNCH_HANG. 因为extensions没有main函数,在你的extenxion以及依赖的库中的静态的构造函数和+load函数所花费的时间就是初始化时间,为了避免这种exception,你应该尽可能的减少在这些函数中的工作。

Trace Trap [EXC_BREAKPOINT // SIGTRAP]

Similar to an Abnormal Exit, this exception is intended to give an attached debugger the chance to interrupt the process at a specific point in its execution. You can trigger this exception from your own code using the __builtin_trap() function. If no debugger is attached, the process is terminated and a crash report is generated.

Swift code will terminate the program with this exception type if it detects an unexpected condition at runtime such as:

  • a non-optional type with a nil value
  • a failed forced type conversion

Look at the Backtrace of the crashed thread to determine where the unexpected condition was encountered. Additional information may have also been logged to the device's console.

Guarded Resource Violation [EXC_GUARD]

System libraries可能把一些文件标记为guarded,如果对guarded文件进行普通的操作就会触发EXC_GUARD exception。 这类Exception就可以快速地定位问题所在:是否关闭了也被System library打开的文件。例如, 如果一个app关闭了由Core Data依赖的SQLite文件,Core Data就会在crash。Guard Exception是些此类问题得以更早地被注意,也更容易debug。

相对应地Exception Subtype是个bitfield:

  • [63:61] - Guard Type: guarded resource的类型. 如果值是0x2, 则表示the resource 是个文件(a file descriptor).
  • [60:32] - Flavor: exception被触发的情形:
    • 如果位(1 << 0)被设置,则表明尝试对guarded 文件进行close().
    • 如果位(1 << 1)被设置,对guarded文件调用dup(), dup2(), fcntl(), 上述三个函数的参数是F_DUPFD 或者F_DUPFD_CLOEXEC
    • 如果位(1 << 2)被设置,试图通过socket来发送guarded文件
    • 如果位(1 << 4)被设置,试图对guarded文件进行写操作
  • [31:0] - File Descriptor: 试图去修改的文件描述符

Resource Limit [EXC_RESOURCE]

The process hit a resource consumption limit. This is not a crash, but a notification from the OS that the process is using too many resources. The exact resource is in the Exception Subtype field.

  • The exception subtype WAKEUPS indicates that a thread was waking up too many times per second, which forces the CPU to wake up very often and consumes battery life. You should investigate why this is happening and avoid it if possible.
  • The exception subtype MEMORY indicates that the process has crossed a memory limit imposed by the system. This may be a precursor to termination for excess memory usage.

Other Exception Types

还有一些未命名的以16进制表示的(e.g. 00000020)Exception Type. 如果遇到这类Exception,真接查看Exception Codes:

  • 0xbaaaaaad, 表明此日志是系统快照(stackshot), 而不是一个crash report。按住Home键和任意音量键就会产生一个system stackshot,所以这类异常很多时候,是由用户无意识触发的,并不代表错误。
  • 0xbad22222, 意味着一个VoIP app因为resumed太频烦而被iOS结束。
  • 0x8badf00d, 意味着app因为watchdog timeout而被结束。App花费太多的时间去launch,terminate,或响应系统事件。常见的场景一般发生在如下函数:
    • application:didFinishLaunchingWithOptions:
    • applicationWillResignActive:
    • applicationDidEnterBackground:
    • applicationWillEnterForeground:
    • applicationDidBecomeActive:
    • applicationWillTerminate:
      在主线程上进行同步的网络请求,而使主线程block。
  • 0xc00010ff, 意味着app是在响应a thermal event时被操作系统所killed。This may be due to an issue with the particular device that this crash occurred on, or the environment it was operated in. For tips on making your app run more efficiently, see iOS Performance and Power Optimization with Instruments WWDC session.
  • 0xdead10cc, app已进入background运行还持有系统资源(如通讯录数据库)而被系统killed。
  • 0xdeadfa11, 用户强制app退出。用户操作:按住并关按钮直到出现"滑动来关机" -> 按Home键。

Applicaton Specific Information

Application Specific Information:
MyApp[134] was suspended with locked system files:
/private/var/mobile/Library/AddressBook/AddressBook.sqlitedb

crashes含有额外的一些信息,帮助我们更好地理解app crash时的运行环境

Backtrace

Backtrace包含各个线程在程序结束时的信息:

  • 未symbolicated之前:
Last Exception Backtrace:
(0x2f82ffce 0x39fdecca 0x2f82fea8 0x301dcd56 0x3217fd2c 0x3205dae0 0x3217fb9c 0x3217e92a 0x32125c42 0x321195c8 0x321194b0 0x3982fc 0x3212017a 0x3211f774 0x321267d2 0x321b6ffa 0x2b1f7c 0x2b17ba 0x39727e 0x320504c6 0x32050284 0x321dc386 0x320f9d7e 0x320f9b88 0x320f9b20 0x3204bd74 0x31cc9626 0x31cc4e36 0x31cc4cc8 0x31cc46da 0x31cc44ea 0x31cbe218 0x2f7fb2a0 0x2f7f8c44 0x2f7f8f86 0x2f763f0a 0x2f763cee 0x3466865e 0x320af168 0x637186 0x3a4ebab2)
  • symbolicated之后:
Last Exception Backtrace:
0   CoreFoundation                  0x2f82ffce __exceptionPreprocess + 126
1   libobjc.A.dylib                 0x39fdecca objc_exception_throw + 34
2   CoreFoundation                  0x2f82fea8 +[NSException raise:format:arguments:] + 96
3   Foundation                      0x301dcd56 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86
4   UIKit                           0x3217fd2c __53-[UITableView _configureCellForDisplay:forIndexPath:]_block_invoke + 392
5   UIKit                           0x3205dae0 +[UIView(Animation) performWithoutAnimation:] + 68
6   UIKit                           0x3217fb9c -[UITableView _configureCellForDisplay:forIndexPath:] + 96
7   UIKit                           0x3217e92a -[UITableView _createPreparedCellForGlobalRow:withIndexPath:] + 430
8   UIKit                           0x32125c42 -[UITableView _updateVisibleCellsNow:] + 1802
9   UIKit                           0x321195c8 -[UITableView _visibleCells] + 20
10  UIKit                           0x321194b0 -[UITableView setSeparatorStyle:] + 120
11  WYPatient                       0x003982fc -[WYAccountSettingViewController tableView:numberOfRowsInSection:] (WYAccountSettingViewController.m:129)
12  UIKit                           0x3212017a -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 2374
13  UIKit                           0x3211f774 -[UITableViewRowData rectForFooterInSection:heightCanBeGuessed:] + 292
14  UIKit                           0x321267d2 -[UITableViewRowData rectForTableFooterView] + 358
15  UIKit                           0x321b6ffa -[UITableView setTableFooterView:] + 358
16  WYPatient                       0x002b1f7c -[WYBaseTableViewController defaultConfigTableView] (WYBaseTableViewController.m:94)
17  WYPatient                       0x002b17ba -[WYBaseTableViewController viewDidLoad] (WYBaseTableViewController.m:35)
18  WYPatient                       0x0039727e -[WYAccountSettingViewController viewDidLoad] (WYAccountSettingViewController.m:39)
19  UIKit                           0x320504c6 -[UIViewController loadViewIfRequired] + 514
20  UIKit                           0x32050284 -[UIViewController view] + 20
21  UIKit                           0x321dc386 -[UINavigationController _startCustomTransition:] + 630
22  UIKit                           0x320f9d7e -[UINavigationController _startDeferredTransitionIfNeeded:] + 414
23  UIKit                           0x320f9b88 -[UINavigationController __viewWillLayoutSubviews] + 40
24  UIKit                           0x320f9b20 -[UILayoutContainerView layoutSubviews] + 180
25  UIKit                           0x3204bd74 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 376
26  QuartzCore                      0x31cc9626 -[CALayer layoutSublayers] + 138
27  QuartzCore                      0x31cc4e36 CA::Layer::layout_if_needed(CA::Transaction*) + 346
28  QuartzCore                      0x31cc4cc8 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 12
29  QuartzCore                      0x31cc46da CA::Context::commit_transaction(CA::Transaction*) + 226
30  QuartzCore                      0x31cc44ea CA::Transaction::commit() + 310
31  QuartzCore                      0x31cbe218 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 52
32  CoreFoundation                  0x2f7fb2a0 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 16
33  CoreFoundation                  0x2f7f8c44 __CFRunLoopDoObservers + 280
34  CoreFoundation                  0x2f7f8f86 __CFRunLoopRun + 726
35  CoreFoundation                  0x2f763f0a CFRunLoopRunSpecific + 518
36  CoreFoundation                  0x2f763cee CFRunLoopRunInMode + 102
37  GraphicsServices                0x3466865e GSEventRunModal + 134
38  UIKit                           0x320af168 UIApplicationMain + 1132
39  WYPatient                       0x00637186 main (main.m:18)
40  libdyld.dylib                   0x3a4ebab2 tlv_initializer + 2

Symbolication

从iOS设备上得到的Crash logs是不包含函数或者方法的名字的(symbols). 你看到的全是一堆十六进制地址如下所示:

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   TheElements               0x00000001000effdc 0x1000e4000 + 49116
1   UIKit                     0x000000018ca5c2ec 0x18ca14000 + 295660
2   UIKit                     0x000000018ca5c1f4 0x18ca14000 + 295412
3   QuartzCore                0x000000018c380f60 0x18c36c000 + 85856
4   libdispatch.dylib         0x0000000198fb9368 0x198fb8000 + 4968
5   libdispatch.dylib         0x0000000198fbd97c 0x198fb8000 + 22908
6   CoreFoundation            0x000000018822dfa0 0x188150000 + 909216
7   CoreFoundation            0x000000018822c048 0x188150000 + 901192
8   CoreFoundation            0x00000001881590a0 0x188150000 + 37024
9   GraphicsServices          0x00000001912fb5a0 0x1912f0000 + 46496
10  UIKit                     0x000000018ca8aaa0 0x18ca14000 + 486048
11  TheElements               0x00000001000e9800 0x1000e4000 + 22528
12  libdyld.dylib             0x0000000198fe2a04 0x198fe0000 + 10756

1 UIKit 0x000000018ca5c2ec 0x18ca14000 + 295660
有四列:

  1. 序号 - 1
  2. 库名字 - UIKit
  3. 函数调用地址 - 0x000000018ca5c2ec
  4. 分成两部分基地址和偏移量 - 0x18ca14000 + 295660
    0x18ca14000是基地址(指向文件),295660偏移量(代码在文件中的行数)

这根本看不出什么来,所以你需要将它符号化- Symbolication,转化成可读的函数或者方法名。
Symbolication时,需要app的二进制包和对应的.dSYM文件(在创建二进制包时生成)。这两个文件必须相对应,否则没办法完全符号化!

符号化崩溃报告:

  • XCode
  • Symbolicatecrash command

参考文献
本文将会依次介绍这两种方法,在介绍之前如果你不知道如何在你的电脑上找到崩溃报告,请参考下面链接:
how-to-find-crash-logs
how-to-get-a-crash-log-in-macos
还有如何显示隐藏文件(隐藏在包里):
show-hidden-files

1. Using XCode

你需要三个文件:

  • Crash report
  • Symbol filef(.dSYM)
  • Application bundle(.app file. 从.ipa中获取)

将这三个文件放在能被Spotlight搜索到的同一目录下(如Home),然后按如下步骤:

  1. 连接你的设备至mac
  2. 从"Window"菜单中选择"Devies"
  3. 在左边的"DEVICES"列下,选择你的iOS设备
  4. 点击"View Device Logs"
  5. 将你的crash report拖到出现的面板的左列
  6. Xcode会自动符号化并display

2. 使用 symbolicatecrash 命令

  1. export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
  2. <symbolicatecrash file path> <crash file path> <.dSYM file path>

symbolicatecrash的文件路径可通过find命令查找:
find /Applications/Xcode.app -name symbolicatecrash -type f

你也可以把输出重定向到一个文件中:
<symbolicatecrash file path> <crash file path> <.dSYM file path> > <output file path>
<symbolicatecrash file path> [--output OUTPUT_FILE] <crash file path> <.dSYM file path>

Exceptions

这里的Exceptions就是程序中的Exceptions。如out-of-bounds collection access容器的越界访问,修改不可变对象,没有实现protolocl的required方法等。
如果一个Exception没有被caught,会有一个叫做uncaught exception handler的函数来解析它。这个handler默认logs异常信息和backtrace到设备的console然后结束程序。中有最后的未被捕捉的异常才会被写进生成的crash report中,并实写在"Last Exception BackTrace"中,如下:

Last Exception Backtrace:
(0x2f82ffce 0x39fdecca 0x2f82fea8 0x301dcd56 0x3217fd2c 0x3205dae0 0x3217fb9c 0x3217e92a 0x32125c42 0x321195c8 0x321194b0 0x3982fc 0x3212017a 0x3211f774 0x321267d2 0x321b6ffa 0x2b1f7c 0x2b17ba 0x39727e 0x320504c6 0x32050284 0x321dc386 0x320f9d7e 0x320f9b88 0x320f9b20 0x3204bd74 0x31cc9626 0x31cc4e36 0x31cc4cc8 0x31cc46da 0x31cc44ea 0x31cbe218 0x2f7fb2a0 0x2f7f8c44 0x2f7f8f86 0x2f763f0a 0x2f763cee 0x3466865e 0x320af168 0x637186 0x3a4ebab2)

因为Last Exception Backtrace只包含了最后一个未捕捉的异常的信息,你应该根据初始设备的console logs来更好地分析导至崩溃的原因。

Thread State

Thread 0 crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x00000000      r2: 0x00000000      r3: 0x3a544aa9
    r4: 0x00000006    r5: 0x3c30818c      r6: 0x185bb250      r7: 0x27daea40
    r8: 0x185bb250    r9: 0x00000001     r10: 0x3267bfcc     r11: 0x27dae830
    ip: 0x00000148    sp: 0x27daea34      lr: 0x3a60b797      pc: 0x3a5a11f0
  cpsr: 0x00000010

列举了崩溃线程的ARM thread state:崩溃时寄存器的值。看不懂没关系,分析crash report时这部分信息并不是必须的,不过它可以帮助我们更好的crash 发生时的状态。

Binary Images

Binary Images:
0x55000 - 0x154cfff WYPatient armv7  <b1cae54dc3e1375fab7637c420e3b025> /var/mobile/Applications/E935C852-F448-4B56-9934-76472B9CE336/WYPatient.app/WYPatient
0x2be7b000 - 0x2be9bfff dyld armv7  <651a31c39f71311f965f8ac44de02c88> /usr/lib/dyld
0x2e4ef000 - 0x2e5d7fff RawCamera armv7  <8f62f266f7d539a5a388221dfe90db50> /System/Library/CoreServices/RawCamera.bundle/RawCamera
...

每一行描述了一个binary image的详细信息,以第一条为例:

  • 0x55000 - 0x154cfff: binary image的地址空间
  • WYPatient:二进制包名
  • armv7:处理器架构
  • <b1cae54dc3e1375fab7637c420e3b025>:二进制包的UUID
  • /var/mobile/Applications/E935C852-F448-4B56-9934-76472B9CE336/WYPatient.app/WYPatient:在disk上的路径

理解Low Memory Reports(内存不足)

当内存不足发生时,(Low-memory notifications)被发送到每一个正在运行的app或者进程,要求释放内存,从而减少内存压力。如果内存还是不足系统会终止background processes 来缓解内存压力。如果能释放出足够的内存来,那你的app会继续运行。否则你的app会被iOS强制终止,因为没有足够的内存让你的app继续执行下去,这时就会有law memory report生成。
low memory report与其它的crash report不同,它没有backtraces。它的头和一般的crash report的Header相似。H
Header之后就列举了系统的内存统计信息。其中Page Size字段最值得关注。
low memory report中最重要的部分当属进程表了。进程表列举了low memory report生成时所有正在运行的进程,包括系统守护进程。如果一个进程被jettisoned,原因会写在[reason]列。一个进程被jettisoned可能因为下列原因:

  • [vm-pageshortge]/[vm-thrashing]/]vm]: 进程因为内存压力而被终止(内存不足发生)
  • Note: Note: The system avoids killing the frontmost app when vnodes are nearly exhausted. This means that your application, when in the background, may be terminated even if it is not the source of excess vnode usage.
  • [highwater]: A system daemon crossed it's high water mark for memory usage.(系统守护进程内存使用超出了水准)
  • [jettisoned]: be jettisoned 其它一些原因

当你看到一个law memory crash,你应该考虑检查你消耗内存的代码和你对low memory通知的响应。
Memory Usage Performance Guideline
Advanced Memory Analysis with Instruments

具体例子

其它

Overview of iOS Crash Reporting Tools: Part 1/2
Part 2/2
上面两篇文章介绍了Crash Repoting Tools。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容