如何得到crash report
當一個iOS應用程序崩潰時, 系統會創建一份crash日志保存在設備上. 這份crash日志記錄著應用程序崩潰時的信息, 通常包含著每個執行線程的棧調用信息(低內存閃退日志例外). 如果設備就在身邊, 可以連接設備, 打開Xcode - Window - Organizer, 在左側面板中選擇Device Logs(可以選擇具體設備的Device Logs或者Library下所有設備的Device Logs), 然后根據時間排序查看設備上的crash日志. 這是開發, 測試階段最經常采用的方式.
如果應用程序已經提交到App Store發布, 用戶已經安裝使用了, 那么開發者可以通過iTunes Connect(Manage Your Applications - View Details - Crash Reports)獲取用戶的crash日志. 不過這并不是100%有效的, 因為這需要用戶設備同意上傳相關信息.
線上app的崩潰日志會被app store收集并符號化分組. 類似的崩潰報告的集合被稱為崩潰點, (如果用戶選擇了與蘋果共享診斷數據, 這些崩潰日志才會被收集并被符號化). 打開Xcode - Window - Organizer, 在點擊相應應用后, 會顯示此應用的崩潰集合. 可以看到每一個集合中都會有很多個設備, 如果右鍵進去查看的話, 會看到很多文件. 右鍵顯示包內容, 會看到最終的詳細日志, 當選中了一個崩潰集合后, 如果選擇在項目中打開, 會在項目代碼中找到具體出問題的代碼. 選中Open in Project的話, 會直接在工程中打開.
如果用戶反饋應用曾虧, 也可以通過讓用戶設備與iTunes同步, 設備與電腦上的iTunes Store同步后, 會將崩潰日志保存在電腦上(路徑:Mac OS X:~/Library/Logs/CrashReporter/MobileDevice/)到上述位置把崩潰日志下載下來, 然后通過電子郵件發送給你.
通過第三方工具來獲取崩潰信息.
如何得到.dSYM
我們在Archive的時候會生成.xcarchive文件, 顯示包內容就能夠在里面找到.dSYM文件和.app文件. .dSYM文件位于 /Users/<用戶名>/Library/Developer/Xcode/Archives 目錄下, 在目錄中包含了一個16進制的保存函數地址映射信息的中轉文件, 所有Debug文件的symbols都在這個文件中(包含文件名, 函數名, 行號等), 也稱之為調試符號信息文件.
當我們軟件 release 模式打包或上線后, 不會像我們在 Xcode 中那樣直觀的看到用崩潰的錯誤, 這個時候我們就需要分析 crash report 文件了, iOS 設備中會有日志文件保存我們每個應用出錯的函數內存地址, 通過 Xcode 的 Organizer 可以將 iOS 設備中的 DeviceLog 導出成 crash 文件, 這個時候我們就可以通過出錯的函數地址去查詢 dSYM 文件中程序對應的函數名和文件名. 大前提是我們需要有軟件版本對應的 dSYM 文件, 這也是為什么我們很有必要保存每個發布版本的 Archives 文件了.
每一個 xx.app 和 xx.app.dSYM 文件都有對應的 UUID, crash 文件也有自己的 UUID, 只要這三個文件的 UUID 一致, 我們就可以通過他們解析出正確的錯誤函數信息了.
通過dwarfdump --uuid xx.app/xx (xx代表你的項目名)查看xx.app文件的UUID
通過dwarfdump --uuid xx.app.dSYM查看xx.dSYM的UUID
crash 文件內第一行 Incident Identifier 就是該 crash 文件的 UUID.
結合分析crash文件
根據crash report, .dSYM分析崩潰函數.
如果使用的是友盟的話, 友盟自帶的有一個分析工具. 但是要注意, 使用的時候要確保你的.xcarchive在 ~/Library/Developer/Xcode/或該路徑的子目錄下. .xcarchive里的.dsYM文件和.app文件是有對應的UUID的. 然后你的crash report里也是有UUID, 只有當UUID相等時才能分析對. 如果是在別人電腦上archive, 那你需要把.dSYM文件copy過來.
symbolicatecrash是xcode的一個符號化crash log的命令行工具. 使用方法也就是導出.crash文件(crash log)和找到.dsYM文件, 然后進行分析.?查看這里
如果你有多個“.ipa”文件, 多個”.dSYMB”文件, 你并不太確定到底“dSYM”文件對應哪個”.ipa”文件, 可以使用命令行工具atos.?查看這里
崩潰日志分析
Xcode->Window->Organizer->Crashes,?這里有關于崩潰日志的詳細分析.
盜圖一張, 關于崩潰日志的詳細信息
野指針分析
因為野指針的原因發生崩潰是常常出現的事, 而且比較隨機. 所以我們要提高野指針的崩潰率好來幫我們快速找到有問題的代碼. 對象釋放后只有出現被隨機填入的數據是不可訪問的時候才會必現Crash.
這個地方我們可以做一下手腳, 把這一隨機的過程變成不隨機的過程. 對象釋放后在內存上填上不可訪問的數據, 其實這種技術其實一直都有, Edit Scheme -> Diagnostics ->Enable Malloc Scribble 選中就可以實現這個功能
僵尸模式分析
啟用了NSZombieEnabled, 它會用一個僵尸來替換默認的dealloc實現, 也就是在引用計數降到0時, 該僵尸實現會將該對象轉換成僵尸對象. 僵尸對象的作用是在你向它發送消息時, 它會顯示一段日志并自動跳入調試器. 當啟用NSZombieEnabled時, 一個錯誤的內存訪問就會變成一條無法識別的消息發送給僵尸對象. 僵尸對象會顯示接受到得信息, 然后跳入調試器, 這樣你就可以查看到底是哪里出了問題.
一般這時崩潰的原因就是因為調用了已經釋放的內存空間,或者說重復釋放了某個地址空間.
打開NSZombieEnabled之后, 如果遇到對應的崩潰類型既調用了已經釋放的內存空間, 或者說重復釋放了某個地址空間, 那么就能在GDB中看到對應的輸出信息.
如果崩潰是發生在當前調用棧, 通過上面的做法, 系統就會把崩潰原因定位到具體代碼中. 但是, 如果崩潰不在當前調用棧, 系統就僅僅只能把崩潰地址告訴我們, 而沒辦法定位到具體代碼, 這樣我們也沒法去修改錯誤. 這時就可以修改scheme, 讓xcode記錄每個地址alloc的歷史, 這樣我們就可以用命令把這個地址還原出來. Edit Scheme -> Environment Variables -> 加入MallocStackLoggingNoCompact, 并且設置為YES.
Enable Address Sanitizer
Edit Scheme -> Diagnostics ->Enable Address Sanitizer選中就可以實現這個功能, 設置這個參數后, 我們可以看到一些更詳細的錯誤信息提示, 設置還有內存使用情況的展示.
這類工具的理論依據是: 訪問內存時, 通過比較訪問的內存和程序實際分配的內存, 驗證內存訪問的有效性, 從而在bug發生時就檢測到它們, 而不會等到副作用產生時才有所察覺.
靜態分析
Static Analyzer是一個非常好的工具去發現編譯器警告不會提示的問題和一些個人的內錯泄露和死存儲(不會用到的賦了值的變量)錯誤. 這個方法可能大大的提高內存使用和性能, 以及提升應用的整體穩定性和代碼質量.
打開方式: Xcode->Product-Analyze 然后我們就能看到如下藍色箭頭所示的一些有問題的代碼.
unrecognized selector send to instancd 快速定位
在debug navigator的斷點欄里添加Create Symbolic Breakpoint
在Symbolic中填寫如下方法簽名:?-[NSObject(NSObject) doesNotRecognizeSelector:]
常見崩潰信息類型
在iOS中就是未被捕獲的Objective-C異常(NSException)導致程序向自身發送了SIGABRT信號而崩潰, 常見的如下:
SIGSEGV: 段錯誤信息(SIGSEGV)是操作系統產生的一個更嚴重的問題。當硬件出現錯誤、訪問不可讀的內存地址或向受保護的內存地址寫入數據時,就會發生這個錯誤。硬件錯誤這一情況并不常見。當要讀取保存在RAM中的數據,而該位置的RAM硬件有問題時,你會收到SIGSEGV。SIGSEGV更多是出現在后兩種情況。默認情況下,代碼頁不允許進行寫操作,而數據而不允許進行執行操作。當應用中的某個指針指向代碼頁并試圖修改指向位置的值時,你會收到SIGSEGV。當要讀取一個指針的值,而它被初始化成指向無效內存地址的垃圾值時,你也會收到SIGSEGV SIGSEGV錯誤調試起來更困難,而導致SIGSEGV的最常見原因是不正確的類型轉換。要避免過度使用指針或嘗試手動修改指針來讀取私有數據結構。如果你那樣做了,而在修改指針時沒有注意內存對齊和填充問題,就會收到SIGSEGV。
SIGBUS: 總線錯誤信號(SIGBUG)代表無效內存訪問,即訪問的內存是一個無效的內存地址。也就是說,那個地址指向的位置根本不是物理內存地址(它可能是某個硬件芯片的地址)。SIGSEGV和SIGBUS都羽毛球EXC_BAD_ACCESS的子類型。
SIGTRAP: SIGTRAP代表陷阱信號。它并不是一個真正的崩潰信號。它會在處理器執行trap指令發送。LLDB調試器通常會處理此信號,并在指定的斷點處停止運行。如果你收到了原因不明的SIGTRAP,先清除上次的輸出,然后重新進行構建通常能解決這個問題。
EXC_ARITHETIC: 當要除零時,應用會收到EXC_ARITHMETIC信號。這個錯誤應該很容易解決.
SIGILL: SIGILL代表signal illegal instruction(非法指令信號)。當在處理器上執行非法指令時,它就會發生。執行非法指令是指,將函數指針會給另外一個函數時,該函數指針由于某種原因是壞的,指向了一段已經釋放的內存或是一個數據段。有時你收到的是EXC_BADINSTRUCTION而不是SIGILL,雖然它們是一回事,不過EXC*等同于此信號不依賴體系結構。
SIGABRT: SIGABRT代表SIGNAL ABORT(中止信號)。當操作系統發現不安全的情況時,它能夠對這種情況進行更多的控制;必要的話,它能要求進程進行清理工作。在調試造成此信號的底層錯誤時,并沒有什么妙招。Cocos2d或UIKit等框架通常會在特定的前提條件沒有滿足或一些糟糕的情況出現時調用C函數abort(由它來發送此信號)。當SIGABRT出現時,控制臺通常會輸出大量的信息,說明具體哪里出錯了。由于它是可控制的崩潰,所以可以在LLDB控制臺上鍵入bt命令打印出回溯信息
更多的SIGABRT信號類型看這里.
EXC_BAD_ACCESS
EXC_BAD_ACCESS是一個比較難處理的crash, 當一個app進入一種毀壞的狀態, 通常是由于內存管理問題而引起的時, 就會出現出現這樣的crash. 通常Signal信號錯誤都會提醒EXC_BAD_ACCESS, 我們可以通過僵尸模式來捕獲這種異常.
看門狗超時
這種崩潰通常比較容易分辨, 因為錯誤碼是固定的0x8badf00d. 在iOS上, 它經常出現在執行一個同步網絡調用而阻塞主線程的情況. 因此, 永遠不要進行同步網絡調用.
Exception Codes異常編碼
0x8badf00d: 讀做 “ate bad food”! (把數字換成字母,是不是很像 :p)該編碼表示應用是因為發生watchdog超時而被iOS終止的。 通常是應用花費太多時間而無法啟動、終止或響應用系統事件。
0xbad22222: 該編碼表示 VoIP 應用因為過于頻繁重啟而被終止。
0xdead10cc: 讀做 “dead lock”!該代碼表明應用因為在后臺運行時占用系統資源,如通訊錄數據庫不釋放而被終止 。
0xdeadfa11: 讀做 “dead fall”! 該代碼表示應用是被用戶強制退出的。根據蘋果文檔, 強制退出發生在用戶長按開關按鈕直到出現 “滑動來關機”, 然后長按 Home按鈕。強制退出將產生 包含0xdeadfa11 異常編碼的崩潰日志, 因為大多數是強制退出是因為應用阻塞了界面
#常見的崩潰類型收集
新老操作系統兼容
在新 iOS 上正常的應用, 到了老版本 iOS 上秒退最常見原因是系統動態鏈接庫或Framework無法找到. 這種情況通常是由于 App 引用了一個新版操作系統里的動態庫(或者某動態庫的新版本)或只有新 iOS 支持的 Framework, 而又沒有對老系統進行測試, 于是當 App 運行在老系統上時便由于找不到而秒退.
還有就是有些方法是新版操作系統才支持的, 而又沒有對該方法是否存在于老系統中做出判斷.
本地存儲的數據結構變化
程序在升級時, 修改了本地存儲的數據結構, 但是對用戶既存的舊數據沒有做好升級, 結果導致初始化時因為無法正確讀取用戶數據而秒退.
訪問的數據為空或者數據類型不對
這類情況是比較常見的, 后端傳回了空數據, 客戶端沒有做對應的判斷繼續執行下去了, 這樣就產生了crash. 或者自己本地的某個數據為空數據而去使用了. 還有就是訪問的數據類型不是期望的數據類型而產生崩潰.
操作了不該操作的對象
這里有可能是空指針或者野指針.
空指針是沒有存儲任何內存地址的指針, 一般指nil, 但是在iOS中向nil發消息是不會崩潰的.
野指針是指指向一個已刪除的對象(“垃圾”內存既不可用內存)或未申請訪問受限內存區域的指針. 野指針是比較危險的, 因為野指針指向的對象已經被釋放了, 不能用了, 你再給被釋放的對象發送消息就是違法的, 所以會崩潰.
內存處理不當
內存管理是軟件開發中一個重要的課題, iOS自從引入ARC機制后, 對于內存的管理開發者好像輕松了很多, 但是還會發生一些內存泄露之類的問題. Facebook工程師們開源了一些自動化工具來解決監測內存泄露問題:FBRetainCycleDetector、FBAllocationTracker、FBMemoryProfiler.
主線程UI長時間卡死, 被系統殺掉
主線程被卡住是非常常見的場景, 具體表現就是程序不響應任何的UI交互. 這時按下調試的暫停按鈕, 查看堆棧, 就可以看到是到底是死鎖, 死循環等, 導致UI線程被卡住.
多線程之間切換訪問引起的crash
多線程引起的崩潰大部分是因為使用數據庫的時候多線程同時讀寫數據庫而造成了crash.?這里關于多線程crash的調試.