前言:crash log 對 定位崩潰問題 ,並且不容易復現,尤其是及時對appstore 上正在運營的 app 的迭代改進來說 非常重要.
1 crash兩種情況
1.1 測試環境下 追蹤bug
1.2 App Store 上應用 追蹤bug
我們主要討論在App Store 上應用 追蹤bug 的情況
2獲取crash log信息途徑
2.1自己收集,做錯誤分析 錯誤趨勢:
收集崩潰信息 存儲 上傳服務器 (時機可以是再一次打開應用時候同步)
方法:
// 將系統提供的獲取崩潰信息函數 封裝成CrashExceptioinCatcher類方法
//.h #import <Foundation/Foundation.h> @interface CrashExceptioinCatcher : NSObject + (void)startCrashExceptionCatch; @end //.m #import "CrashExceptioinCatcher.h" // 提交異常Log信息 void uncaughtExceptionHandler(NSException *exception) { // 異常Log信息// TODO: 提交服務器收集 // .... // NSArray *arr = [exception callStackSymbols]; // NSString *reason = [exception reason]; // NSString *name = [exception name];
//除了可以選擇寫到應用下的某個文件,通過后續處理將信息發送到服務器等 //不建議發送郵件形式告知程序人員,當數據量足夠大,很可能就被當成垃圾郵件屏蔽了//或者調用某個處理程序來處理這個信息也可 但是 要慎重,有時候 閃退本身就是一種保護機制,錯誤不能繼續錯誤下去 到此就應該戛然而止 }
@implementation CrashExceptioinCatcher + (void)startCrashExceptionCatch { // Sets the top-level error-handling function where you can perform last-minute logging before the program terminates. NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler); //設置異常Log信息的處理 } //該類方法需要在 “didFinishLaunchingWithOptions”里面調就開始調用,因為我們需要在應用的整個生命周期中都有機會獲取crash log 的機會,雖然我們希望自己的程序完美
[CrashExceptioinCatcher startCrashExceptionCatch];
2.2Xcode 工具
Xcode-Devices中直接查看”測試機 設備的crash log”
在手機 設置-> 診斷與用量-> 診斷與用量數據 里面 有各種應用的crash log,
獲取方式:
手機連接電腦, xcode中 Window->Devices->xxx 的iPhone 明細欄目中 有 View Device log 按鈕 點擊即手機app中的crash log
如圖1
圖2 crash log 源碼(我手機里 選中的這個 是微信的log
)
2.3蘋果官方提供的crash log崩潰收集服務
即”獲取用戶的 crash log”
這個是需要用戶配合的,因為需要用戶在手機 中 設置-> 診斷與用量->勾選 自動發送 ,然后在xcode中 Window->Organizer->Crashes 對應的app,就是當前app最新一版本的crash log ,並且是解析過的,可以根據crash 棧 等相關信息 ,尤其是程序代碼級別的 有超鏈接,一鍵可以直接跳轉到程序崩潰的相關代碼,這樣更容易定位bug出處.
參見圖3
2.4第三方:
2.4.1 友盟”錯誤趨勢 錯誤分析”
這個我正在用,crash log 可讀性差,需要配合.dSYM文件 定位可能報錯代碼位置(下面會詳細介紹crash log文件和.dSYM文件 是什么 怎么用)
網上的確有很牛逼的人,人家直接寫了mac程序dSYMTool(具體使用參見參考資料(3))
簡單說就是,我們每次迭代的應用時候,對應版本的.xcarchive文件要保存好備份,配合工具使用,當UUID和友盟里日志一致時候,填寫錯誤信息內存地址,就可得到具體的代碼行,即潛在的錯誤出處.
2.4.2 Fabric 崩潰檢查
這個我也正在用,它的 crash log直接可讀,直接定位可能報錯代碼位置 (此時沒使用友盟錯誤統計)
小結:
不論是自己收集crash還是靠第三方收集,一般一個應用程序只應該有一處調用crash統計,很多第三方為了使自己能夠盡可能全面統計crash會對NSSetUncaughtExceptionHandler()函數指針的惡意覆蓋,因為這個函數是將函數地址當做參數傳遞,所以只要重復調用就會被覆蓋,這樣就不能保證崩潰收集的穩定性。
2.5 解析Crash文件(Xcode 符號化 和 命令行解析)
2.5.1 xcode 符號化 crash log
准備三個文件 (1)dSYM (appName.app.dSYM) 文件是
(2).crash 原始crash log 文件
(3) appName.app 應用程序文件 在ipa文件中
因為 每一個xxx.app.dSYM,xx.app文件都擁有相應的uuid 當然 .crash文件也有uuid,只有三者uuid一至才表明之三者可以解析出正確的日志文件 所以 這三個文件
uuid 當用戶刪除 應用后 重裝 理論上 uuid 是會變化的, 現在可以通過"keychain"方式 存儲, 此時當應用被刪除,當應用重新安裝后 uuid仍然被保存可讀取,除非刷機或者重新安裝系統uuid才會變.(目前這是最值得用的一個重要參數,udid 包含太多硬件和用戶信息,暴露會影響信息安全,所以被禁用,不允許上架的) 參考(5)
三個文件放到一個文件中,然后打開 View Device logs 把 .crash文件拖進去,然后點擊右鍵re- symbolicate log 進行解析,過一會 crash log 即可讀
2.5.2 命令行解析 (一般少有 用2.5.1 和 第三方 十分普遍)
(1)使用symbolicatecrash手動解析 目前 暫沒.crash文件 稍后補充 實踐經驗
(2)atos 命令 解析,最簡單的是 在獲取符號化后的crash log
拿到 錯誤地址 執行:
a. cd /Users/hr-zhjh/Library/Developer/Xcode/Archives/2017-01-18/appName\ 17-1-18\ 上午12.13.xcarchive/dSYMs/appName.app.dSYM/Contents/Resources/DWARF
b. atos -arch arm64 -o dailylife 0x1741ff200
結果:-[AHFSkuMessageView textFieldShouldReturn:] (in dailylife) (AHFSkuMessageView.m:50)
atos -arch armv7 dailylife 0x43f21
結果:-[AHFSkuMessageView setMessageViewWithArray:] (in dailylife) (AHFSkuMessageView.m:106)
(3) dwarfdump 命令
命令:dwarfdump --arch=armv7 --lookup 0x43f21 /Users/hr-zhjh/Library/Developer/Xcode/Archives/2017-01-18/appName\ 17-1-18\ 上午12.13.xcarchive/dSYMs/Xxxx.app.dSYM
這樣就可以去查查 錯誤詳細原因了
3 解讀 crash log 信息
未經過二次分析解讀的crash log可讀性差,參見圖2.
dYSM文件是iOS編譯后保存16進制函數地址映射信息的中轉文件,每次應用程序build或者 archive后,都會生成對應的xxx.app, xxx.app.dSYM文件。(所以說 每次封包 xxx.app, xxx.app.dSYM 都是需要備份的文件,方便追溯crash)
xx.app.dSYM 文件和 友盟 crash log都有自己的 UUID 當二者一致 那么crash 的堆棧信息就一致,查出來的錯誤內存地址轉化成的代碼行才相對更准確
轉化成可讀的"crash log"如下(參見圖 4)
(1)設備信息和 crash 信息
例如:
Incident Identifier: 270D46C0-99F3-407D-A08A-306865B7C1A4 //crash的id CrashReporter Key: 597b2e336c5ec7000f1016b56ccba018f7d5960d //crash的設備id Hardware Model: iPhone7,1 //手機型號 Process: [AppName] [6621] //APP的名字[進程的id] Path: /private/.../Application.../WeChat //APP的位置 Identifier: com.tencent.xin //bundle ID Version: 6.5.3.32 (6.5.3) //版本號 Code Type: ARM-64 (Native) //app的應用架構之類 一些資料講 如果是64 則是程序bug 如果是armv7 就是友盟端錯誤... Parent Process: launchd [1]
Coalition: com.tencent.xin [2043] Date/Time: 2017-02-08 15:26:31.8885 +0800 //crash發生時間 Launch Time: 2017-02-08 15:26:21.3045 +0800 //進入應用時間 OS Version: iOS 10.0.1 (14A404) //iOS版本 Report Version: 105
通常第三方,會把 錯誤時間 硬件機型都統計出來了 給個扇形 折線 條形統計圖鮮明表現出來告知我們, 如果是我們自己做錯誤收集,我們就應該實際需求歸類處理這些
對自己有用的數據.
(2)異常信息
常見異常類型信息
1、Exception Type
1)EXC_BAD_ACCESS 此類型的Excpetion是我們最長碰到的Crash,通常用於訪問了不改訪問的內存導致。一般EXC_BAD_ACCESS后面的"()"還會帶有補充信息。 SIGSEGV: 通常由於重復釋放對象導致,這種類型在切換了ARC以后應該已經很少見到了。 SIGABRT: 收到Abort信號退出,通常Foundation庫中的容器為了保護狀態正常會做一些檢測,例如插入nil到數組中等會遇到此類錯誤。 SEGV:(Segmentation Violation),代表無效內存地址,比如空指針,未初始化指針,棧溢出等; SIGBUS:總線錯誤,與 SIGSEGV 不同的是,SIGSEGV 訪問的是無效地址,而 SIGBUS 訪問的是有效地址,但總線訪問異常(如地址對齊問題, 它之所以稱為總線錯誤是因為對未對齊的內存訪問時,被阻塞的組件就是地址總線) SIGILL:嘗試執行非法的指令,可能不被識別或者沒有權限 2)EXC_BAD_INSTRUCTION 此類異常通常由於線程執行非法指令導致 3)EXC_ARITHMETIC 除零錯誤會拋出此類異常 2、Exception Code 0xbaaaaaad 此種類型的log意味着該Crash log並非一個真正的Crash,它僅僅只是包含了整個系統某一時刻的運行狀態。通常可以通過同時按Home鍵和音量鍵,可能由於用戶不小心觸發 0xbad22222當VOIP程序在后台太過頻繁的激活時,系統可能會終止此類程序 0x8badf00d這個前面已經介紹了,程序啟動或者恢復時間過長被watch dog終止 0xc00010ff程序執行大量耗費CPU和GPU的運算,導致設備過熱,觸發系統過熱保護被系統終止 0xdead10cc程序退到后台時還占用系統資源,如通訊錄被系統終止 0xdeadfa11前面也提到過,程序無響應用戶強制關閉
crash log:
Exception Type: EXC_CRASH (SIGKILL) //異常的類型 Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000118 //異常子類型
我手機中 WeChat展示形式如下: Exception Code: 0x0000000000000000, 0x0000000000000000 //異常地址 Exception Note: EXC_CORPSE_NOTIFY//描述 Termination reason:Namespace SPRINGBOARD, Code 0x8badf00d //終止原因 Triggered by Thread: 0 //異常發生的線程(0為主線程,其他為子線程)
(3) 線程回溯(backtrace),線程信息,crash 棧
上面(2)異常信息 描灰處 相對最關鍵定位錯誤類型 方向信息 那么(3) 錯誤棧里面就是定位錯誤出處的
簡單分析一下:
(1) 以下做標記處並沒展示出app執行代碼 但是有地址 也是值得查查 該代碼映射到哪里,這個應該用 dSYM 查一下
(2)thread 1 基本是UI層級渲染相關的地址 深度渲染 影響開啟進程???
(3) Code 0x8badf00d 意思是 "程序啟動或者恢復時間過長被watch dog終止"
"The exception code 0x8badf00d indicates that an application has been terminated by iOS because a watchdog timeout occurred. The application took too long to launch, terminate, or respond to system events. One common cause of this is doing synchronous networking on the main thread. Whatever operation is on Thread 0: needs to be moved to a background thread, or processed differently, so that it does not block the main thread." 參見 參考鏈接4
那么問題就很簡單了,就是應用重新被開啟時間過長/終止/響應系統行為. 這種類似的事情應該放到父線程中操作,不應該放在主線程中阻塞主線程.
這里猜測"開啟應用過程中 一些處理數據在主線程中了或者 做了一些耗費性能的離屏幕渲染的行為等 再根據dSYM定位的代碼 理論上就可以明確錯誤了"
Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0 Crashed: 0 libsystem_malloc.dylib 0x000000018cf7cb7c szone_size + 544 1 libsystem_malloc.dylib 0x000000018cf7c7a4 free + 220 2 WeChat 0x0000000100af5924 0x10001c000 + 11376932 3 WeChat 0x0000000100aec7f0 0x10001c000 + 11339760 4 WeChat 0x0000000102798558 0x10001c000 + 41403736 5 WeChat 0x0000000100af3274 0x10001c000 + 11367028 6 WeChat 0x0000000100af17c8 0x10001c000 + 11360200 7 WeChat 0x0000000100af4128 0x10001c000 + 11370792 8 WeChat 0x0000000100af3e80 0x10001c000 + 11370112 9 WeChat 0x000000010273afec 0x10001c000 + 41021420 10 WeChat 0x000000010273b7dc 0x10001c000 + 41023452 11 WeChat 0x0000000100acf36c 0x10001c000 + 11219820 12 WeChat 0x000000010264dd8c 0x10001c000 + 40050060 13 WeChat 0x000000010264d9c8 0x10001c000 + 40049096 14 WeChat 0x0000000101e16d04 0x10001c000 + 31436036 15 WeChat 0x0000000101e11eb4 0x10001c000 + 31415988 16 UIKit 0x0000000193dbd738 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1196 17 QuartzCore 0x000000019128640c -[CALayer layoutSublayers] + 14 18 QuartzCore 0x000000019127b0e8 CA::Layer::layout_if_needed(CA::Transaction*) + 292 19 UIKit 0x0000000193dd21a8 -[UIView(Hierarchy) layoutBelowIfNeeded] + 1020 20 UIKit 0x0000000193dce480 +[UIView(Animation) performWithoutAnimation:] + 104 21 UIKit 0x0000000194103728 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 1072 22 UIKit 0x00000001941037f4 -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 80 23 UIKit 0x00000001940f0d9c -[UITableView _updateVisibleCellsNow:isRecursive:] + 2304 24 UIKit 0x0000000194108858 -[UITableView _performWithCachedTraitCollection:] + 116 25 UIKit 0x0000000193ea4d04 -[UITableView layoutSubviews] + 176 26 WeChat 0x000000010264a078 0x10001c000 + 40034424 27 UIKit 0x0000000193dbd738 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1196 28 QuartzCore 0x000000019128640c -[CALayer layoutSublayers] + 148 29 QuartzCore 0x000000019127b0e8 CA::Layer::layout_if_needed(CA::Transaction*) + 292 30 UIKit 0x0000000193dd21a8 -[UIView(Hierarchy) layoutBelowIfNeeded] + 1020 31 UIKit 0x0000000193e7a620 -[UINavigationController _layoutViewController:] + 1196 32 UIKit 0x0000000193e77f04 -[UINavigationController _layoutTopViewController] + 228 33 UIKit 0x0000000193e90e5c -[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:] + 760 34 UIKit 0x0000000193e90b28 -[UINavigationTransitionView _notifyDelegateTransitionDidStopWithContext:] + 420 35 UIKit 0x0000000193e90780 -[UINavigationTransitionView _cleanupTransition] + 724 36 UIKit 0x0000000193df8d38 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312 37 UIKit 0x0000000193df6f14 +[UIViewAnimationState popAnimationState] + 324 38 UIKit 0x0000000193e8400c -[UINavigationTransitionView transition:fromView:toView:] + 1972 39 UIKit 0x0000000193e79d5c -[UINavigationController _startTransition:fromViewController:toViewController:] + 2572 40 UIKit 0x0000000193e78e28 -[UINavigationController _startDeferredTransitionIfNeeded:] + 856 41 UIKit 0x0000000193e789dc -[UINavigationController __viewWillLayoutSubviews] + 64 42 UIKit 0x0000000193e78940 -[UILayoutContainerView layoutSubviews] + 188 43 UIKit 0x0000000193dbd738 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1196 44 QuartzCore 0x000000019128640c -[CALayer layoutSublayers] + 148 45 QuartzCore 0x000000019127b0e8 CA::Layer::layout_if_needed(CA::Transaction*) + 292 46 QuartzCore 0x000000019127afa8 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32 47 QuartzCore 0x00000001911f7c64 CA::Context::commit_transaction(CA::Transaction*) + 252 48 QuartzCore 0x000000019121f0d0 CA::Transaction::commit() + 512 49 QuartzCore 0x000000019121faf0 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 120 50 CoreFoundation 0x000000018df257dc __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 51 CoreFoundation 0x000000018df2340c __CFRunLoopDoObservers + 372 52 CoreFoundation 0x000000018de52068 CFRunLoopRunSpecific + 476 53 UIKit 0x0000000193e2b7cc -[UIApplication _run] + 608 54 UIKit 0x0000000193e26550 UIApplicationMain + 208 55 WeChat 0x00000001000a90ec 0x10001c000 + 577772 56 libdyld.dylib 0x000000018ce345b8 start + 4 Thread 1: 0 libsystem_kernel.dylib 0x000000018cf46a88 __workq_kernreturn + 8 1 libsystem_pthread.dylib 0x000000018d00936c _pthread_wqthread + 1452 2 libsystem_pthread.dylib 0x000000018d008db4 start_wqthread + 4 Thread 2: 0 libsystem_kernel.dylib 0x000000018cf46a88 __workq_kernreturn + 8 1 libsystem_pthread.dylib 0x000000018d00936c _pthread_wqthread + 1452 2 libsystem_pthread.dylib 0x000000018d008db4 start_wqthread + 4 Thread 3 name: Dispatch queue: NSOperationQueue 0x1090b5900 :: NSOperation 0x1090f2b30 (QOS: USER_INTERACTIVE) Thread 3: 0 libsystem_kernel.dylib 0x000000018cf2816c mach_msg_trap + 8 1 libsystem_kernel.dylib 0x000000018cf27fdc mach_msg + 72 2 CoreFoundation 0x000000018df25cec __CFRunLoopServiceMachPort + 192 3 CoreFoundation 0x000000018df23908 __CFRunLoopRun + 1132 4 CoreFoundation 0x000000018de52048 CFRunLoopRunSpecific + 444 5 WeChat 0x00000001029dbac4 0x10001c000 + 43776708 6 CoreFoundation 0x000000018df7e160 __invoking___ + 144 7 CoreFoundation 0x000000018de71c3c -[NSInvocation invoke] + 284 8 Foundation 0x000000018ea39c98 -[NSInvocationOperation main] + 40 9 Foundation 0x000000018e96e954 -[__NSOperationInternal _start:] + 620 10 Foundation 0x000000018ea3bb90 __NSOQSchedule_f + 228 11 libdispatch.dylib 0x000000018ce011c0 _dispatch_client_callout + 1 Thread 3 name: KSCrash Exception Handler (Secondary) Thread 3: 0 libsystem_kernel.dylib 0x000000018cf2816c mach_msg_trap + 8 1 libsystem_kernel.dylib 0x000000018cf27fdc mach_msg + 72 2 libsystem_kernel.dylib 0x000000018cf2c28c thread_suspend + 76 3 WeChat 0x000000010041dcf0 0x10001c000 + 4201712 4 libsystem_pthread.dylib 0x000000018d00b860 _pthread_body + 240 5 libsystem_pthread.dylib 0x000000018d00b770 _pthread_body + 0 6 libsystem_pthread.dylib 0x000000018d008dbc thread_start + 4 0x00000001947a0e6c -[UIEventFetcher thr ..... EOF
參考資源:
(1)http://www.cocoachina.com/ios/20151218/14748.html
(2)http://www.jianshu.com/p/12a2402b29c2
(3)http://www.cocoachina.com/ios/20141219/10694.html
(4)http://stackoverflow.com/questions/18996287/app-crash-in-the-device-exception-0x8badf00d
(5)http://www.jianshu.com/p/2741f0124cd3