在調試自己和別人的IOS App時,發生Crash是非常正常的情況,分析這些Crash的主要手段之一就是分析Crash發生時產生的錯誤日志。對於未越獄的IOS設備,獲取錯誤日志主要通過Xcode自帶的日志獲取功能,但是這種方式有以下兩點限制:
1.只能獲取開發者自己開發的App的日志,無法獲取第三方App的日志。
2.自動化工作難度較高(錯誤日志收集,錯誤日志整理等)。
文中,筆者首先簡單介紹了一下使用Xcode抓取和分析錯誤日志的方法,然后為了克服上面的兩個缺陷,筆者調研了一套可自動化的第三方App在本地測試的錯誤日志抓取方法。最后,筆者簡單介紹了一下IOS錯誤日志的基本構成,供讀者進行日志分析和分類收集參考。
錯誤日志的獲取
方法1: 使用Xcode獲取錯誤日志
對於自己研發並在測試機上測試的應用,我們可以通過Xcode來分析測試過程中的錯誤日志。但是App一但發布到AppStore,那么我們如何獲取用戶在使用過程中產生的錯誤日志呢?為了解決這一問題,蘋果官方提供了一套供App開發者使用的基於Xcode工具的解決方案。
對於App在測試機上運行的情況,Xcode支持獲取Apple機器的Log的情況。打開Xcode並打開自己開發的AppProject,在Window->Devices中,找到對應的設備
點擊View Device Logs
在此處可以查看設備的日志信息,確認測試機連接到Mac,即可看到已經符號化完成的錯誤日志
對於在AppStore已經發布的App產生的錯誤日志,在Window->Organizer中,找到對應的App,在Crashes目錄中找到AppStore下載的其他使用者使用過程中產生的錯誤日志。
方法2: 使用命令行獲取錯誤日志
蘋果官方提供的方案雖然能夠成功的獲取錯誤日志,但是有一個前提,那就是用戶必須是App的開發者,如果我們需要獲取第三方開發者開發的App的錯誤日志,那么上述方法就不能正確的工作了。適用於這一場景的可行方案之一,是使用libimobiledevice庫中的idevicecrashreport命令來獲取。
libimobiledevice庫:libimobiledevice 是Github上的一個開源庫,主要提供和未越獄的蘋果IOS設備通信的服務。包括App安裝,系統日志獲取,設備信息獲取和錯誤日志獲取等能力(其他具體的服務能力可以參考libimobiledevice的官網:http://www.libimobiledevice.org/)。
本文具體主要使用idevicecrashreport和ideviceinstaller命令。
假設你獲取的第三方App的Ipa已經發布到App Store,那么你可以直接從App Store上下載並測試。如果未發布到App Store,則需要使用ideviceinstaller命令安裝該App到指定的手機。具體的command如下:
出現如下日志后安裝成功(IOS > 9的設備使用ideviceinstaller會出現Segmentation fault,但是不影響App本身的安裝)
然后就可以愉快地測試App了:)
App出現Crash后,ios設備本身保留了CrashReport。為了將設備上的CrashReport拷貝到本機,就需要使用idevicecrashreport命令了。具體的command如下:
如果需要在設備上保留錯誤日志,使用-k選項;如果需要將crashreport單獨存放,使用-e選項。
導出后的文件結構如下所示:
其中wifi為IOS的wifi信息,此處我們並不關心。我們關注其中CrashDemo開頭的兩個后綴為ips的文件(CrashDemo是我用來測試的App名)。使用Xcode打開一個ips文件。如下所示:
這樣我們就成功得到了App的錯誤日志。
該方法的優點:
-
可以獲取第三方App的錯誤日志。
-
方法全部通過命令行方式進行獲取,方便編寫shell進行自動化獲取
該方法的缺點:
無法獲得AppStore上使用者相關的錯誤日志。
錯誤日志的符號化
我們可以注意到,上述第二種錯誤日志的獲取方法所獲取到的日志,日志中的堆棧信息並不像Android等錯誤日志中的堆棧信息那樣打印出了調用的堆棧,而是使用段基址 + 偏移地址的格式顯示。除此之外,對於錯誤信息,也進行了相關的處理。為了能夠正確的分析錯誤,我們需要對這些地址進行符號化,從而將日志翻譯成能夠處理的信息。一般來說,符號化日志需要開發者提供編譯App后生成的dsym文件,該文件包含了App編譯后的所有類和函數的地址信息。但是如果我們需要分析第三方開發者的App,那么我們是無法獲得該App的dsym文件的。下面給出了無dsym文件場景下符號化日志信息的方法。
不帶dsym文件的日志符號化
使用symbolicate符號化錯誤日志文件
symbolicate:symbolicate是github開源的三方腳本,該腳本使用xcode自帶的符號化工具symbolicatecrash進行符號化。symbolicate支持兩種符號化的方法。使用App的dsym文件和使用App的ipa解壓后的App文件夾。
那么為什么不直接使用xcode的symbolicatecrash命令進行符號化呢?這一點我們可以通過分析symbolicate.sh的源碼來了解。
通過分析源碼,我們可以看出xcode自帶的symbolicatecrash工具的主要缺陷有兩點:
1. 工具隨着Xcode版本有着比較大的變更。
2. 缺少對錯誤日志UUID和App有效性的校驗。
在我們的場景中,我們沒有App的dsym文件,所以我們主要依賴App的ipa解壓后的文件夾來進行調試,解壓ipa可以使用mac自帶的unzip命令
解壓后的app目錄在appdirpath/Payload目錄下,在Finder中顯示如下:
symbolicate腳本的使用方法如下:
符號化后的錯誤日志文件如下:
可以看見,堆棧信息的部分的已經完成了符號化,現在我們可以開始錯誤日志的分析了。
錯誤日志的分析
在完成了錯誤日志的符號化之后,接下來就是對錯誤日志的分析了,蘋果官方提供的錯誤日志格式還是非常詳細的。下面簡單介紹錯誤日志(CrashReport)的基本格式。
CrashReport基本格式
IOS在以下兩種情況下會產生錯誤日志:
1.應用違反操作系統規則。
2.應用中有Bug。
1中“違反操作系統規則“主要包括watchdog終止,用戶強制結束和低內存終止三種情況。
2則是應用本身導致的問題。
1和2的日志格式不同,本文主要介紹2的情況下產生的錯誤日志的基本格式。
下圖是一個錯誤日志的例子
整個錯誤日志分為進程信息、Crash信息、Crash異常、線程回溯、線程狀態和二進制映像6個部分。
進程信息:包括Crash的ID(Incident Identifier),設備的Model和標識符(CrashReporter Key和Hardware Model),App信息(Path、Identifier、Version和CodeType),以及啟動的進程信息(Process、ParentProcess)。
Crash信息:包括Crash發生的時間和系統號
Crash異常:包括異常類型(Exception Type)和異常碼(Exception Codes)。此處對經常出現的異常碼匯總如下(蘋果的異常碼還是很生動的)
0x8badf00d:(ate bad food)這個異常碼表示該異常是由watchdog終止引發的。
0xbad22222:這個異常碼表示該異常是由VoIP被過於頻繁重啟引發的。
0xdeadl0cc:(dead lock)這個異常碼表示該異常是由於App長期占用系統資源而被系統強行終止引發的異常。
0xdeadfa11:(dead fail)這個異常碼表示該異常是由用戶強制終止引發的。
線程回溯信息:
回溯信息包括四列:
幀編號
二進制庫的名稱
調用方法的地址
第四列分為兩個子列,一個基本地址和一個偏移量。
對於未符號化的日志,這一列的內容比較難以理解
對於符號化后的日志,該列內容如下:
分別為該進程調用的函數名稱和對應的代碼行號
線程狀態:Crash中寄存器中的值(不常用,堆棧信息足以完成大部分的Crash分析)
二進制映像:Crash發生時已經載入的二進制文件
以上就是本文的全部內容了,文中涉及到的開源庫的Github地址如下:
libimobiledevice: https://github.com/libimobiledevice/libimobiledevice
symbolicate: https://github.com/JohnWong/symbolicate