1. 程式人生 > >如何看iOS崩潰日誌

如何看iOS崩潰日誌

連接 stop 全部 產生 guid 數據 繼續 進程 返回

重點:Triggered by Thread這句話後邊的線程號,快速定位問題出現在那個線程,是否是你的鍋;Triggered by Thread所指的線程表示導致異常、崩潰的線程

下邊內容轉自簡書

簡介

當一個應用程序崩潰, 會產生一個崩潰報告(crash report) ,並存儲到那個設備。崩潰報告描述了應用程序崩潰的條件,大多數情況會包含每個執行線程的一個完整的回溯,對我們調試應用程序的問題是非常有用的。你應該查看崩潰報告來了解你應用程序有什麽崩潰,然後嘗試修復它們。
崩潰報告必須被符號化(symbolicated),才能夠被分析。符號化就是將內存地址替換成人能夠閱讀的函數名和代碼所處的行數(即第幾行)。如果你通過Xcode的Devices窗口,從一個設備中獲取崩潰日誌,幾秒鐘後會自動為你完成符號化。否則,你需要將.crash文件導入到Xcode的Devices窗口,來將它符號化。
低內存報告(Low Memory report)和崩潰報告不一樣,這類型的報告沒有回溯。當一個低內存報告發生時,你必須檢查你的內存使用模式以及對低內存警告的應對方法。這篇文章將提供幾種內存管理參考,你可能會有用。

獲取崩潰和低內存報告

Debugging Deployed iOS Apps討論怎樣直接從設備獲取崩潰和低內存報告。
App Distribution Guide中的Analyzing Crash Reports會討論如何查看從TestFlight beta測試者以及從App Store下載你App的用戶中收集的全部崩潰報告。

符號化崩潰報告

符號化是把回溯地址(即符號)還原成源碼方法或者函數名的過程。不先把崩潰報告進行符號化,是很難判斷崩潰發生在哪裏。

Note:低內存報告不需要進行符號化。

Note:來至macOS的崩潰報告通常在它們產生的時候就已經符號化的,或者部分符號化的。這部分集中討論來之iOS,watchOS和tvOS,但是總體過程和macOS相似。

Figure 1:崩潰的報告以及符號化過程的總覽。

技術分享圖片 總覽

  1. 在編譯器將你的源代碼翻譯成機器碼的同時,它也會生成debug symbols (調試符號),它在編譯後的二進制中,映射每個機器指令到原來的每一行源代碼。這些調試符號是存儲到二進制文件裏還是其附屬的調試符號文件,即Debug Symbol (dSYM) file,取決於build setting中調試信息格式(DEBUG_INFORMATION_FORMAT)的設定。默認情況是一個應用程序debug的build會存儲到編譯的二進制文件中,當Release的時候會存儲調試符號到一個附屬的dSYM文件來減輕二進制文件的大小。

Debug Symbol file和應用程序的二進制文件是通過預構建基礎(per-build-basis)的構建UUID來綁定的。每次構建應用程序都會生成一個新的UUID,來唯一確定這次構建。甚至使用相同的編譯器設定,對同樣的源代碼,來進行功能上一模一樣的執行重新構建,也會有不同的構建UUID。來自本構建的Debug Symbol文件,盡管是相同的源文件,也不會和其他構建的二進制文件互通。

  1. 當你archive要發布應用程序時,Xcode會收集應用程序二進制文件以及.dSYM文件,然後把它們存儲到你的home文件夾的一個地方。你可以在Xcode Organizer的"Archived" 部分中,找到所有你archived的應用程序。

Important:要符號化來之測試者,app審核以及用戶的崩潰報告,你必須保存每次你發布應用程序的archive。

  1. 當你通過App Store發布你的App,或者通過TestFlight發起一次beta test,會有當上傳你的Archive到iTunes Connect,包含dSYM文件的選項。在提交對話框中,勾選 “Include app symbols for your application…”。上傳你的dSYM文件對於接收來自TestFlight用戶和選擇了分享診斷數據的顧客(即app store用戶)的崩潰報告是必要的。

Important:來自App Review的崩潰報告會沒法符號化,盡管當你上傳到iTunes Connect的archive包含了dSYM文件。你需要通過Xcode來符號化來自App Review的崩潰報告。

  1. 當你的應用程序崩潰,一個未符號化的崩潰報告會產生並存儲到設備上。

  2. 通過 Debugging Deployed iOS Apps
    步驟,用戶可以直接從他們的設備獲取的崩潰報告。如果你是通過AdHoc發布你的應用程序,或者是企業發布(Enterprise distribution),這是唯一從你的用戶獲取崩潰報告的方法。

  3. 從一個設備獲取的崩潰報告是沒有被符號化的,需要使用Xcode來進行符號化。Xcode使用應用程序二進制文件關聯的dSYM文件,把回溯中的每個地址替換為你源代碼原來的位置。替換的結果就是符號化的崩潰報告。

  4. 如果用戶選擇分享診斷數據給Apple,或者如果用戶通過TestFlight安裝了你應用程序的一個beta版本,崩潰報告會被上傳到App Store。

  5. App store符號化那些崩潰報告,把相似的崩潰報告分組。這些全部相似崩潰報告,被稱為崩潰點(Crash Point)。

  6. 已經符號化的崩潰報告可以在Xcode的Crashes organizer中找到和使用。

Bitcode

Bitcode 是代表一個已編譯的程序的中間媒介。當Bitcode開啟的時候,你archive一個應用程序,編譯器產生包含bitcode二進制文件而不是機器碼。一旦二進制已經上傳到App Store,bitcode會被編譯成機器碼。App Store 可能在以後再次編譯這個bitcode,用於未來編譯器的提升,你這邊不需要做任何事。

Figure 2:Bitcode編譯過程。

技術分享圖片 Bitcode編譯過程

因為你的二進制的最後編譯是由App Store完成的,你的Mac不會包含dSYM文件,來符號化來自App Store崩潰報告或者用戶從它們設備獲取發送給你的崩潰報告。盡管當你archive你的App,會產生dSYM文件,但它是屬於bitcode二進制文件的,不能用於符號化崩潰報告。App Store在進行bitcode編譯會產程dSYM文件,你可以通過Xcode或者iTunes connect網頁進行下載。你必須下載這些dSYM文件,才能符號化來自App Store崩潰報告或者用戶從它們設備獲取發送給你的崩潰報告。從崩潰報告服務獲取的崩潰報告會被自動符號化。

Important: App Store編譯的二進制文件的UUID會和你原來提交的二進制文件的UUID不同。

從Xcode下載dSYM文件
  1. 在 Archives organizer選項,選擇你原來提交到App Store的archive。
  2. 點擊dSYMs按鈕來下載。
    Xcode下載dSYM文件並把它們插入到被選擇的archive。
從iTunes connect網頁下載dSYM文件
  1. 打開App詳情頁。
  2. 點擊活動。
  3. 從所有Builds列表中,選擇一個版本。
  4. 點擊Download dSYM鏈接。
把‘hidden‘符號名翻譯它們原來的名字

當你上傳你的bitcode App到App Store,你可以在提交對話框裏,不勾選選擇"Upload your app‘s symbols to receive symbolicated reports from Apple",從而不發送應用程序的symbols。如果你選擇不發送的App的符號信息給Apple,在發送你的App到iTunes Connect之前,Xcode會用一個混淆的symbols,如 "__hidden#109_"來代替你App的symbols。Xcode會創建原始symbols和"hidden" symbols的映射關系並存儲這個映射關系到一個. bcsymbolmap文件到這個應用程序的archive中,每一個.dSYM文件都有對應的一個.bcsymbolmap文件。

在符號化崩潰報告之前,你需要反向混淆(還原)這個從iTunes Connect下載的文件。如果你使用Xcode來下載dSYM文件,會為你自動進行反向混淆。然而,如果你使用iTunes Connect網址下載的.dSYM文件,打開終端並使用下面的命令行來反向混淆你的symbols(使用你自己的archive 和從iTunes connect下載的dSYMs文件夾來代替下面範例)。

xcrun dsymutil -symbol-map ~/Library/Developer/Xcode/Archives/2017-11-23/MyGreatApp\ 11-23-17\,\ 12.00\ PM.xcarchive/BCSymbolMaps ~/Downloads/dSYMs/3B15C133-88AA-35B0-B8BA-84AF76826CE0.dSYM

你下載的dSYMs文件夾中的每一個. dSYM文件需要運行這個命令。

確定一個崩潰報告是否被符號化了

一個崩潰報告可能未被符號化,完全符號化,或者部分符號化。未被符號化的崩潰報告在回溯中不會包含方法或者函數名。取而代之的是已加載二進制鏡像的可執行代碼的16進制地址。在一個完全符號化的崩潰報告,回溯的每一行16進制地址都會被對應的符號代替。在部分符號化的崩潰報告,只有部分地址被對應的符號代替。

顯然,你應該試著完全符號化崩潰報告,以便更深入地分析崩潰。一個部分符號化的崩潰報告,也許能包含足夠信息來明白崩潰,這取決崩潰的類型以及哪部分回溯被成功符號化。一個未被符號化的崩潰報告很少被使用。
Figure 3: 同樣的回溯在不同程度的符號化。

技術分享圖片 不同程度的符號化

使用Xcode符號化崩潰報告

Xcode 會自動嘗試符號化它遇到的所有崩潰報告。你只需要將崩潰報告添加到Xcode Organizer。

Note: Xcode不會接收不是以.crash為擴展名的崩潰報告。如果你接收到一個崩潰報告沒有擴展名或者擴展名是.txt的,在進行以下步驟之前請將它重命名.crash擴展名。

  1. 將你的Mac和一個iOS設備連接。
  2. 從"Window"菜單,選擇"Device"。
  3. 在"DEVICES" 部分的左邊欄,選擇一個設備。
  4. 點擊右手邊板面"Device Information"部分的"View Device Logs"按鈕。
  5. 將崩潰報告拖到左邊欄當前的面板
  6. Xcode會自動符號化這個崩潰報告,並顯示結果。

要符號化一個崩潰報告,Xcode必須能找到以下信息:

  • 崩潰應用程序的二進制文件和dSYM文件
  • 所有這個應用程序使用的自定義framework的二進制和dSYM文件。如果frameworks是編譯來自應用程序的源代碼,它們的dSYM文件會和應用程序的dSYM文件被一起復制進archive。如果frameworks是第三方編譯的,你需要向其作者獲取dSYM文件。
  • 系統符號是應用程序運行崩潰的所在的系統。這些symbols包含frameworks的調試信息中,包括了一個指定的發布系統信息(如iOS9.3.3)。系統符號(OS symbols)是指定架構的-一個64位設備的iOS發布系統不會包含armv7的符號。Xcode會自動復制從連接你Mac的每個設備中自動復制系統符號。
    如果任意這些信息丟失,Xcode可能無法符號化崩潰報告,或者只能部分符號化崩潰報告。
使用atos符號化崩潰報告

atos命令轉換數字地址到它們等價的符號。如果調試符號信息能夠使用,atos的輸出包含文件名和源碼的行數信息。atos命令能夠符號化未符號化或部分符號化的崩潰報告回溯中的單個地址。使用atos符號化崩潰報告的一部分:

  1. 在回溯中找到你想符號化的一行。註意二進制鏡像的名字在第二列,而地址在第三列。
  2. 找到名字在崩潰報告底部的二進制鏡像列表中的二進制鏡像。註意二進制的架構和加載地址。
    Figure 4:使用atos所需要的崩潰報告信息。
    技術分享圖片 崩潰報告信息
  3. 找到二進制文件對應的dSYM文件。你可以使用Spotlight找到匹配dSYM文件UUID的二進制鏡像。(可以參考下面部分的:符號化的疑難解答)dSYM文件是包含了編譯器在構建時候產生的包含DWARF調試信息文件的集合(bundles)。當調用atos的時候,你必須提供這個文件的路徑,而不是aSYM集合。
  4. 有了以上信息,你可以使用atos符號化回溯中的地址。你可以指定多行信息地址,用空格來分隔,來符號化多個地址。
    atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>

Listing 1:按照上面步驟使用atos命令,以及輸出的結果。

$ atos -arch arm64 -o TheElements.app.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc
-[AtomicElementViewController myTransitionDidStop:finished:context:]
符號化的疑難解答

如果Xcode不能完全符號崩潰報告,很可能是因為你的Mac沒有應用程序二進制對應的aSYM文件,應用程序關聯的Framework的對應的dSYM文件,或者應用程序運行崩潰所在的設備系統的symbols。下面的步驟展示怎樣使用Spotlight來確定用於符號化回溯對應二進制鏡像的dSYM文件是否存在在你的Mac上。

Figure 5:找到二進制鏡像的UUID。

技術分享圖片 UUID

  1. 找到回溯中Xcode無法符號化的一行。註意二進制鏡像的名字在第二列。
  2. 找到名字在崩潰報告底部的二進制鏡像列表中的二進制鏡像。這個列表包含了崩潰發生時,每個被加載處理的鏡像的UUID。
    Listing 2:你可以使用grep命令行工具來快速找到二進制鏡像列表的入口。
$ grep --after-context=1000 "Binary Images:" <Path to Crash Report> | grep <Binary Name>
  1. 將二進制鏡像的UUID轉化成一個被分割成8-4-4-4-12(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)組的32字節長字符串。註意所有字母必須是大寫的。

  2. 使用mdfind命令行工具和"com_apple_xcode_dsym_uuids == <UUID>"(包含引號)查詢來UUID。
    Listing 3使用mdfind命令行工具來查找UUID對應的dSYM文件。

$ mdfind "com_apple_xcode_dsym_uuids == <UUID>"
  1. 如果Spotlight找到一個UUID的一個dSYM文件,mdfind會打印dSYM文件的路徑以及可能會打印它包含的archive。如果對應UUID的dSYM沒有找到,mdfind不會打印任何東西並退出。

如果Spotlight找到二進制對應的dSYM文件,但是Xcode根據那二進制鏡像還是不能符號化地址,你應該提交一個bug。將崩潰報告和相關的dSYM文件附帶至bug報告中。一個變通的辦法,安裝上面的介紹的方法使用atos手動符號化地址。

如果Spotlight沒有找到二進制對應的dSYM文件,確定你有導致崩潰應用程序版本的Xcode archive,而這個archive處於Spotlight可以找到的地方(應該在你的home目錄中)。如果你的應用程序開啟bitcode,請確保你已經從App Store下載了最後編譯的dSYM文件。

如果你認為你有正確對應二進制文件的dSYM文件,你可以使用dwarfdump命令進行打印匹配的UUIDs。你也可以使用dwarfdump命令來打印二進制文件的UUIDs。
xcrun dwarfdump --uuid <Path to dSYM file>

Note:你必須有提交到崩潰的App Store版本的應用程序的archive。dSYM文件和應用程序的二進制是在預構建基礎上綁定在一起的。盡管使用相同的源碼和構建配置進行新的archive,也不會產生一個和這個崩潰build互通的dSYM文件。

如果你已經沒有這個archive了,你應該提交一個保存了archive的新版本的應用程序,然後你就能夠符號化這個新版本的崩潰。

分析崩潰報告

這部分將討論一個標準崩潰報告的每一部分。

頭部

每個崩潰報告都從頭部開始
Listing 4:一個崩潰報告的頭部

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:一個報告的唯一ID。兩個報告不可能使用相同的ID。
  • CrashReporter Key:每個設備的隱藏ID。兩個報告來自相同設備會有相同的CrashReporter Key
  • Beta Identifier:設備和崩潰應用提供商組合的唯一碼。兩個應用程序的報告如果來至相同的提供商和相同的設備會有相同的Beta Identifier。這個只有展示通過TestFlight發布的應用程序產生的崩潰報告,用它來代替CrashReporter Key
  • Process:崩潰進程的可執行的名字。它和應用程序信息屬性列表對應CFBundleExecutable鍵的值。
  • Version:崩潰進程的版本。這個字段的值是崩潰應用程序的CFBundleVersionCFBundleVersionString串聯。
  • Code Type:崩潰進程的target的架構。它會是ARM-64, ARM, x86-64, or x86的其中一個。
  • Role:終止時候進程被分配的任務角色。
  • OS Version:崩潰發生的系統版本,包括build號。
異常信息

不要被Objective-C/C++的異常迷惑了(盡管其中一個可能導致崩潰),這部分列出提供崩潰本質的信息的匹配異常類型(Exception Type)和相關字段。 不是所有字段都會出現在每一次崩潰。
Listing 5: 因為未知Objective-C異常而進程被終止產生的崩潰報告,其中異常代碼的摘錄。

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0

Listing 6:因為取一個空指針的值(dereferenced a NULL pointer)而進程被終止產生的崩潰報告,其中異常代碼的摘錄。

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位16進制數字的異常的指定信息。通常來說,這個字段不會顯示因為崩潰報告者會解析這些異常代碼成人可讀的描述來展示到其他的字段。
  • Exception Subtype:人可讀的異常代碼的名字。
  • Exception Message:從異常代碼中解壓得到的額外人類可讀的信息。
  • Exception Note:非指定一個異常類型的額外信息。如果這個字段包含模擬的(SIMULATED,不是一個崩潰),那麽進程不會崩潰,但被系統請求殺死,通常是看門狗。
  • Termination Reason:當進程被終止時指定的退出理由信息。從一個進程的外部和內部看,都是系統的關鍵組件。遭遇到致命錯誤(如錯誤的代碼簽名,依賴庫的丟失,或者沒有合適的授權去獲取隱私的敏感信息)時會終止進程。macOS Sierra, iOS 10, watchOS 3, and tvOS 10采用了新的基礎架構來記錄這些錯誤和這些操作系統產生的崩潰報告會在Termination Reason字段列出。
  • Triggered by Thread:導致異常原因所處的線程。
    下面的部分解釋一些最常見的異常類型:
錯誤的內存訪問 [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]

這個進程嘗試去訪問無效的內存。或者它嘗試使用一個內存保護級別不允許的方式去訪問內存(例如寫入一個只讀的內存)。Exception Subtype字段包含一個kern_return_t值來描述錯誤和不正確訪問的內存的地址。
以下是調試錯誤訪問內存訪問崩潰的提示:

  • 如果objc_msgSend或者objc_release是在崩潰線程的回溯頂部附近,進程可能是嘗試對一個已銷毀的對象發送消息。你應該用Zombies instrument
    來profile這個應用程序,以便更好地了解崩潰的條件。
  • 如果gpus_ReturnNotPermittedKillClient是在崩潰線程的回溯頂部附近,進程被殺是因為它嘗試在後臺使用OpenGL ES或者Metal來繪制。參考:QA1766: How to fix OpenGL ES application crashes when moving to the background。
  • 開啟Address Sanitizer來運行你的應用程序。address sanitizer(地址消毒劑,實際就是地址檢測,能檢測內存的異常)為你編譯的代碼中添加額外的內存訪問檢測。當你應用程序運行時,如果內存訪問方式能導致崩潰,Xcode會警告你。
異常(Abnormal)退出 [EXC_CRASH // SIGABRT]

進程異常退出了。通常導致這種異常類型崩潰的絕大多數原因是未捕獲的Objective-C/C++的異常,調用了abort()
App Extensions如果花費太多時間初始化,就會因為這種異常類型而被終止(一種看門狗的終止)。如果一個extension因為啟動時掛起被殺死,產生的崩潰報告的Exception Subtype字段值會是LAUNCH_HANG。因為extensions沒有main函數,初始化的任何時間都發生在你的extension及其依賴庫中的靜態構造函數和+load方法中。你應該盡可能地推遲這部分工作。

陷阱追蹤 [EXC_BREAKPOINT // SIGTRAP]

和異常退出類似,此異常旨在使附加調試器有機會在執行過程中的某個特定點中斷進程。你可以在你的代碼中使用__builtin_trap() 函數來觸發這種異常。如果沒有附加調試器,進程會被終止並產生一個崩潰報告。
低級別的庫(例如libdispatch)遇上一個致命錯誤的會捕獲進程。關於錯誤的額外信息能夠在崩潰報告的Additional Diagnostic Information(額外診斷信息,下文有介紹)部分或者設備的控制臺中找到。

SWIFT代碼如果在運行時遇到意外情況,將以該異常類型終止,如:

  • 不可選類型是nil
  • 強制類型轉換失敗
    觀察回溯來確定意外條件出現在哪裏。 額外的信息也可能在設備控制臺中打印出來。你必須修改崩潰地方的代碼以優雅地處理運行時的錯誤。例如,使用對一個可選值使用可選綁定來代替強制解包。
非法指令[EXC_BAD_INSTRUCTION // SIGILL]

進程嘗試去執行一個非法或者未定義的指令。進程由於錯誤配置的函數指針跳到一個無效地址。
在英特爾處理器,ud2操作碼會導致EXC_BAD_INSTRUCTION異常,但它通常用於捕捉進程的調試目的。在英特爾處理器中,Swift代碼如果在運行時一個意外條件觸發了,會以這種異常類型終止。細節請參看陷阱追蹤。

停止[SIGQUIT]

該進程在另一個具有管理其生命周期的進程的請求下被終止。SIGQUIT不意味著這個進程崩潰,但可能確實是被檢測到行為不端。

在iOS,鍵盤extensions如果加載時間太長,會被主App停止。這樣崩潰報告中的回溯很可能不會指向對應的代碼。最有可能的是,在extension的啟動路徑上的一些其他代碼花費很多時間才完成,但是在時間限制內完成了,而當extension退出時,代碼運行到回溯顯示的部分。你應該extension以便更好的了解啟動的過程哪兒出現大部分的工作量,使用後臺線程來執行這些工作量,或者推遲執行工作量(待extension已經加載後)。

被殺死[SIGKILL]

進程因為系統請求被終止。查看Termination Reason字段以便更好了解終止的原因。

Termination Reason字段會包含一個命名空間,後面跟著一段代碼。以下代碼是基於watchOS的:

終止代碼0xc51bad01表示由於在執行後臺任務時使用了太多的CPU時間,所以一個watch app被終止。為了解決這個問題,優化執行後臺任務的代碼以獲得更高的CPU效率,或者減少App在後臺運行的工作量。

終止代碼0xc51bad02表示由於在分配的時間內未能完成後臺任務而終止了手表應用程序。要解決此問題,請減少在後臺運行時應用程序執行的工作量。

終止代碼0xc51bad03表示一個watch app在分配的時間內未能完成後臺任務,並且整體系統足夠繁忙,以至於App可能沒有接收足夠CPU時間到執行後臺任務。盡管App可以通過減少在後臺任務中執行的工作量來避免此問題,但是0xc51bad03並不表示應用程序出錯了。更可能的是,由於整體系統負載,App無法完成其工作。

訪問非法資源[EXC_GUARD]

進程違反了資源守護策略。系統的庫會標記指定的文件描述符為guarded(被守護的),之後對這些描述符的正常操作會觸發一個EXC_GUARD異常(當它想要對這些文件描述符進行操作時,系統使用特殊的“守護”私有API)。這可以幫助您快速追蹤問題,如關閉由系統庫打開的文件描述符。例如,如果一個應用程序關閉了用於訪問支持Core Data存儲的SQLite文件的文件描述符,那麽Core Data將在後面莫名其妙地崩潰。guard異常更快地註意到這些問題,從而使它們更易於調試。

來自較新版本的iOS的崩潰報告在 Exception SubtypeException Message字段中包含了導致EXC_GUARD異常的操作的人為可讀的細節, 引起EXC_GUARD異常的操作的人類可讀的細節。在來自macOS或較舊版本的iOS的崩潰報告中,該信息被編碼為第一個異常代碼,作為一個位字段,如下所示:

  • [63:61] - Guard Type(守護類型):被守護資源的類型。值0x2表示資源是一個文件描述符。
  • [60:32] - Flavor:觸發違規的條件。
    如果設置了第一位(1 << 0),進程嘗試對一個被守護的文件描述符調用close()。
    如果設置了第二位(1 << 1),進程嘗試對一個被守護的文件描述符使用F_DUPFDF_DUPFD_CLOEXEC命令調用dup()dup2()fcntl()
    如果設置了第三位(1 << 2),進程嘗試通過socket發送一個被守護文件標識符。
    如果設置了第五位(1 << 4),進程嘗試寫入一個被守護文件描述符。
  • [31:0] - File Descriptor:進程嘗試修改的被守護文件的描述符。
資源限制 [EXC_RESOURCE]

該進程超出了資源消耗限制。這是來自操作系統的通知,該進程正在使用太多的資源。具體的資源列在Exception Subtype字段中。如果 Exception Note字段包含NON-FATAL CONDITION,那麽即使生成崩潰報告,該過程也不會被終止。

異常子類型MEMORY表示該進程已超過系統強加的內存限制。這可能是終止過量內存使用的先兆。

異常子類型WAKEUPS表示進程中的線程每秒被喚醒的次數太多,這迫使CPU醒來的頻率很高,並消耗電池壽命。
通常,這是由線程間通信(通常使用peformSelector:onThread:dispatch_async)導致的,這些通信不知不覺地發生得比正常情況頻繁得多。因為觸發這種異常的通信發生得非常頻繁,所以通常會有多個具有非常相似的回溯的後臺線程 - 指示通信起源的位置。

其他異常類型

某些崩潰報告可能包含一個未命名的異常類型,將以十六進制值打印(例如00000020)。如果您收到一個這種崩潰報告,請直接查看Exception Codes字段以獲取更多信息。

  • 異常代碼0xbaaaaaad表示日誌是整個系統的堆棧快照,而不是崩潰報告。要拍攝堆棧快照,請同時按側面按鈕和兩個音量按鈕。這些日誌通常是由用戶意外創建的,並不表示一個錯誤。
  • 異常代碼0xbad22222表示一個VoIP應用程序已被iOS終止,因為它恢復得太頻繁。
  • 異常代碼0x8badf00d表示應用程序因為看門狗超時已被iOS終止。應用程序啟動,終止或響應系統事件花了太長時間。一個常見的原因是在主線程上進行同步網絡操作。無論是什麽操作,線程0都需要移動到後臺線程,或者以不同的方式進行處理,這樣就不會阻塞主線程。
  • 異常代碼0xc00010ff表示該App因響應熱事件(如CPU溫度過高)而被操作系統殺死。這可能是由於發生此次崩潰的特定設備或其所在的環境所引起的問題。有關使你的App更高效運行的提示,請參閱iOS Performance and Power Optimization with Instruments WWDC session。
  • 異常代碼0xdead10cc表示應用程序已被操作系統終止,因為它在掛起期間持有文件鎖或sqlite數據庫鎖。如果您的應用程序在掛起時對鎖定了的文件或sqlite數據庫執行操作,則必須請求額外的後臺執行時間才能完成這些操作,並在掛起之前放棄鎖定。
  • 異常代碼0x2bad45ec表示應用程序由於安全違規而被iOS終止。終止描述“進程被檢測到在安全模式下進行不安全的繪圖”表示App嘗試在不允許的情況下繪制到屏幕上,例如屏幕鎖定的時候。用戶可能不會註意到這個終止,因為屏幕關閉或終止時顯示的是鎖定屏幕。

Note: 使用App切換器終止一個掛起的程序不會產生崩潰報告。一旦程序掛起了,它隨時有可能被iOS終止,所有不會產生崩潰報告。

額外診斷信息

這部分包含特定於終止類型的額外診斷信息,其中可能包括:

應用程序的特定信息:在進程終止之前捕獲的框架錯誤消息
內核消息:有關代碼簽名問題的詳細信息
Dyld錯誤消息:由動態鏈接器發出的錯誤消息

dyld 是Apple 的動態鏈接器;在 xnu 內核為程序啟動做好準備後,就會將 PC 控制權交給 dyld 負責剩下的工作 (dyld 是運行在 用戶態的, 這裏由 內核態 切到了用戶態)。

從macOS Sierra,iOS 10,watchOS 3和tvOS 10開始,大部分信息現在都在異常信息下的Termination Reason字段中報告。

您應該閱讀本節以更好地了解進程終止的情況。
Listing 7:一個進程因為一個框架的鏈接無法找到而被終止的崩潰報告的應用程序特定信息部分的摘錄。

Dyld Error Message:
Dyld Message: Library not loaded: @rpath/MyCustomFramework.framework/MyCustomFramework
  Referenced from: /private/var/containers/Bundle/Application/CD9DB546-A449-41A4-A08B-87E57EE11354/TheElements.app/TheElements
  Reason: no suitable image found.

Listing 8: 一個進程因為無法快速加載其初始視圖控制器而被終止的崩潰報告的應用程序特定信息部分的摘錄。

Application Specific Information:
com.example.apple-samplecode.TheElements failed to scene-create after 19.81s (launch took 0.19s of total time limit 20.00s)
 
Elapsed total CPU time (seconds): 7.690 (user 7.690, system 0.000), 19% CPU
Elapsed application CPU time (seconds): 0.697, 2% CPU
回溯

崩潰報告中最有趣的部分是每個進程的線程在其終止時的回溯。 這每一個跟蹤與您在使用調試器暫停過程時所看到的類似。
Listing 9:完全符號化的崩潰報告中回溯部分的摘錄。

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   TheElements                     0x000000010006bc20 -[AtomicElementViewController myTransitionDidStop:finished:context:] (AtomicElementViewController.m:203)
1   UIKit                           0x0000000194cef0f0 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
2   UIKit                           0x0000000194ceef30 -[UIViewAnimationState animationDidStop:finished:] + 160
3   QuartzCore                      0x0000000192178404 CA::Layer::run_animation_callbacks(void*) + 260
4   libdispatch.dylib               0x000000018dd6d1c0 _dispatch_client_callout + 16
5   libdispatch.dylib               0x000000018dd71d6c _dispatch_main_queue_callback_4CF + 1000
6   CoreFoundation                  0x000000018ee91f2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
7   CoreFoundation                  0x000000018ee8fb18 __CFRunLoopRun + 1660
8   CoreFoundation                  0x000000018edbe048 CFRunLoopRunSpecific + 444
9   GraphicsServices                0x000000019083f198 GSEventRunModal + 180
10  UIKit                           0x0000000194d21bd0 -[UIApplication _run] + 684
11  UIKit                           0x0000000194d1c908 UIApplicationMain + 208
12  TheElements                     0x00000001000653c0 main (main.m:55)
13  libdyld.dylib                   0x000000018dda05b8 start + 4
 
Thread 1:
0   libsystem_kernel.dylib          0x000000018deb2a88 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x000000018df75188 _pthread_wqthread + 968
2   libsystem_pthread.dylib         0x000000018df74db4 start_wqthread + 4
 
...

第一行列出當前正在執行的調度隊列的線程號和標識符。其余行列出了回溯中各個堆棧幀的詳細信息。從左到右:

  • 堆棧幀號。堆棧幀以調用順序呈現,其中第0幀是執行停止時正在執行的函數。第1幀是調用第0幀的函數,依此類推。
  • 堆棧幀的執行函數所在的二進制文件的名稱。
  • 對於第0幀,執行暫停時正在執行的機器指令的地址。對於剩余的堆棧幀,當控制權返回到堆棧幀時,將執行的機器指令的地址。
  • 在已符號化的崩潰報告中,函數的方法名稱就在堆棧幀中。
異常

Objective-C中的異常用於指示在運行時檢測到的編程錯誤,例如訪問具有超出邊界的索引的數組,嘗試修改不可變對象,不執行協議必須的方法或發送接收器不能識別的消息。

Note:對已銷毀的對象的發送消息可能會導致NSInvalidArgumentException異常,而不是因為非法訪問內存而導致程序崩潰。當一個新對象分配內存,恰好被分配到先前被釋放對象占用的內存中時,會發生這種情況。如果您的應用程序是因未捕獲的NSInvalidArgumentException(在異常回溯中查找 - [NSObject(NSObject)doesNotRecognizeSelector:])而崩潰,請考慮使用僵屍工具(Zombies instrument)來profile您的應用程序,以消除不正確的內存管理的可能性。

如果一個異常沒有被捕獲,則被一個稱為未捕獲的異常處理函數(uncaught exception handler)的函數截獲。默認的未捕獲異常處理程序將異常消息記錄到設備的控制臺,然後終止該進程。只有異常回溯寫入到生成的崩潰報告中的Last Exception Backtrace部分下,如Listing 10所示。崩潰報告中省略了異常消息。如果您收到帶有Last Exception Backtrace的崩潰報告,則應從原始設備獲取控制臺日誌,以便更好地了解導致該異常的條件。
Listing 10:一個未符號化的崩潰報告的Last Exception Backtrace部分的摘錄。

Last Exception Backtrace:
(0x18eee41c0 0x18d91c55c 0x18eee3e88 0x18f8ea1a0 0x195013fe4 0x1951acf20 0x18ee03dc4 0x1951ab8f4 0x195458128 0x19545fa20 0x19545fc7c 0x19545ff70 0x194de4594 0x194e94e8c 0x194f47d8c 0x194f39b40 0x194ca92ac 0x18ee917dc 0x18ee8f40c 0x18ee8f89c 0x18edbe048 0x19083f198 0x194d21bd0 0x194d1c908 0x1000ad45c 0x18dda05b8)

一個崩潰日誌的Last Exception Backtrace部分只包含十六進制地址,必須符號化以產生可用的回溯,如Listing 11所示。

Listing 11:一個已符號化的崩潰報告的Last Exception Backtrace部分的摘錄。 這是在加載storyboard一個scene時引發了此異常。 連接到scene中一個元素的對應IBOutlet丟失了。

Last Exception Backtrace:
0   CoreFoundation                  0x18eee41c0 __exceptionPreprocess + 124
1   libobjc.A.dylib                 0x18d91c55c objc_exception_throw + 56
2   CoreFoundation                  0x18eee3e88 -[NSException raise] + 12
3   Foundation                      0x18f8ea1a0 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 272
4   UIKit                           0x195013fe4 -[UIViewController setValue:forKey:] + 104
5   UIKit                           0x1951acf20 -[UIRuntimeOutletConnection connect] + 124
6   CoreFoundation                  0x18ee03dc4 -[NSArray makeObjectsPerformSelector:] + 232
7   UIKit                           0x1951ab8f4 -[UINib instantiateWithOwner:options:] + 1756
8   UIKit                           0x195458128 -[UIStoryboard instantiateViewControllerWithIdentifier:] + 196
9   UIKit                           0x19545fa20 -[UIStoryboardSegueTemplate instantiateOrFindDestinationViewControllerWithSender:] + 92
10  UIKit                           0x19545fc7c -[UIStoryboardSegueTemplate _perform:] + 56
11  UIKit                           0x19545ff70 -[UIStoryboardSegueTemplate perform:] + 160
12  UIKit                           0x194de4594 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1352
13  UIKit                           0x194e94e8c -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 268
14  UIKit                           0x194f47d8c _runAfterCACommitDeferredBlocks + 292
15  UIKit                           0x194f39b40 _cleanUpAfterCAFlushAndRunDeferredBlocks + 560
16  UIKit                           0x194ca92ac _afterCACommitHandler + 168
17  CoreFoundation                  0x18ee917dc __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
18  CoreFoundation                  0x18ee8f40c __CFRunLoopDoObservers + 372
19  CoreFoundation                  0x18ee8f89c __CFRunLoopRun + 1024
20  CoreFoundation                  0x18edbe048 CFRunLoopRunSpecific + 444
21  GraphicsServices                0x19083f198 GSEventRunModal + 180
22  UIKit                           0x194d21bd0 -[UIApplication _run] + 684
23  UIKit                           0x194d1c908 UIApplicationMain + 208
24  TheElements                     0x1000ad45c main (main.m:55)
25  libdyld.dylib                   0x18dda05b8 start + 4

Note:如果您發現由應用程序設置的異常處理域內引發的異常未被捕獲,請驗證在構建應用程序或庫時是否未指定-no_compact_unwind標誌。

64位iOS使用“zero-cost(零成本)”異常實現。在“zero-cost”系統中,每個函數都有額外的數據,用於描述如何在函數中拋出異常時展開堆棧。如果在沒有展開數據的堆棧幀中拋出異常,則異常處理無法執行,並且進程中止。在堆棧上可能有一個異常處理程序,但是如果一個框架沒有展開數據,那麽引發異常時無法從堆棧幀到達那裏。指定-no_compact_unwind標誌意味著你沒有得到那個代碼的展開表,所以你不能在這些函數中拋出異常。

此外,如果您在應用程序或庫中包含純C代碼,則可能需要指定-funwind-tables標誌以包含該代碼中所有函數的展開表。

線程狀態

這部分列出崩潰的線程的線程狀態。這是執行中止時的寄存器及其值的列表。在閱讀崩潰報告時,不一定要明白線程狀態,但是你可以使用這些信息來更好地理解崩潰的條件。

Listing 12:來自ARM64設備的崩潰報告中線程狀態部分的摘錄。

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x000000019ff776c8   x2: 0x0000000000000000   x3: 0x000000019ff776c8
    x4: 0x0000000000000000   x5: 0x0000000000000001   x6: 0x0000000000000000   x7: 0x00000000000000d0
    x8: 0x0000000100023920   x9: 0x0000000000000000  x10: 0x000000019ff7dff0  x11: 0x0000000c0000000f
   x12: 0x000000013e63b4d0  x13: 0x000001a19ff75009  x14: 0x0000000000000000  x15: 0x0000000000000000
   x16: 0x0000000187b3f1b9  x17: 0x0000000181ed488c  x18: 0x0000000000000000  x19: 0x000000013e544780
   x20: 0x000000013fa49560  x21: 0x0000000000000001  x22: 0x000000013fc05f90  x23: 0x000000010001e069
   x24: 0x0000000000000000  x25: 0x000000019ff776c8  x26: 0xee009ec07c8c24c7  x27: 0x0000000000000020
   x28: 0x0000000000000000  fp: 0x000000016fdf29e0   lr: 0x0000000100017cf8
    sp: 0x000000016fdf2980   pc: 0x0000000100017d14 cpsr: 0x60000000
二進制鏡像

本部分列出終止時進程加載的二進制映像。
Listing 13:在崩潰報告中二進制鏡像部分中,應用程序的入口摘錄。

Binary Images:
0x100060000 - 0x100073fff TheElements arm64 <2defdbea0c873a52afa458cf14cd169e> /var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
...

了解低內存報告

了解低內存報告
當檢測到低內存條件時,iOS中的虛擬內存系統依靠應用程序的協作來釋放內存。低內存通知發送到所有正在運行的應用程序和進程作為一個釋放內存的請求,希望減少內存的使用。如果內存壓力仍然存在,系統可能會終止後臺進程以減輕內存壓力。如果有足夠的內存被釋放,你的應用程序將繼續運行。否則,您的應用程序將被iOS終止,因為沒有足夠的內存來滿足應用程序的需求,而低內存報告會被生成並存儲在設備上。

低內存報告的格式不同於其他崩潰報告,因為沒有應用程序線程的回溯。低內存報告與崩潰報告的頭部(Header)相似。下面的頭部是列出系統範圍內存統計信息的字段集合。記下Page Size字段的值。低內存報告中每個進程的內存使用情況以內存頁面數量的形式報告。

低內存報告中最重要的部分是進程表。此表列出了生成低內存報告時所有正在運行的進程,包括系統守護進程。如果一個進程被“拋棄”,原因將被列在 [reason] 欄中。一個進程可能會被拋棄的原因有很多:

  • [per-process-limit]:該進程超過了系統強加的內存限制。系統為所有應用程序中的每個進程建立駐留內存的限制。超過此限制使得流程符合終止條件。

Note:Extensions每個進程的內存限制要低得多。某些技術(如map views和SpriteKit)具有較高的基線內存消耗,可能不適合用於Extensions。

  • [vm-pageshortage] / [vm-thrashing] / [vm]:由於內存壓力,進程被終止。
  • [vnode-limit]:打開的文件過多。

Note:當vnodes(虛擬節點,系統中數據分片的單位)快用完時,系統可以避免殺死最前面的應用程序。這意味著當您的應用程序處於後臺,即使它不是使用多余的vnode的源頭,也可能被終止。

  • [highwater]:系統守護進程超過了內存使用的高位標記。
  • [jettisoned]:這個進程因為其他原因被拋棄(終止)了。
    如果在app/extension沒有列出原因,那麽崩潰的原因不是內存壓力。查找一個.crash文件(在上一節中介紹)以獲取更多信息。

當你看到一個內存不足的崩潰時,你應該調查你的內存使用模式和你對低內存警告的應對,而不是關心終止時正在執行什麽代碼。在應用程序中查找內存問題(Locating Memory Issues in Your App)列出了如何使用泄漏工具(Leaks Instrument)發現內存泄漏的詳細步驟,以及如何使用分配工具的標記堆(Allocations Instrument‘s Mark Heap)功能來避免廢棄的內存。 內存使用性能指南討論了響應低內存通知的正確方法,以及有效使用內存的許多技巧。還建議您查看WWDC 2010 session,使用工具進行高級內存分析(Advanced Memory Analysis with Instruments)。

Important:Leaks(泄漏)和Allocations(分配)工具不會跟蹤所有內存使用情況。您需要使用VM Tracker工具(包含在工具分配的模板中)運行您的應用程序以查看您的總內存使用情況。VM Tracker默認是禁用的。要使用VM跟蹤器來分析您的應用程序,請單擊該工具,檢查“Automatic Snapshotting(自動快照)”標誌或手動按下“Snapshot Now(快照)”按鈕。

相關文檔

有關如何使用工具僵屍模板修復關於內存過度釋放的崩潰的信息,請參閱Eradicating Zombies with the Zombies Trace Template。

有關應用程序archiving的更多信息,請參閱App Distribution Guide。

有關解析崩潰日誌的更多信息,請參閱Understanding Crash Reports on iPhone OS WWDC 2010 Session。


、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、轉自
作者:MrLittleWhite
鏈接:https://www.jianshu.com/p/574a5a6d00c5

如何看iOS崩潰日誌