了解和分析iOS Crash(上)

商業轉載請聯繫騰訊WeTest獲得授權,非商業轉載請註明出處

原文鏈接:http://wetest.qq.com/lab/view/404.html


WeTest 導讀

北京時間9月13日凌晨一點,蘋果一年一度的發布會如期而至。新機型的發布又會讓適配相關的同學忙上一陣子啦,並且iOS Crash的問題始終伴隨著移動開發者。本文將從三個階段,由淺入深的介紹如何看懂並分析一篇crash報告,一起身臨其境去讀懂它吧。

翻譯自蘋果官方文檔:Understanding and Analyzing Application Crash Reports

孟嵩:這篇萬字長文,大概前後翻譯了一個月,「寫」了三遍:第一遍是直譯,第二遍是把直譯改成程序員看著舒服的「行話」,第三遍是把原文里說的過於抽象或者簡單的部分加上我的註解(大家看見所有以孟嵩開頭的部分)。

當app發生crash時會產生crash report,這對我們定位crash的原因非常有幫助。該篇重點介紹了如何符號化、看懂並解析一篇crash Report。

孟嵩:

開篇給出了這個文檔的三個階段,由淺入深為:

1. 符號化,把不可讀的文檔轉成可讀

2. 看懂,意思就是知道文檔里哪個部分表達的什麼

3. 解析,意思就是能從文檔中定位問題,獲取解決問題的有價值的信息。

ps:文內展示代碼均可左右滑動查看

介紹

當app發生crash時,系統會生成crash report並存儲在設備上。crash report會描述app在何種情況之下被系統終止運行,一般情況下描述會包括完整的線程調用堆棧,這對app的調試(和問題的定位)是非常有幫助的。所以你應當仔細研讀這些crash report,去了解你的app究竟發生的是哪種crash,並嘗試修復它們。

Crash Report,尤其是堆棧信息,在被符號化之前是不可讀的。所謂符號化就是把內存地址用可讀的函數名和行數來替換。如果你不是從設備直接獲取的crash日誌,而是通過Xcode的Device Window(即通過視圖操作而非手動命令行),它們會在幾秒之後自動被符號化。當然你也可以把.crash文件加入到Xcode的Device Window並自行將它符號化。

Low Memory Report與其它crash report不同,它沒有堆棧信息。當由於低內存而發生crash時,你必須反思你的內存使用模式和你針對低內存警告的應對方法。本文會提供給你幾個內存管理的參考實現,供你參考。

獲取Crash Report和Low Memory Report

如何調試已經部署好的iOS Apps討論了如何從一個iOS設備直接拿到crash report和low memory report。

App發布指南里的分析Crash Reports討論了如何查看那些crash report,這些report既包含通過TestFlight下載的測試用戶處獲得,又包含通過App Store下載的正式用戶處獲得。

符號化一篇Crash report

符號化指的是一種手段,這種手段指的是把堆棧信息(二進位信息)解釋成源碼里的方法名或者函數名,也就是所謂符號。只有符號化成功後,crash report才能幫助開發者定位問題。

注意:Low Memory Report不需要被符號化(因為沒有堆棧信息)。

注意:在MacOS平台上產生的crash report在生成的時候一般都會被完全符號化過或者半符號化過。因此本節指的符號化針對的是從iOS、watchOS乃至tvOS中提取出來的crash report。整體處理流程上,macOS的carsh report比較類似。

[ crash上報和符號化過程概述 ]

1. 編譯器在把你的源代碼轉換成機器碼的同時,也會生成一份對應的Debug符號表。Debug符號表其實是一個映射表,它把每一個藏在編譯好的binary信息中的機器指令映射到生成它們的每一行源代碼中。通過build setting里的Debug Information Format(DEBUG_INFORMATION_FORMAT),這些Debug符號表要麼被存儲在編譯好的binary信息中,要麼單獨存儲在Debug Symbol文件中(也就是dSYM文件):一般來說,debug模式構建的app會把Debug符號表存儲在編譯好的binary信息中,而release模式構建的app會把debug符號表存儲在dSYM文件中以節省體積。

在每一次的編譯中,Debug符號表和app的binary信息通過構建時的UUID相互關聯。每次構建時都會生成新的唯一的能夠標識那次構建的UUID,即便你用同樣的源代碼,通過同樣的編譯setting,UUID也不會相同。相應的,dSYM文件也不能用於解析其它(UUID對應的)binary信息,即便構建自於同一個源代碼。

孟嵩:意思就是說,同一次構建,app+dSYM+UUID是一套的。如果這幾個文件不屬於同一次構建,即便是相同的源代碼,互相之間在符號化這個事情上也無法互相工作。

2. 當你為了分發app而選擇Archive(存檔)時,Xcode會把app的二進位信息和.dYSM文件存儲在你的home文件夾下的某個地方。你可以在Xcode的Organizer裡面通過」Archived」選項找到所有你存檔過的app。 更多存檔app的細節,請點擊官方文檔-分發你的App一文。

注意:想要解析來自於測試、app review或者客戶的crash report,你需要保留分發出去的那些構建過的archive文件。

3. 如果你是通過App Store分發app或者是Test Flight分發的beta版本的app,你將在上傳archive到ITC(iTunes Connect)時看見一個「是否將dSYM一起上傳」的選項。在上傳對話框中,請勾選」在app中包含app符號表」。上傳你的dYSM文件對於從TestFlight用戶和客戶以及願意分享診斷信息的客戶那邊接收crash report是很有必要的。更多詳情請參考官方文檔-分發你的App一文。

注意:接收自App Review的crash report是不會被符號化的,及時你再上傳你的app到ITC時勾選了包含dSYM文件。任何來自於App Review的crash report都需要在Xcode里做符號化。

4. 當你的app 發生crash時,一個沒有被符號化的crash report會被創建並存儲在設備上。

5. 用戶可以通過調試已部署的iOS APP里提到的方法來直接從他們的設備里獲得crash report。如果你通過AdHoc或者企業證書分發app,這是你唯一能從用戶獲取crash report的方法。

6. 從設備上直接獲取的crash report是沒有被符號化的,你需要通過Xcode來符號化。Xcode會結合dSYM文件和你app的二進位信息把堆棧里的每一個地址對應到源代碼中。處理後的結果就是一個符號化過的crash report。

7. 如果用戶願意和Apple共享診斷信息,或者用戶通過TestFlight下載了你的beta版本app,那crash report會被上傳到App Store。

8. App Store在符號化crash report後會把內部所有的crash reports做匯總並分組,這種聚合(相似crash report)的方法叫做crash聚類。

9. 這些符號化後的crash report可以在你的Xcode的Crash Organizer中進行查看。

Bitcode

Bitcode(位編碼)是一個編譯好的項目的中間表現形式。當你在允許bitcode的前提下Archive一個app時,編譯器會在二進位中包含bitcode而不是機器碼。一旦binary信息被上傳到App Store中,bitcode會被再次編譯成機器碼。也許App Store會在將來二次編譯bitcode,例如為提高編譯器性能而二次編譯等。不過這不重要,因為一切對你來說是透明的,也就不需要你來額外付出什麼。

[ 圖2 BitCode編譯過程概覽 ]

因為你的binary信息的最終編譯結果是在App Store上體現的,因此你的Mac將不會包含那些需要對從App Review或者用戶的設備那裡獲取到的Crash report所必須的符號化用的dSYM。

孟嵩:這裡原文很拗口,大概意思就是需要的東西都在App Store雲端,之後的操作會自動進行,見下文。

雖然當你Archive你的app時會創建dSYM文件,但它們只能用在bitcode binary信息中,並不能用於符號化crash report。 App Store允許你從Xcode或者ITC網站中下載這些隨著bitcode編譯而產生的dSYM文件。 為了解析從App Review或者給你發送crash report的用戶的crash report,你必須要下載這些dSYM文件,這樣才能符號化crash report。 如果是從crash reporting service那裡接收crash report,符號化會自動完成。

注意:App Store上編譯的binary信息和提交的原始文件的UUID是不同的。

從Xcode下載dSYM文件

· 在Archives organizer,選擇你之前提交到App Store的Archive文件

· 選擇Download dSYM按鈕Archive

Xcode會下載dSYM文件並且把他們插入到選擇的Archive中。

從ITC網站上下載dSYM文件

· 打開App詳情頁面

· 點擊 Activity

· 從所有的構建中,選擇一個版本

· 點擊 下載dSYM文件的鏈接

把」隱藏的」符號名還原成原始名

當你把一個帶有bitcode的app上傳到App Store時,你也許在提交對話框中並沒有勾選「上傳你的app的符號表信息以便從Apple那邊接收符號化過的 report」的選項。 當你選擇不發送符號表信息給Apple時,Xcode會在你發送app到ITC之前用晦澀難懂的符號例如」__hidden#109_」等來替換你的app里的dSYM文件。Xcode會創建一個原始符號和」隱藏」符號的對照表,並且將其存儲在Archive的app文件中的一個bcsymbolmap文件里。每一個dSYM文件都會有一個對應的bcsymbolmap文件。

在符號化crash report之前,你需要把那些從ITC中下載下來的dSYM文件中的晦澀信息給解析一下。 如果你使用Xcode中的下載dSYM按鈕,這步解析會自動完成。但是,如果你通過ITC網站來下載dSYM的話,你需要打開Terminal並且手動輸入下面的命令來做解析(把example的path信息和sSYM信息替換一下)

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文件都運行一次這條命令。

如何判斷Crash report是否已經符號化

一個crash report有可能未符號化,完全符號化,也有可能部分符號化。未符號化的crash report不會在堆棧信息中包含方法名或者函數名。相反,你會在載入好的binary信息中發現可執行的16進位地址信息。在完全符號化的crash report里,堆棧中的每一行16進位地址信息都會被替換成對應的符號。在部分符號化的crash report中,只有一部分堆棧信息被替換成相應的符號信息。

顯然,你應當儘力去完全符號化你的crash report,因為那樣你才能夠獲得crash report里最有價值的信息。一個部分符號化的crash report也許包含了可以理解crash的信息,這取決於crash的類型和哪一部分被成功符號化了。一個未符號化的crash report用處有限。

[ 相同堆棧信息下的不同程度的符號化 ]

用Xcode符號化iOS的Crash report

一般來說,Xcode會自動嘗試符號化它所有的Crash report。所以你只需要把crash report加到Xcode Organizer就可以了。

Note:Xcode只認.crash後綴的crash report。如果你收到的crash report沒有後綴名或者後綴是txt,在執行下列步驟之前先把它改成.crash。

· 把iOS設備連接到你的Mac

· 從Window菜單欄選擇Devices

· 在Devices左側,選擇一個設備

· 點擊右邊在「Device Information「 下面的 」View Device Logs」 按鈕

· 把你的Crash report拖拽到左側panel中

· Xcode會自動符號化Crash report並且顯示結果

為了符號化一個Crash report,Xcode需要去定位如下信息:

· 崩潰的app的binary信息以及dSYM文件

· 所有app關聯的自定義framework的binary信息以及dSYM文件。如果是從app構建出來的framework,它們的dYSM會隨著app的dSYM文件一起拷貝到archive中。如果是第三方的framework,你需要去找作者要dYSM文件。

· 發生crash時app所依賴的OS的符號表信息。這些符號表包含了特定OS版本

(例如iOS9.3.3)上的framework所需調試信息。 OS 符號表的架構具有獨特性——一個64位的iOS設備不會包含armv7的符號表。Xcode將要自動拷貝你連接到的特定版本的Mac的符號表。

在上述任何一處,如果沒有Xcode,你將無法符號化一個crash report,或者只能部分符號化一個crash report。

用atos符號化Crash report

atos命令可以把地址里的數字替換成等價的符號。如果調試符號信息是完備的,則atos的輸出信息將會包含文件名和對應的資源行數。atos命令可以被用來單獨符號化那些未符號化或者部分符號化過的crash report(中的堆棧信息里的地址)。

想要使用atos符號化crash report可以按如下方式操作:

1. 找到你想要符號化的那一行,記下第二列的binary信息名,以及第三列的地址。

2. 從crash report底部的binary信息名列表中找到那個名字,記下來架構名和載入的地址。

孟嵩:例如在下圖裡,我們想符號化的部分就是0x00000001000effdc,binary信息名是The Elements,底部能找到對應的名字的架構名稱是arm64,載入地址是0x1000e4000。

[ 在Crash report里提取出使用atos所需要的信息 ]

1. 定位二進位對應的dSYM文件。你可以用Splotlight,結合UUID,來尋找匹配的dSYM文件。(請查看相關章節。)dSYM是一個bundle,包含通過編譯器在build時編譯出來的DWARF調試信息(nimo: DWARF的可能的解釋是,Debugging With Attributed Record Formats,是一種調試文件結構標準,結構相當的複雜)。你在使用atos時必須提供這個文件的路徑,而不是dSYM的bundle路徑。

2. 有了上述信息之後,你就可以把堆棧里的地址通過atos命令來符號化了。你可以符號化多條地址,通過空格來進行區分。

atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>

清單1 使用atos命令的樣例,以及結果輸出

$ atos -arch arm64 -o TheElements.app.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc

-[AtomicElementViewController myTransitionDidStop:finished:context:]

利用符號化排查問題

如果Xcode沒有完全符號化一個crash report,很可能是你的Mac丟失了app binary信息對應的dSYM文件,或者是丟了一個或多個app關聯的framework的dSYM文件,也有可能在發生Crash時OS層面的app的設備符號表丟失了。下列步驟顯示了如何使用Spotlight來判斷那些可以符號化對應堆棧地址信息的dSYM文件是否在你的Mac上。

[ 定位一個二進位鏡像 ]

1. 在Xcode無法符號化的堆棧里找一行,注意第二列的binary信息的名字。

2. 在crash report的底部中的二進位信息列表裡找到那個名字。這個列表包含了每一個crash事故現場存在於進程里的二進位信息的UUID。

孟嵩:本例中需要關注的binary信息的名字是The Element,在底部列表中對應的二進位信息的UUID是77b672e2b9f53b0f95adbc4f68cb80d6

列表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信息。

列表3 使用mdfind命令來通過給定UUID查找dSYM文件。

$ mdfind "com_apple_xcode_dsym_uuids == <UUID>"

1. 如果spotlight找到了UUID對應的dSYM文件,mdfind會把dSYM文件和可能包含的歸檔文件的路徑列印出來。如果一個UUID對應的dSYM文件沒有找到,mdfind會直接退出。

如果spotlight找到了二進位對應的dSYM文件,但是Xcode沒有能結合二進位信息成功把地址符號化,那你應該上報一枚bug並且把crash report和對應的dSYM文件一起附到bug report中。作為權宜之策,你可以手動用atos來對地址進行符號化。

如果spotlight沒有找到二進位信息對應的dSYM文件,確保你還有app發生crash的那個版本的Xcode歸檔文件,並且這個文件存在於spotlight可以找到的某個地方。如果你的app是支持bitcode方式構建的,確保你已經從App Store下載了最終編譯版本的dSYM文件。

如果你覺得你已經有了二進位信息對應的正確的dSYM文件,那你可以用dwarfdump命令來列印對應的匹配UUID。你也可以用用dwarfdump命令來列印二進位的UUID。

xcrun dwarfdump --uuid <Path to dSYM file>

注意:你必須保存你最開始上傳到App Store的發生crash的app的歸檔文件。dSYM文件和app二進位文件是一一對應,且每次構建都不相同。即便通過相同的源碼和配置,再執行一次構建,生成的dSYM文件也無法和之前的crash report做符號化匹配。

如果你不在存有這個歸檔文件,你應該重新提交一次有歸檔的新版本,以確保再發生crash的時候你可以符號化crash report。

分析Crash report

這段將會討論一篇標準crash report的各章節的含義。


接下文《了解和分析iOS Crash(下)》


推薦閱讀:

TAG:iOS | iOS開發 | 科技 |