什么是崩潰報告
頭(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.(報告標(biāo)識符,兩個報告永遠都不會共享一個標(biāo)識符)
- CrashReporter Key: An anonymized per-device identifier. Two reports from the same device will contain identical values.(標(biāo)識設(shè)備符,來自同一設(shè)備的報告,這個字段相同)
- Process: The executable name for the process that crashed. This matches the value for the CFBundleExecutable key in the application's information property list.(崩潰進程執(zhí)行名,和CFBundleExecutable相應(yīng))
- 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.(處理器架構(gòu))
- 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 Type,Exception 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值則是試圖訪問的內(nèi)存地址。
如果objc_msgSend或者objc_release靠近Backtrace(見下文), 崩潰的原因可能就是試圖對一個已經(jīng)deallocated 對象發(fā)送消息。請用Zombies instrument對野指針作進一步分析。
Abnormal Exit(非正常退出) [EXC_CRASH // SIGABRT]
發(fā)生這種崩潰大多數(shù)是因為沒有catch Objective-C/C++ exceptions.
App Extensions如果花費太多時間去初始化以此類Exception退出(a watchdog termination).而且如果extensions是因為在launch的時候因被hang(掛起)而被殺死的,那么生成的crash report的Exception Subtype的會是LAUNCH_HANG. 因為extensions沒有main函數(shù),在你的extenxion以及依賴的庫中的靜態(tài)的構(gòu)造函數(shù)和+load函數(shù)所花費的時間就是初始化時間,為了避免這種exception,你應(yīng)該盡可能的減少在這些函數(shù)中的工作。
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可能把一些文件標(biāo)記為guarded,如果對guarded文件進行普通的操作就會觸發(fā)EXC_GUARD exception。 這類Exception就可以快速地定位問題所在:是否關(guān)閉了也被System library打開的文件。例如, 如果一個app關(guān)閉了由Core Data依賴的SQLite文件,Core Data就會在crash。Guard Exception是些此類問題得以更早地被注意,也更容易debug。
相對應(yīng)地Exception Subtype是個bitfield:
- [63:61] - Guard Type: guarded resource的類型. 如果值是0x2, 則表示the resource 是個文件(a file descriptor).
- [60:32] - Flavor: exception被觸發(fā)的情形:
- 如果位(1 << 0)被設(shè)置,則表明嘗試對guarded 文件進行close().
- 如果位(1 << 1)被設(shè)置,對guarded文件調(diào)用dup(), dup2(), fcntl(), 上述三個函數(shù)的參數(shù)是F_DUPFD 或者F_DUPFD_CLOEXEC
- 如果位(1 << 2)被設(shè)置,試圖通過socket來發(fā)送guarded文件
- 如果位(1 << 4)被設(shè)置,試圖對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, 表明此日志是系統(tǒng)快照(stackshot), 而不是一個crash report。按住Home鍵和任意音量鍵就會產(chǎn)生一個system stackshot,所以這類異常很多時候,是由用戶無意識觸發(fā)的,并不代表錯誤。
- 0xbad22222, 意味著一個VoIP app因為resumed太頻煩而被iOS結(jié)束。
- 0x8badf00d, 意味著app因為watchdog timeout而被結(jié)束。App花費太多的時間去launch,terminate,或響應(yīng)系統(tǒng)事件。常見的場景一般發(fā)生在如下函數(shù):
- application:didFinishLaunchingWithOptions:
- applicationWillResignActive:
- applicationDidEnterBackground:
- applicationWillEnterForeground:
- applicationDidBecomeActive:
- applicationWillTerminate:
在主線程上進行同步的網(wǎng)絡(luò)請求,而使主線程block。
- 0xc00010ff, 意味著app是在響應(yīng)a thermal event時被操作系統(tǒng)所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運行還持有系統(tǒng)資源(如通訊錄數(shù)據(jù)庫)而被系統(tǒng)killed。
- 0xdeadfa11, 用戶強制app退出。用戶操作:按住并關(guān)按鈕直到出現(xiàn)"滑動來關(guān)機" -> 按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時的運行環(huán)境
Backtrace
Backtrace包含各個線程在程序結(jié)束時的信息:
- 未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設(shè)備上得到的Crash logs是不包含函數(shù)或者方法的名字的(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
- 庫名字 - UIKit
- 函數(shù)調(diào)用地址 - 0x000000018ca5c2ec
- 分成兩部分基地址和偏移量 - 0x18ca14000 + 295660
0x18ca14000是基地址(指向文件),295660偏移量(代碼在文件中的行數(shù))
這根本看不出什么來,所以你需要將它符號化- Symbolication,轉(zhuǎn)化成可讀的函數(shù)或者方法名。
Symbolication時,需要app的二進制包和對應(yīng)的.dSYM文件(在創(chuàng)建二進制包時生成)。這兩個文件必須相對應(yīng),否則沒辦法完全符號化!
符號化崩潰報告:
- 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),然后按如下步驟:
- 連接你的設(shè)備至mac
- 從"Window"菜單中選擇"Devies"
- 在左邊的"DEVICES"列下,選擇你的iOS設(shè)備
- 點擊"View Device Logs"
- 將你的crash report拖到出現(xiàn)的面板的左列
- Xcode會自動符號化并display
2. 使用 symbolicatecrash 命令
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
<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容器的越界訪問,修改不可變對象,沒有實現(xiàn)protolocl的required方法等。
如果一個Exception沒有被caught,會有一個叫做uncaught exception handler的函數(shù)來解析它。這個handler默認logs異常信息和backtrace到設(shè)備的console然后結(jié)束程序。中有最后的未被捕捉的異常才會被寫進生成的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只包含了最后一個未捕捉的異常的信息,你應(yīng)該根據(jù)初始設(shè)備的console logs來更好地分析導(dǎo)至崩潰的原因。
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:崩潰時寄存器的值。看不懂沒關(guān)系,分析crash report時這部分信息并不是必須的,不過它可以幫助我們更好的crash 發(fā)生時的狀態(tài)。
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:處理器架構(gòu)
- <b1cae54dc3e1375fab7637c420e3b025>:二進制包的UUID
- /var/mobile/Applications/E935C852-F448-4B56-9934-76472B9CE336/WYPatient.app/WYPatient:在disk上的路徑
理解Low Memory Reports(內(nèi)存不足)
當(dāng)內(nèi)存不足發(fā)生時,(Low-memory notifications)被發(fā)送到每一個正在運行的app或者進程,要求釋放內(nèi)存,從而減少內(nèi)存壓力。如果內(nèi)存還是不足系統(tǒng)會終止background processes 來緩解內(nèi)存壓力。如果能釋放出足夠的內(nèi)存來,那你的app會繼續(xù)運行。否則你的app會被iOS強制終止,因為沒有足夠的內(nèi)存讓你的app繼續(xù)執(zhí)行下去,這時就會有l(wèi)aw memory report生成。
low memory report與其它的crash report不同,它沒有backtraces。它的頭和一般的crash report的Header相似。H
Header之后就列舉了系統(tǒng)的內(nèi)存統(tǒng)計信息。其中Page Size字段最值得關(guān)注。
low memory report中最重要的部分當(dāng)屬進程表了。進程表列舉了low memory report生成時所有正在運行的進程,包括系統(tǒng)守護進程。如果一個進程被jettisoned,原因會寫在[reason]列。一個進程被jettisoned可能因為下列原因:
- [vm-pageshortge]/[vm-thrashing]/]vm]: 進程因為內(nèi)存壓力而被終止(內(nèi)存不足發(fā)生)
- 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.(系統(tǒng)守護進程內(nèi)存使用超出了水準)
- [jettisoned]: be jettisoned 其它一些原因
當(dāng)你看到一個law memory crash,你應(yīng)該考慮檢查你消耗內(nèi)存的代碼和你對low memory通知的響應(yīng)。
Memory Usage Performance Guideline
Advanced Memory Analysis with Instruments
具體例子
其它
Overview of iOS Crash Reporting Tools: Part 1/2
Part 2/2
上面兩篇文章介紹了Crash Repoting Tools。