WWDC 2018:理解崩潰以及崩潰日誌

WWDC 2018 Session 414: Understanding Crashes and Crash Logshtml

查看更多 WWDC 18 相關文章請前往 老司機x知識小集xSwiftGG WWDC 18 專題目錄ios

做者簡介:@Vong,目前就任於美拍,喜歡折騰~git

人非聖賢,孰能無過。每一個人在寫代碼的時候,或多或少都會犯錯,那麼如何調試、找出問題所在呢?讓咱們跟隨蘋果工程師一塊兒瞭解一下崩潰是如何產生以及如何解決它們的吧。github

1. 基礎知識

崩潰是什麼?崩潰是當應用想要作某件事的時候,被意外終止。macos

1.1 崩潰爲何會發生

主要是如下幾方面緣由編程

  • CPU 沒法執行的代碼。
  • 被操做系統「強殺」,系統爲了用戶體驗,會強制終止掉那些卡頓時間過長或者內存消耗太高的應用。
  • 編程語言爲了防止錯誤發生而觸發的崩潰,如 NSArray 或者 Swift.Array 越界
  • 開發者爲了防止錯誤發生而觸發的崩潰,好比一些非空判斷的斷言

1.2 崩潰長什麼樣子

1.2.1 調試器裏

當咱們鏈接着 Xcode 進行調試的時候,遇到崩潰,大概長這個樣子。 數組

當連着調試器的時候,咱們可以拿到崩潰現場的一些調用棧以及對應的方法,當沒有連着調試器的時候,系統會將崩潰日誌存儲到磁盤當中。多線程

1.2.2 崩潰日誌裏

一般狀況下,release 模式的應用的崩潰日誌是沒有符號化的,日誌內記錄的都是地址。咱們能夠經過 Xcode 來將崩潰日誌進行符號化,解析出對應文件名、方法名以及對應崩潰在第幾行。app

1.3 獲取崩潰日誌

獲取崩潰日誌的方式不少,咱們先來了解一下如何經過 Xcode Organizer 來獲取從 TestFlightApp Store 下載的應用的崩潰日誌。編程語言

1.3.1 Organizer Window

先來看一下下面這張圖:

下面數字 1~6 分別表明圖中標註的 1~6

  • 1.能夠看到全部平臺發佈在 App Store 或者 TestFlight 上的應用。
  • 2.崩潰日誌列表,能夠看到對應影響的設備數以及對應的平臺、擴展(extension),如圖中藍色框標註的位置。
  • 3.崩潰所在調用棧及崩潰位置的高亮。
  • 4.在對應工程中打開崩潰所在的文件,並跳轉到指定位置,方便追蹤問題。
  • 5.最近數據分析,包含系統和機型兩個維度。
  • 6.在崩潰數較多時,支持翻頁。

PS:上面6個只是簡單介紹了一下主題部分,剩餘的能夠自行探索使用。好比搜索、對單個日誌作一些筆記、以及將已修復的崩潰標記爲已解決等等。

那麼如何才能在 Organizer 中獲取對應的崩潰日誌呢?很簡單,只須要作到下面幾步

    1. Xcode 中登陸已付費的開發者賬號。
    1. 上傳應用到 App StoreTestFlight 時,一併上傳符號文件。
    1. 打開 Xcode Organizer 窗口,選中 Crashes tab(快捷鍵:Cmd+Shift+6)。

1.3.2 Devices Window

鏈接上設備,打開 Xcode,使用快捷鍵 Cmd+Shift+2 來打開 Devices Window,選中對應設備,而後選擇 View Device Logs,便可查看當前設備磁盤上的全部崩潰文件,找到應用對應的日誌便可展開分析。

有些時候,獲取到的崩潰日誌並無符號化。這個時候須要本身作一些額外操做,這裏能夠參考我以前在知識小集分享過的一個小 tip——iOS快速解析崩潰日誌

1.3.3 其它途徑

  • Xcode 的自動化測試(獲得的是已符號化的日誌)
  • Mac 自帶的 Console 應用,獲取 Mac 或者模擬器的崩潰日誌
  • iOS設備可經過這種操做獲取,打開【設置】->【隱私】->【分析】->【分析數據】拿到對應的未符號化的崩潰日誌,而後經過系統自帶的分享便可傳輸到對應的設備上進行分析。

1.4 符號化最佳實踐

  • 上傳應用的符號文件,以便蘋果後臺能夠直接符號化崩潰日誌,最終得以在 Xcode OrganizerCrashestab 中呈現。
  • 保留應用歸檔文件,以便作本地符號化,只要有歸檔文件在,Xcode 會自動進行符號化。
  • Xcode OrganizerArchivetab 爲已開啓 bitcode 的應用下載 dSYM 文件。

2. 分析奔潰日誌

2.1 崩潰日誌的組成

  • 崩潰摘要,主要記錄一些基本信息,好比機型、系統版本、崩潰時間等
  • 崩潰緣由
  • 崩潰信息(這一部分在真機上處於隱私緣由,通常都是不可見的,在模擬器和 MacOS 上可見)
  • 崩潰線程的調用棧
  • 崩潰發生時,其它線程的調用棧
  • 寄存器狀態
  • 已加載的可執行二進制文件

2.2 如何分析

首先從崩潰緣由中的崩潰類型開始

如上圖的崩潰類型爲 EXC_BAD_INSTRUCTION,它表明 CPU 嘗試在執行一段不存在或無效的代碼,而致使進行被「殺死」。

而後咱們能夠找到崩潰線程的調用棧的前幾行,結合崩潰信息(若是有的話)進一步分析。找到崩潰棧中第一處二進制名爲應用名稱所在那一行,進到對應文件對應的代碼行數進行查看(如上圖中標紅的那一行),而後進一步分析。上圖中的崩潰能夠很明顯看出其緣由是對 nil 進行了強制解包。

2.3 斷言和先決條件致使的崩潰

斷言和先決條件的意義在於當錯誤發生時,強制終止當前進程。

上述提到的對 nil 強制解包致使的崩潰是斷言和先決條件中的一種。而它們還包含下面幾種狀況:

  • 數據越界訪問
  • 算術溢出
  • 未捕獲的異常
  • 代碼中的自定義斷言

2.4 操做系統「殺死」應用致使的崩潰

某些狀況下,系統處於保護目的,會將一些異常的應用「殺死」。如下幾種場景可能觸發系統將應用「殺死」:

  • 看門狗事件,主線程長時間無響應
  • 設備過分發燙
  • 內存消耗殆盡
  • 非法的應用簽名

以上幾種場景緻使的崩潰,其崩潰日誌能夠在上面提到的 Device Window 中查看,Organizer Window 並不必定可以收集到這些日誌。更多細節能夠參考蘋果的這個技術講座 Understanding and Analyzing Application Crash Reports

先來看一個關於看門狗的例子。

上面的崩潰類型爲 EXC_CRASH (SIGKILL)SIGKILL 通常表明的是系統終止了進程的運行,這種信號沒法被應用捕獲,進而也就沒法處理。終止緣由爲 Namespace SPRINGBOARD, Code 0x8badf00d,若是你有查看上面提到的關於崩潰日誌的講座,你應該會知道 Code 0x8badf00d 表明什麼。從終止描述中來看,是因爲啓動時長超過了 19.97 秒。

此次總算知道爲何看門狗對應的 code0x8badf00d 了,從此次蘋果工程師的發音上來看,這個 code 的發音同 ate bad food

2.4.1 如何避免啓動超時

應用審覈被拒的比較常見的緣由就包含啓動超時這一項。那麼如何來避免這種狀況發生呢?蘋果工程師給了咱們這些建議:

  • 在真機上測試,由於看門狗在模擬器以及調試階段是被禁用的
  • 在低性能設備上測試,高性能設備響應確定會快,沒法體現出真實效果

2.4.2 如何避免內存問題

常見的內存錯誤包含:過分釋放、野指針(訪問已釋放對象)、內存訪問越界(好比 C 數組)。咱們仍是經過一個日誌來分析一下具體問題。

由上圖中標註的1,咱們知道崩潰類型爲 EXC_BAD_ACCESS(SIGSEGV),這種類型崩潰主要是有兩種狀況致使:

  • 對只讀的內存地址進行寫操做
  • 訪問不存在的內存地址

經過崩潰棧中的objc_releaseobject_dispose 等,咱們更加肯定這是因爲內存問題致使的崩潰。咱們經過這幾個線索能夠知道,LoginViewController 實例在調用 deinit 方法銷燬相關屬性的時候,發生了內存問題,進而致使崩潰的產生。

咱們回到日誌的第一部分中的Exception Codes,蘋果的工程師說能夠根據經驗以及日誌中的相關信息得出結論,對應的 BAD_ADDRESS0x7fdd5e70700。緣由是 0x7fdd5e70700 恰好在日誌中的這一段 MALLOC_TINY 00007fdd5e400000-00007fdd5e800000 地址範圍內。

一些關於內存及釋放的基礎

Objective-C 對象以及一些 Swift 對象的內存佈局如圖,當一個對象有效(未釋放)時以 isa 開始,isa 指向它所屬的類。objc_release 主要是讀取對象的 isa 指針,而後將 isa 指針解除對 Class 的引用。

正常狀況下,一切都能照常工做。若是對象已經被釋放,會發生什麼呢?free 函數調用後,會將對象刪除,而且將其插入到包含了其它已釋放對象組成的鏈表中,同時將以前 isa 區域指向鏈表中下一個已釋放對象。

當以前的 isa 內存區域被寫入成 rotated free list 指針時,意味着訪問這個地址返回的將是一個無效的內存地址,進而致使崩潰。因此當 objc_release 去解除 isa 引用時,訪問到的是 rotated free list,因此崩潰就發生了。

因此能夠分析出,確定是在釋放某個屬性時,該屬性已經被釋放。咱們能知道具體是哪一個屬性致使的麼?答案是確定的。

目前從崩潰的那一行來看,__ivar_destroyer 是編譯器幫咱們自動生成的函數,因此咱們無從知曉具體是哪一行致使的問題。咱們只知道這個類有如圖三個屬性:

可是從 @objc LoginViewController.__ivar_destroyer + 42 能夠獲取到一些信息,+42 表明着彙編裏面的該函數的偏移量。咱們能夠對 __ivar_destroyer 函數進行反彙編,而後看偏移量爲42對應獲取的是哪一個屬性,在 Xcode 中可使用 lldb 調試。

斷點後分別輸入上圖中黃色字的命令,分別爲 command script import lldb.macosx.crashlogcrashlog /Users/.../RideSharingApp-2018-05-24-1.crash,後面的路徑須要替換成你的崩潰日誌路徑。Xcode 會自動檢索二進制文件以及對應的 dSYM 文件,而後符號化顯示在 lldb 控制檯中。而後咱們找到崩潰處的地址,執行以下命令,便可獲得對應的反彙編代碼:

咱們不須要理解每一行彙編的意思,每行後面的註釋能夠幫助咱們理解,根據註釋能夠知道 一、二、3 處代碼分別表明着 userNamedatabaseviews 的釋放。回到上面提到的 +42,咱們找到第3處的第一行,有一點須要注意的是大部分狀況下彙編的偏移地址是返回地址,因此調用 objc_release 是在上一行。因此能夠判斷出是在釋放 database 時出現了問題。雖然咱們目前還不知道具體問題所在,可是能夠經過這些信息縮小查找問題的範圍,能夠查找使用到 database 的地方,來找到真正的問題所在。

2.4.2 日誌分析總結

  • 理解崩潰日誌產生的緣由
  • 檢查崩潰棧信息
  • 使用反彙編幫咱們找到更多線索來分析 bad address 問題

2.4.3 常見內存錯誤

  • objc_msgSend 或者 retain/release 崩潰

  • 沒法識別的方法異常

  • abort() inside malloc/free

2.5 日誌分析建議

  • 不要只關注崩潰發生的那一行代碼,多查看一下和崩潰相關的代碼,好比上面那個崩潰代碼並非真正致使 bug 出現的緣由
  • 查看全部調用棧,不要只關注崩潰所在線程的調用棧,非崩潰線程調用棧能夠幫助咱們查看崩潰時應用所處狀態
  • 多查看一些崩潰日誌,有些時候不少崩潰日誌都是崩潰在同一個地方,可是某些崩潰日誌會包含更多的信息
  • 使用 Xcode 提供的工具來複現內存問題,好比 Address Sanitizer 或者 Zombies

3. 多線程問題

3.1 崩潰日誌中多線程問題的一些「症狀」

  • 最難復現和診斷的一類 bug
  • 多線程問題一般會引發內存競爭
  • 多個線程執行着類似代碼
  • 同一個 bug 可能會有不一樣的崩潰日誌

3.2 使用 Thread Sanitizer 檢測多線程問題

多線程問題即便咱們拿到日誌大機率狀況下也沒法分析問題所在,即便連着 Xcode 調試也不必定可以穩定復現,即便運氣好能復現也可能分析不出具體問題。因此咱們能夠藉助 Xcode 提供的工具來幫咱們分析,這個工具就是 Thread Sanitizer。經過快捷鍵 Cmd+shift+,,而後選則 Diagnostics tab,勾選 Thread Sanitizer 便可。以下圖所示

  • 可穩定復現多線程 bug
  • 在模擬器下也可進行
  • 只查找當前正在執行的代碼的問題

3.3 實用建議

在建立 GCD Queue(NS)OperationQueue(NS)Thread 時,使用自定義名稱,方便後續調試以及崩潰日誌內查看。

let queue = DispatchQueue(label: "com.example.myapp.networking")

let operationQueue = OperationQueue()
operationQueue.name = "Networking OperationQueue"
 
let thread = Thread(...)
thread.name = "Networking Thread"
複製代碼

3.4 額外建議

  • 使用真機測試
  • 嘗試復現,從用戶處拿到崩潰日誌後根據調用棧嘗試去復現問題
  • 使用工具來查找難以復現的 bug,下面兩個工具的更多使用方式能夠參考 WWDC 2016 Session 412 Thread Sanitizer and Static Analysis
    • 使用 Address Sanitizer 來查看內存問題
    • 使用 Thread Sanitizer 來查看多線程問題
相關文章
相關標籤/搜索