iOS崩潰分析
寫在前面:本文會在最開頭將蘋果官方的文件Understanding and Analyzing Application Crash Reports進行翻譯,但這不僅僅是一篇翻譯的文章,本文會讓大家更加全面的瞭解ios的崩潰報告的獲取、分析、用途。翻譯的時候我會結合自己以往的使用經驗來進行翻譯。
理解和分析應用程式崩潰報告
重要提示:本文件包含有關開發中的一個介面或技術的初步資訊。此資訊將被更改,並根據本文件實現的軟體應該用最終的作業系統軟體進行測試。
當應用程式崩潰時,建立了一個崩潰報告,這是非常有用的瞭解什麼造成的崩潰。本檔案包含重要的資訊,如何symbolicate,理解和解釋的崩潰報告。
簡介
當應用程式崩潰時,將建立一個崩潰報告並存儲在裝置上。崩潰報告描述的情況下,應用程式終止,在大多數情況下,包括每個執行執行緒的一個完整的回溯,通常對於除錯應用中的問題是非常有用。你應該看看這些崩潰報告,瞭解你的應用程式有什麼崩潰,然後嘗試修復它們。
有回溯的崩潰報告需要被符號化了才可以分析。符號化(symbolication)成人們可讀的函式名稱和行號來取代記憶體地址。如果你通過Xcode的裝置視窗來獲取裝置的崩潰日誌,它們將在幾秒鐘後自動被符號化。否則,你將需要自己將崩潰日誌符號化,通過自己匯入崩潰檔案到Xcode裝置視窗。看到符號化(symbolicating)後的崩潰報告。
低記憶體報告不同於其他的崩潰報告,沒有回朔在這種型別的報告。當低記憶體崩潰發生時,你必須調查你的記憶體使用模式和對低記憶體警告的響應。此文件指向了您可能會發現有用的多個記憶體管理引用。
獲取崩潰和低記憶體報告
除錯部署iOS應用程式 討論如何直接從iOS裝置獲取崩潰和記憶體不足的報道。
在應用程式分發指南中的分析崩潰報告 討論如何從TestFlight beta測試者和那些已經從App Store下載應用程式的使用者中收集崩潰報告。
symbolicating崩潰報告
symbolication是解決回溯地址的原始碼的方法或函式名的過程,稱為符號化。沒有經過符號化的崩潰報告,是不能瞭解具體的崩潰資訊的。
注:低記憶體報告不需要symbolicated。
注:來自MacOS崩潰報告通常是已經符號化的,或部分符號化後的(symbolicated)。
本節重點symbolicating從iOS,WatchOS,和TVOS的崩潰報告,但整體過程類似MacOS。
圖1概述了崩潰報告和symbolication過程。
獲取崩潰報告以及符號化過程
1.當編譯器將原始碼翻譯成機器程式碼時,它也會生成除錯符號,它將編譯後的二進位制中的每一個機器指令對映到原始碼的行原始碼中。根據除錯資訊格式(debug_information_format)編譯設定,這些除錯符號儲存在二進位制或在同伴的除錯符號檔案(dsym)。預設情況下,除錯版本的應用程式的除錯符號儲存在編譯後的二進位制中,而釋出版本的應用程式的除錯符號儲存在相應的dsym文中件以減少二進位制大小。
除錯符號檔案和應用程式二進位制檔案與每一個build生成的UUID捆綁在一起。一個新的UUID生成是由build一個應用產生的,它應用程式每次build(編譯打包)的唯一標識。即使一個功能相同的可執行檔案是從相同的原始碼重構,具有相同的編譯器設定,也會有不同的生成的UUID。除錯符號檔案的後續版本,甚至來自同一個原始檔,不會與其他版本的二進位制檔案相混淆。
2.當你的歸檔(archive)要分發的應用程式,Xcode會收集應用程式二進位制隨著.dsym檔案,並且存放在home資料夾裡。你可以在Xcode的組織者在“歸檔”部分,找到所有你歸檔後(archived)的應用。有關建立存檔的更多資訊,可以參考應用程式分發指南。
重要:為了符號化(symbolicate)從測試人員,審查程式,和客戶得到的崩潰報告,你必須保留每個存檔您的應用程式。
3.如果你是通過App Store釋出的應用程式,或使test flight進行測試,你將選擇包括dsym檔案到iTunes Connect。在提交對話方塊中,選擇“您的應用程式的應用程式符號……”。為了接收來test flight收集的 以及 那些選擇了分享的診斷資料的客戶的崩潰報告,上傳你的dsym檔案是必要的 。有關崩潰報告服務的更多資訊,可以參考應用程式分發指南。
重要:從app review收到的崩潰報告將是unsymbolicated,即使你有上傳dsym檔案到iTunes Connect。
你需要使用Xcode 來symbolicate任何從app review得到的崩潰報告。
看下午中"到symbolicating崩潰報告Xcode"。
4.當你的應用程式崩潰時,一個unsymbolicated崩潰報告會被建立並存儲在裝置上。
5.使用者可以按照除錯部署iOS應用程式的步驟,直接從他們的裝置中檢索崩潰報告。如果你的應用程式通過AdHoc or Enterprise distribution釋出的,這是從你的使用者獲取崩潰報告的唯一途徑。
6.從裝置檢索得到的未符號化的(unsymbolicated)崩潰報告需要使用Xcode來符號化(symbolicated)。Xcode使用您的應用程式的二進位制在原始碼中的源位置的回溯替換每個地址相關聯的dsym檔案。其結果是一個符號化的(symbolicated)崩潰報告。
7.如果使用者選擇了與蘋果共享診斷資料,或者如果使用者通過TestFlight安裝了你的應用程式,崩潰報告會被上傳到應用商店。
8.App Store 符號化崩潰報告 並且將類似的崩潰報告分組。類似的崩潰報告的集合被稱為崩潰點。
9.符號化的崩潰報告是在Xcode's Crashes organizer提供給你的。
Bitcode
Bitcode是一個編譯程式的中間表示。當你可以用bitcode來 archive an application,編譯器產生的二進位制檔案包含bitcode而不是機器程式碼。一旦二進位制已經上傳到App Store,這可以被編譯成機器碼。App Store在將來,在沒有任何行動的一部分的情況下,利用未來的改進的編譯器,再次編譯。
Bitcode compilation process
因為你的二進位制最後的編譯出現在App Store,Mac將不包含符號化從應用程式審查或使用者發給你他們從裝置中取得的崩潰報告所需要的除錯符號檔案(.dsym)。雖然dsym檔案是您歸檔應用程式(archive your application)的時候產生的,它是為bitcode二進位制並不能用來symbolicate崩潰報告。App Store在你從Xcode或從iTunes Connect網站,可以獲得編譯的bitcode並且可以下載的過程中,產生dsym檔案。符號化從應用程式審查或使用者發給你他們從裝置中取得的崩潰報告,你必須下載這些dsym檔案。崩潰報告獲得通過崩潰報告服務將自動symbolicated。
重要:二進位制應用程式商店會比最初二進位制編譯,有不同的UUID
從Xcode獲取dSYMs檔案
1.下載dsyms檔案
在歸檔管理中選擇相應的歸檔並下載dsyms檔案
2.在歸檔出的檔案中找到dSYMs檔案
確定崩潰報告是否被符號化
符號化崩潰報告
1.符號化用xcode編譯安裝軟體的裝置上的崩潰報告
當應用在裝置上執行,遇到崩潰的時候會產生崩潰日誌。如果這個應用是用xcode直接在裝置上編譯執行的,那麼就可以將手機連線到編譯的時候所用的電腦上,開啟xcode,在Window的Devices中去檢視日誌。找到日誌的時候,xcode會自己去符號化崩潰日誌。
選取裝置
檢視裝置日誌
xcode自己去符號化崩潰檔案
2.符號化用安裝包安裝在測試裝置上的應用所產生的崩潰日誌
符號化的時候需要準備symbolicatecrash檔案 .dSYM檔案 以及.app檔案
符號化前先檢查一下三者的uuid是不是一致的,只有是一致的才能符號化成功。
檢視xx.app檔案的uuid的方法:
dwarfdump --uuid xxx.app/xxx (xxx工程名)
檢視xx.app.dSYM檔案的uuid的方法令:
dwarfdump --uuid xxx.app.dSYM (xxx工程名)
而.crash的uuid位於,crash日誌中的Binary Images:中的第一行尖括號內。如:armv7<8bdeaf1a0b233ac199728c2a0ebb4165>
將三種檔案拷貝到同一個目錄中,在終端中使用命令
./symbolicatecrash ./.crash ./.app.dSYM > xxx.crash來解析崩潰日誌。
如果想今後解析日誌的時候更方便一點,特別是解析多個崩潰日誌的時候,如果一個一個去解析的話,很花費時間的。我的話,會將這些寫到一個指令碼中,解析的時候就只用執行指令碼,就可以很方便快捷的獲取到崩潰日誌。
3.獲取並符號化線上的崩潰報告
一.通過打包上線時的xcode來獲取線上的崩潰報告
線上app的崩潰日誌會被app store收集並符號化分組。類似的崩潰報告的集合被稱為崩潰點。(如果使用者選擇了與蘋果共享診斷資料,這些崩潰日誌才會被收集並被符號化)
在點選相應應用後,會顯示此應用的崩潰集合。可以看到每一個集合中都會有很多個裝置,如果右鍵進去檢視的話,會看到很多檔案。
再次右鍵進去檢視的話,會最終看到詳細的崩潰日誌
當選中了一個崩潰集合後,如果選擇在專案中開啟,會在專案程式碼中找到具體出問題的程式碼
二、通過友盟等第三方工具來獲取崩潰日誌
崩潰日誌列表
其中的一個崩潰日誌
崩潰的程式碼的位置,這個是最關鍵的,可以通過這個來找到程式碼中的出問題的地方
export dSYMPath="$(find ~/Library/Developer/Xcode -iname '*.dSYM' -print0 | xargs -0 dwarfdump -u | grep C0349572-9622-3A00-81D0-4DDE0E00DC7A | sed -E 's/^{FNXX==XXFN}+//' | head -n 1)";是為了找到歸檔時候產生的dsym檔案的路徑
dwarfdump --arch=armv7 --lookup 0x426031 "$dSYMPath"是符號化的關鍵,可以找出出問題的地方。
分析符號化後的崩潰報告
1.頭部
每個崩潰報告都會以一個頭部開始
Listing 4 Excerpt of the header from a crash report. Incident Identifier: B6FD1E8E-B39F-430B-ADDE-FC3A45ED368C CrashReporter Key: f04e68ec62d3c66057628c9ba9839e30d55937dc Hardware Model: iPad6,8 Process: TheElements [303] Path: /private/var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements Identifier: com.example.apple-samplecode.TheElements Version: 1.12 Code Type: ARM-64 (Native) Role: Foreground Parent Process: launchd [1] Coalition: com.example.apple-samplecode.TheElements [402] Date/Time: 2016-08-22 10:43:07.5806 -0700 Launch Time: 2016-08-22 10:43:01.0293 -0700 OS Version: iPhone OS 10.0 (14A5345a) Report Version: 104
以下的值是特別值得注意的:
Incident Identifier: 一個唯一的標識. 不會存在共用一個標識的崩潰報告.
CrashReporter Key:是與裝置標識相對應的唯一鍵值。雖然它不是真正的裝置識別符號,但也是一個非常有用的情報:如果你看到100個崩潰日誌的CrashReporter Key值都是相同的,或者只有少數幾個不同的CrashReport值,說明這不是一個普遍的問題,只發生在一個或少數幾個裝置上。
Process: 是應用名稱。中括號裡面的數字是閃退時應用的程序ID。
Version: 崩潰程序的版本號. 這個值包含在 CFBundleVersion and CFBundleVersionString中.
Code Type: 崩潰日誌所在裝置的架構. 會是ARM-64, ARM, x86-64, or x86中的一個.
OS Version: 崩潰發生時的系統版本
異常資訊
異常資訊會列出異常的型別、位置。
以下的內容是摘錄的一個崩潰報告的異常程式碼段,該崩潰報告是一個程序由於一個未捕獲的異常而崩潰產生的。
Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY Triggered by Thread: 0
以下的內容是摘錄於一個因為空指標的訪問而崩潰產生的崩潰報告的異常程式碼段
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000 Termination Signal: Segmentation fault: 11 Termination Reason: Namespace SIGNAL, Code 0xb Terminating Process: exc handler [0] Triggered by Thread: 0
異常資訊中可能出現的欄位的解釋如下:
Exception Codes: 處理器的具體資訊有關的異常編碼成一個或多個64位進位制數。通常情況下,這個區域不會被呈現,因為將異常程式碼解析成人們可以看懂的描述是在其它區域進行的。
Exception Subtype: 供人們可讀的異常程式碼的名字
Exception Message: 從異常程式碼中提取的額外的可供人們閱讀的資訊.
Exception Note: 不是特定於一個異常型別的額外資訊.如果這個區域包含SIMULATED (這不是一個崩潰)然後程序沒有崩潰,但是被watchdog殺掉了
Termination Reason: 當一個程序被終止的時的原因。
Triggered by Thread: 異常所在的執行緒.
下面的小節將介紹一些最常見的異常型別。
Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
程序試圖訪問無效的記憶體,或試圖以記憶體的保護級別所不允許的方式去訪問記憶體(例如,寫入到只讀儲存器)。異常型別欄位(Exception Subtype)包含一個kern_return_t描述錯誤,和錯誤的訪問的記憶體地址。
這裡是除錯一個Bad Memory Access的一些小技巧:
1.如果objc_msgSend或者objc_release在回溯(Backtraces)的頂部附近,這個程序可能是嘗試給一個釋放的物件傳送訊息。你應該用Zombies instrument(除錯殭屍物件的工具)來更好的理解這個崩潰。
3.用地址消毒劑(Address Sanitizer,這是xcode7引入的新特性 )來跑程式。
The address sanitizer adds additional instrumentation around memory access in your compiled code. As your application runs, Xcode will alert you if memory is accessed in a way that could lead to a crash.
Abnormal Exit (異常退出)[EXC_CRASH // SIGABRT]
程序異常退出。該異常型別崩潰最常見的原因是未捕獲的Objective-C和C++異常和呼叫abort()。
如果他們需要太多的時間來初始化,程式將被終止,因為觸發了看門狗。如果是因為啟動的時候被掛起,所產生的崩潰報告異常型別(Exception Subtype)將是launch_hang。
Trace Trap (追蹤捕獲)[EXC_BREAKPOINT // SIGTRAP]
類似於異常退出,這個異常是為了給附加的偵錯程式中斷的過程的機會在其執行一個特定的點。您可以從您自己的程式碼引發此異常使用__builtin_trap()函式。如果沒有偵錯程式連線,程序將被終止並生成崩潰報告。
Illegal Instruction(非法指令) [EXC_BAD_INSTRUCTION // SIGILL]
程序試圖執行非法或未定義指令。這個程序可能試圖通過一個配置錯誤的函式指標,跳到一個無效的地址。在英特爾處理器,ud2操作碼造成EXC_BAD_INSTRUCTION異常但通常用於除錯的時候的追蹤。在英特爾處理器上的swift程式碼因為這個異常型別而被終止,如果在執行時遇到意外情況。有關詳細資訊,請參閱追蹤捕獲。
Resource Limit [EXC_RESOURCE]
這個程序超出了資源消耗的限制。這是一個從作業系統通知,程序是使用太多的資源。這雖然不是崩潰但也會生成崩潰日誌。
其它的異常資訊
0x8badf00d: 讀做 “ate bad food”! (把數字換成字母,是不是很像 :p)該編碼表示應用是因為發生watchdog超時而被iOS終止的。 通常是應用花費太多時間而無法啟動、終止或響應用系統事件。
0xbad22222: 該編碼表示 VoIP 應用因為過於頻繁重啟而被終止。
0xdead10cc: 讀做 “dead lock”!該程式碼表明應用因為在後臺執行時佔用系統資源,如通訊錄資料庫不釋放而被終止 。
0xdeadfa11: 讀做 “dead fall”! 該程式碼表示應用是被使用者強制退出的。根據蘋果文件, 強制退出發生在使用者長按開關按鈕直到出現 “滑動來關機”, 然後長按 Home按鈕。強制退出將產生 包含0xdeadfa11 異常編碼的崩潰日誌, 因為大多數是強制退出是因為應用阻塞了介面。
待補充
其它捕捉和符號化崩潰日誌的方法
1.通過程式碼來捕捉崩潰資訊,併發送到指定的地方
在專案的程式碼中重新指定頂層的錯誤處理的handler,這樣可以在軟體崩潰前的最後一秒呼叫錯誤處理的函式來獲得崩潰的資訊(這裡的資訊已經是被符號化的)。然後將崩潰資訊傳送到自己的伺服器或者其它的地方,以便開發者鞏固自己的程式碼的可靠性。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[AFNetworkReachabilityManager sharedManager] startMonitoring]; appDelegate = self; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; _notification = notification; NSSetUncaughtExceptionHandler(&caughtExceptionHandler); /*Changes the top-level error handler. Sets the top-level error-handling function where you can perform last-minute logging before the program terminates */ return YES; } void caughtExceptionHandler(NSException *exception){ /** * 獲取異常崩潰資訊 */ NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSString *content = [NSString stringWithFormat:@"========異常錯誤報告========\\nname:%@\\nreason:\\n%@\\ncallStackSymbols:\\n%@",name,reason,[callStack componentsJoinedByString:@"\\n"]]; //把異常崩潰資訊傳送至開發者郵件 NSMutableString *mailUrl = [NSMutableString string]; [mailUrl appendString:@"mailto:[email protected]"]; [mailUrl appendString:@"?subject=程式異常崩潰資訊,請配合傳送異常報告,謝謝合作!"]; [mailUrl appendFormat:@"&body=%@", content]; // 開啟地址 NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]]; }