前言
在項目開發(fā)中我們總能遇到各種各樣的問題造成Crash崩潰 究其原因一個是我們開發(fā)人員對系統(tǒng)機制理解不夠深刻或者代碼邏輯不夠嚴謹造成的
我們可以少犯錯但不可能不犯錯 ——不知道誰說的系列(:
那么問題發(fā)生后我們應該第一時間定為找到問題再去嘗試解決問題
一般都會經歷這樣一個過程發(fā)現(xiàn)問題 -> 定位問題 -> 解決問題
發(fā)現(xiàn)問題
- 首先大部分問題其實都應該是程序員自己先發(fā)現(xiàn)的
每一次提交和改動都應該經過自己嚴謹?shù)目紤]和初步測試保證沒有問題才可以Commit是我們開發(fā)者基本素養(yǎng)- 再者如果你們有review機制和測試團隊的話 review組同學是為了代碼質量 測試團隊就是為項目上線之前質量把關的最后一步了 一般測試同學會寫各種測試用例各種場景來check開發(fā)同學寫的項目存在哪些問題或者與需求設計不符的
- 然后就到了上線了 高并發(fā)多場景的使用 難保會出現(xiàn)一些沒有發(fā)現(xiàn)奇怪的問題 甚至Crash 這就是用戶來發(fā)現(xiàn)問題了 是我們的失職
在上面任何一個環(huán)節(jié)都肯能出現(xiàn)Crash問題 所以當問題出現(xiàn)后 我們該如何第一時間定位問題 解決問題就尤為重要
定位問題
對于定位問題我們一般又這么幾種方式
一、模擬場景 Debug
一般這種可以知道特定場景并且可以直接真機Debug的問題是最好定位和解決的 一般加個全局斷點就搞定了 也是最簡單最不應該出現(xiàn)的問題
二、可以拿到出現(xiàn)Crash問題的手機(需要開啟與開發(fā)者共享崩潰信息 一般都是打開著的)
可以打開手機設置 -> 隱私 -> 分析-> 打開共享iPhone分析 -> 打開與應用開發(fā)者共享
那我們可以通過這種方式查看手機里的Crash log 來幫助我們分析問題
打開Xcode 選擇頂部條上的Window
選擇Devices and Simulators 進入
選擇View Device Logs 顯示該設備log信息
需要Loading一會兒 我們就可以查看到這臺設備上的一些Crash信息了
上面這張圖看到的是WeChat Crash的堆棧符號化信息 可以看到
EXC_CRASH (SIGKILL)
Code 0x8badf00d
應該是被系統(tǒng)watchdog殺掉了 后面再說如何分析符號化信息
三拟蜻、登陸developer開發(fā)者賬號獲取項目版本線上crash信息
Step1:同樣先打開頂部條上的Window 選擇Organizer
Step2:進入Developer開發(fā)項目打包信息看板 選擇Crashes tab
選擇我們線上打包的項目 選擇Release版本查看Crash信息
Tips:如果不是用你的電腦打包的 可以讓打包的同事把.xcarchive
給你 雙擊打開就會倒入到這里
Step3:上面這張圖可以看到我們選擇了一個FMDB 數(shù)據庫操作引起的Crash 點進去查看最近兩周發(fā)生了29次Crash 分別發(fā)生在什么類型的設備上 而且有詳細的堆棧信息和方法調用列表
Step4:我們可以把鼠標移動到我們想查看的方法上點擊后面的小箭頭 open crash in project 選擇你的項目去打開直接回定位到項目中的代碼塊 方便快捷直接
Tips:定位的地方可能不準確 需要當前文件跟上線代碼保持一致 才回定位準確
四绎签、集成友盟、Bugly或其他第三方handle了Crash數(shù)據
一般項目中都會集成第三方SDK幫助我們統(tǒng)計數(shù)據和Crash信息
如果你的項目中集成了三方工具來收集Crash信息 可以去三方提供的后臺去查看 會有類似于Xcode的上下文堆棧信息列表 借助于對方的符號化工具定位代碼中的問題也是比較方便的一種方式 在這里就不多說了
Orz...待補充中...
解決問題
符號化信息解讀
建議先看一下官方崩潰信息分析文檔 了解和分析應用程序崩潰報告 你會對Crash信息的獲取和分析有一個比較深刻的理解 下面列舉幾種常見的Crash符號化信息和對應的字段意思
設備信息
Incident Identifier: AF4F2C83-8F68-47EF-B5AA-F16B067B5DF4 // crash的ID
CrashReporter Key: 5670de85ee1f0f3c904891536e81ec086ed4b35b // crash 的設備ID
Hardware Model: iPhone8,1 // 手機的型號 (iPhone8,1代表iPhone6s 8,2 代表iPhone6s Plus)
Process: kidneyUser [896] // App的名稱 (該App的進程ID)
Path: /private/var/containers/Bundle/Application/48C71AA1-EB99-49B1-ABD7-2903DBA8E394/kidneyUser.app/kidneyUser // APP 的位置 路徑
Identifier: kidneyDiseaseHospitalUser // bundle ID
Version: 1 (1.0) // APP的版本號
Code Type: ARM-64 (Native) // app的應用架構
Parent Process: launchd [1]
Date/Time: 2016-05-05 10:45:43.43 +0800 // crash發(fā)生的時間
Launch Time: 2016-05-05 10:42:07.07 +0800 // 進入應用的時間
OS Version: iOS 9.3.1 (13E238) // iOS系統(tǒng)的版本
Report Version: 105
如果產品上線之后, 回收集大量的Crash Log日志文件, 可以對Crash文件里面的手機型號,版本號, 手機型號, iOS系統(tǒng)版本,進行分類, 可以獲得更多的信息, 更好的解決bug甚至未知的bug具體原因, 做更好的測試
異常信息
Exception Type: EXC_CRASH (SIGABRT) // 異常的類型
Exception Codes: 0x0000000000000000, 0x0000000000000000 // 異常出錯的代碼
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d//終止原因
Termination Description: SPRINGBOARD, scene-create watchdog transgression: com.tencent.xin exhausted CPU time allowance of 2.48 seconds | | ProcessVisibility: Background | ProcessState: Running | WatchdogEvent: scene-create | WatchdogVisibility: Background | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 10.920 (user 10.920, system 0.000), 65% CPU", | "Elapsed application CPU time (seconds): 5.897, 35% CPU" | )//終斷信息描述
Exception Note: EXC_CORPSE_NOTIFY // 異常通知
Triggered by Thread: 0 // 異常發(fā)生的線程(0代表主線程, 其他為主線程)
進程信息
Filtered syslog:
None found
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 CoreFoundation 0x00000001834153c0 0x183329000 + 967616
1 CoreFoundation 0x00000001833492e8 0x183329000 + 131816
2 CoreFoundation 0x00000001833492e8 0x183329000 + 131816
...
6 CoreFoundation 0x0000000183349274 0x183329000 + 131700
7 Foundation 0x0000000183dacf90 0x183da1000 + 49040
8 UIKit 0x000000018d039758 0x18cff8000 + 268120
...
16 UIKit 0x000000018d14ae58 0x18cff8000 + 1388120
17 UIKit 0x000000018d1371b4 0x18cff8000 + 1307060
18 WeChat 0x00000001053c22e4 0x1029e0000 + 43918052
19 WeChat 0x00000001055106f8 0x1029e0000 + 45287160
20 WeChat 0x00000001054432b4 0x1029e0000 + 44446388
21 WeChat 0x0000000104f31c94 0x1029e0000 + 39132308
22 UIKit 0x000000018d04aee0 0x18cff8000 + 339680
...
28 UIKit 0x000000018d043770 0x18cff8000 + 309104
29 QuartzCore 0x00000001875e525c 0x1874c2000 + 1192540
`
Thread 1:
0 libsystem_pthread.dylib 0x0000000183093b04 0x183093000 + 2820
Thread 2 name: Dispatch queue: NSOperationQueue 0x10dc05910 (QOS: UNSPECIFIED)
Thread 2:
0 libsystem_kernel.dylib 0x0000000182ed3e08 0x182ed3000 + 3592
1 libsystem_kernel.dylib 0x0000000182ed3c80 0x182ed3000 + 3200
2 CoreFoundation 0x0000000183416e40 0x183329000 + 974400
3 CoreFoundation 0x0000000183414908 0x183329000 + 964872
4 CoreFoundation 0x0000000183334da8 0x183329000 + 48552
5 WeChat 0x00000001058784b4 0x1029e0000 + 48858292
6 CoreFoundation 0x0000000183476580 0x183329000 + 1365376
7 CoreFoundation 0x0000000183355748 0x183329000 + 182088
Thread 3:
0 libsystem_kernel.dylib 0x0000000182ef50f4 0x182ed3000 + 139508
1 libsystem_pthread.dylib 0x0000000183097c90 0x183093000 + 19600
2 mars 0x00000001087969e0 0x1085e8000 + 1763808
3 mars 0x00000001085f4148 0x1085e8000 + 49480
4 mars 0x00000001086353fc 0x1085e8000 + 316412
5 libsystem_pthread.dylib 0x0000000183095220 0x183093000 + 8736
6 libsystem_pthread.dylib 0x0000000183095110 0x183093000 + 8464
7 libsystem_pthread.dylib 0x0000000183093b10 0x183093000 + 2832
補充常見的Exception Codes代碼類型
Exception Codes: 常見代碼有以下幾種
0x8badf00d錯誤碼:Watchdog超時酝锅,意為“ate bad food”诡必。
0xdeadfa11錯誤碼:用戶強制退出,意為“dead fall”搔扁。
0xbaaaaaad錯誤碼:用戶按住Home鍵和音量鍵爸舒,獲取當前內存狀態(tài),不代表崩潰稿蹲。
0xbad22222錯誤碼:VoIP應用(因為太頻繁扭勉?)被iOS干掉。
0xc00010ff錯誤碼:因為太燙了被干掉苛聘,意為“cool off”剖效。
0xdead10cc錯誤碼:因為在后臺時仍然占據系統(tǒng)資源(比如通訊錄)被干掉,意為“dead lock”
異常代碼0x8badf00d指示應用程序已終止的iOS 因為看門狗超時發(fā)生焰盗,應用程序時間太長、終止咒林,或對系統(tǒng)時間作出相應熬拒。一個常見的原因是做在主線程上的同步聯(lián)網。無論操作是線程0上垫竞,需要搬到后臺線程澎粟,或處理方式不同,所以它不會阻止在主線程欢瞪。
補充常見的Exception Type異常類型的信息:
1活烙、EXC_BAD_ACCESS:此類型是最常見的crash, 通常用于訪問了不該訪問的內存導致的,一般EXC_BAD_ACCESS后面的()還會帶有補充信息
野指針錯誤形式在Xcode中通常表現(xiàn)為:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)錯誤。因為你訪問了一塊已經不屬于你的內存遣鼓。
2啸盏、SIGSEGV:通常由于重復釋放對象導致, 一般在ARC以后很少見到
3、SIGABRT: 收到Abort信號退出, 通常Foundtion庫中的容器為了保護狀態(tài)正常會做一些檢測, 例如插入nil到數(shù)據中等會遇到此類錯誤.
4骑祟、SEGV(Segmentation Violation):代表無效內存地址, 比如空指針, 未初始化指針, 棧溢出等.
5回懦、SIGBUS:總棧錯誤, 與SIGSEGV不同的是, SIGSEGV訪問的是無效的地址, 而SIGBUS訪問的是有效的地址, 但是總棧訪問異常(如地址對齊問題)
6、SIGILL: 嘗試執(zhí)行非法的指令, 可能不被識別或者沒有權限
7次企、SIGFPE: 數(shù)學計算相關問題, 比如除零操作
8怯晕、SIGIPIPE: 管道另一端沒有進程接手數(shù)據
9、EXC_BAD_INSTRUCTION:此類異常通常由于線程執(zhí)行非法指令導致
10缸棵、EXC_ARITHMETIC:除零錯誤會拋出此類異常
Last Exception Backtrace: 最后異持鄄瑁回溯, 一般根據這個代碼就能找到具體的crash問題
下面截取的是微信的crash blog
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x000000018223ff24 __psynch_cvwait + 8
1 libsystem_pthread.dylib 0x000000018230ad20 _pthread_cond_wait + 704
2 Foundation 0x0000000182f9fdf0 -[NSCondition waitUntilDate:] + 344
3 Foundation 0x0000000182f9ce34 -[NSConditionLock lockWhenCondition:beforeDate:] + 256
4 UIKit 0x000000018781dbc4 -[UIKeyboardTaskQueue waitUntilAllTasksAreFinished] + 196
5 UIKit 0x0000000187c05878 -[UIKeyboardImpl setKeyboardInputMode:userInitiated:] + 112
6 UIKit 0x0000000187c0de44 -[UIKeyboardImpl recomputeActiveInputModesWithExtensions:] + 336
7 UIKit 0x000000018781e8f0 -[UIKeyboardImpl setDelegate:force:] + 2292
8 UIKit 0x0000000187817eb0 -[UIPeripheralHost(UIKitInternal) _reloadInputViewsForResponder:] + 1180
9 UIKit 0x00000001878179e4 -[UIResponder(UIResponderInputViewAdditions) reloadInputViews] + 80
10 UIKit 0x0000000187879670 -[UIResponder becomeFirstResponder] + 600
11 UIKit 0x0000000187879a1c -[UIView(Hierarchy) becomeFirstResponder] + 148
12 UIKit 0x0000000187900b34 -[UITextField becomeFirstResponder] + 64
13 UIKit 0x00000001879b1fe4 -[UITextInteractionAssistant(UITextInteractionAssistant_Internal) setFirstResponderIfNecessary] + 256
14 UIKit 0x00000001879b1498 -
我們可以看到發(fā)生Crash的線程的Crash調用棧, 從上到下分別代表調用順序, 最上面的一個表示拋出異常的位置, 一次往下可以看到API調用順序, 上圖的信息表明本次Crash出現(xiàn)在[NSCondition waitUntilDate:]這個方法中(后面加的數(shù)值 我猜應該是地址偏移量 大概可以找到crash的具體原因(某個文件中的某個方法), 這樣問題就浮出水面了, 方便產品上線后版本迭代, 修改BUG.
使用命令行工具symbolicatecrash
有時候Xcode不能夠很好的符號化crash文件。我們這里介紹如何通過symbolicatecrash來手動符號化crash log。
在處理之前吧凉,請依然將“.app“, “.dSYM”和 ".crash"文件放到同一個目錄下∷沓觯現(xiàn)在打開終端(Terminal)然后輸入如下的命令:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
然后輸入命令:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash appName.crash appName.app > appName.log
現(xiàn)在,符號化的crash log就保存在appName.log中了客燕。
Xcode自帶工具symbolicatecrash解析iOS Crash文件
防止Crash
除了日常代碼習慣良好嚴謹外 攔截存在潛在崩潰危險的方法鸳劳,在攔截的方法里進行相應的處理,也可以防止方法的崩潰
- 1也搓、通過category給類添加方法用來替換掉原本存在潛在崩潰的方法赏廓。
- 2、利用runtime方法交換傍妒,將系統(tǒng)方法替換成我們給類添加的新方法幔摸。
- 3、利用異常的捕獲來防止程序的崩潰颤练,并且進行相應的處理既忆。
- 如果對異常NSException不了解,可以點擊查看NSException的介紹嗦玖。
解析符號化信息