前言:
最近測(cè)試妹子老是抱怨我偶現(xiàn)的Bug不好復(fù)現(xiàn),我這邊出于偷懶(其實(shí)是工作很忙)一直再說不能復(fù)現(xiàn)Bug的妹子不是好測(cè)試,最近閑下來了,正好談?wù)凜rash的收集和分析。
一、Crash收集
噔噔噔噔~萬能的官方文檔又出現(xiàn)了,先上地址
Understanding and Analyzing Application Crash Reports
通過上圖我們可以發(fā)現(xiàn)Crash的收集主要有兩種方式。
1、使用Xcode從設(shè)備獲取崩潰日志:
如果你把你的手機(jī)連接到Mac,并選擇Xcode->Windows->Device and Simulator,然后點(diǎn)擊View Device Logs,你會(huì)看到手機(jī)上會(huì)有好多Log,其中Type為Crash的就是崩潰的Log,如下圖:
2、通過設(shè)備直接獲取崩潰日志(此處以最新版iOS11為例,其他版本可能有些許不同)
1)打開設(shè)置->隱私->分析->分析數(shù)據(jù),在其中找到你想要的應(yīng)用程序的日志,日志將使用以下格式命名:<應(yīng)用名稱> _ <崩潰時(shí)間> _ <設(shè)備名>
2)選擇所需的日志,復(fù)制文本或點(diǎn)擊右上角的分享按鈕分享出去,并且把分享得到的.ips.synced或者復(fù)制文本而來的.txt文件的后綴名改為.crash,因?yàn)閄code不接受沒有.crash擴(kuò)展名的崩潰日志。
3、蘋果爸爸審核時(shí)候給你發(fā)的.crash文件(手動(dòng)滑稽)
What the fuck??面對(duì)一大串的16進(jìn)制數(shù)字你可能會(huì)感到一臉懵逼,莫驚慌,如果你仔細(xì)的看了看官方文檔,你就會(huì)發(fā)現(xiàn)收集Crash的Log是一件很輕松的事情,然而分析Crash卻并不是那么容易的事情(看完這篇文章后你會(huì)發(fā)現(xiàn)也很容易!!!!)
我們能把16進(jìn)制數(shù)字轉(zhuǎn)換成能看懂的東西么?當(dāng)然可以,這個(gè)時(shí)候就需要理解dSYM符號(hào)集,細(xì)心的小伙伴在看第一張Crash流程圖中可能已經(jīng)發(fā)現(xiàn).xcarchive文件中包含了.dSYM文件。
dSYM符號(hào)集:
- 符號(hào)集是我們每次Archive一個(gè)包之后,都會(huì)隨之生成的.dSYM文件,這個(gè)文件必須使用Xcode進(jìn)行打包才有(Debug模式默認(rèn)是關(guān)閉的)。每次發(fā)布一個(gè)版本,我們都需要備份這個(gè)文件,以方便以后的調(diào)試。
- 符號(hào)集中存儲(chǔ)著文件名、函數(shù)名、行號(hào)與內(nèi)存地址的映射表,通過符號(hào)集分析崩潰的.Crash文件可以準(zhǔn)確知道具體的崩潰信息。
- 我們?nèi)绻皇褂?dSYM文件獲取到的崩潰信息都是不完全的(官方文檔說了會(huì)導(dǎo)致不完全符號(hào)化,也就是一部分符號(hào)化好了,一部分沒有)。
- 每一個(gè).dSYM文件都有一個(gè)UUID,和.app文件中的UUID對(duì)應(yīng),代表著是一個(gè)應(yīng)用,而.dSYM文件中每一條崩潰信息也有一個(gè)單獨(dú)的UUID,用來和程序的UUID進(jìn)行校對(duì)。
符號(hào)集的生成與獲取:
- 符號(hào)集在Organizer中選中打包的Archive->Show in Finder中選中Archive,右鍵顯示包內(nèi)容下的dSYMs文件夾下(或者點(diǎn)擊Organizer右邊的Download dSYM,XCode會(huì)從App Store下載該文件并插入到此Archive中)。
- 如果在Debug模式下,找到項(xiàng)目的Build Settings
把Debug Infomatiion Format設(shè)置成DWARF with dSYM file
并把Generate Debug Symbols置為YES
然后編譯,在項(xiàng)目文件夾Products中找到.app文件右擊Show in Finder找到dSYM文件
Xcode設(shè)置圖
看到這里你可能已經(jīng)知道,通過dSYM中存儲(chǔ)的信息可以把crash日志中的16進(jìn)制數(shù)字一一對(duì)應(yīng)成我們看得懂的文件名、函數(shù)名和行號(hào),這個(gè)過程就叫做符號(hào)化,那么如何做呢?
二、校驗(yàn)文件
在符號(hào)化Crash文件之前,你需要準(zhǔn)備好.crash和.dSYM并校驗(yàn)是否匹配為什么要校驗(yàn):
- 因?yàn)榉?hào)集存儲(chǔ)著文件名、函數(shù)名、行號(hào)的信息,每一次代碼更改后編譯符號(hào)集也會(huì)隨之變更,所以要想符號(hào)化.crash文件,.crash與符號(hào)集必須一一對(duì)應(yīng)
- 也就是說由版本為1.0的代碼生成了1.0的APP,同時(shí)生成了1.0的符號(hào)集,1.0的APP發(fā)生了Bug,生成了104115的crash文件,也只有1.0的符號(hào)集才能夠符號(hào)化104115的crash文件,而同時(shí)也必須找到1.0的代碼才能根據(jù)符號(hào)化的crash文件精確定位到bug產(chǎn)生的地方。
如何判斷.crash、.dSYM與.app(是否匹配你的代碼)是否匹配?
- 通過UUID來匹配,UUID是Xcode在編譯時(shí)自動(dòng)為每個(gè)版本生成的唯一標(biāo)識(shí),即使功能相同的可執(zhí)行文件是使用相同的編譯器設(shè)置從相同的源代碼重建的,它也將具有不同的構(gòu)建UUID,總之UUID是唯一的。
如何通過命令行獲取UUID?
獲取.crash的UUID
grep "'Your AppName' arm64" t.crash
獲取.dSYM的UUID
dwarfdump --uuid 'Your AppName'.app.dSYM
獲取.app的的UUID
dwarfdump --uuid 'Your AppName'.app/'Your AppName'
比如上圖能看到三者的UUID都是一致的,可以安心去符號(hào)化文件啦。
三、符號(hào)化文件
1、通過XCode自動(dòng)符號(hào)化Crash文件
1)如果本地存在.crash對(duì)應(yīng)的.dSYM文件,則直接到上文中(1、使用Xcode從設(shè)備獲取崩潰日志:)到View Device Logs這步,把文件拖入右邊的logs列表,Xcode會(huì)自動(dòng)去符號(hào)化文件,如果滿眼都是16進(jìn)制數(shù)字的化,點(diǎn)擊Re-Symbolicate Log即可
2)如果此時(shí)本地的Archive文件已經(jīng)被你刪除,需要把上述兩個(gè)文件放入同一目錄下(全英文目錄),如果.dSYM你并沒有備份,則需要回到crash日志對(duì)應(yīng)的版本重新打包(論版本控制的重要性!),重復(fù)1)的步驟也可以得到符號(hào)化的日志。
2、通過命令行工具symbolicatecrash符號(hào)化
如果你不想用Xcode去符號(hào)化,你也可以通過symbolicatecrash來手動(dòng)符號(hào)化crash日志,symbolicatecrash是Xcode下的一個(gè)工具。
1)首先先找到這個(gè)工具,我們通過Spotlight搜索找到symbolicatecrash并復(fù)制到桌面的CrashSignifying文件夾中,在這個(gè)文件夾下同樣放入.crash、.dSYM文件。
2)打開終端,進(jìn)入你剛才創(chuàng)建的CrashSignifying文件夾中,輸入命令行
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer?
然后在輸入
./symbolicatecrash /Users/你的電腦用戶名/Desktop/CrashSignifying/xxx(崩潰日志名字).crash /Users/你電腦的用戶名/Desktop/CrashSignifying/xxxx(dSYM文件名字).dSYM > Symbol_Crash.crash
如果報(bào)No such file or directory : at ./symbolicatecrash line 909.錯(cuò)誤,嘗試執(zhí)行
./symbolicatecrash ./*.crash ./*.app.dSYM>Symbol_Crash.crash
這樣crash文件就被符號(hào)化完成了,打開符號(hào)化如下圖:四、Crash分析
以上圖為例,大部分字段都是不言而喻的,下面列舉一些有用處的。(在官方文檔都有解釋,這里做歸納與翻譯)
Incident Identifier: 報(bào)告的唯一標(biāo)識(shí)符,兩份報(bào)告決不會(huì)共享同一個(gè)事件標(biāo)識(shí)符。
CrashReporter Key:每個(gè)設(shè)備的匿名標(biāo)識(shí)符,來自同一設(shè)備的兩個(gè)報(bào)告將包含相同的值。
Process:很明顯是我們的進(jìn)程名稱。
Date/Time 與 Launch Time:報(bào)告生成時(shí)間與程序開始運(yùn)行時(shí)間
Exception Type:異常類型
Exception Note:不屬于異常類型的附加信息,如果這個(gè)字段包含SIMULATED(不是崩潰),那這個(gè)進(jìn)程不是崩潰的,而是在系統(tǒng)的請(qǐng)求下被殺死,通常是看門狗機(jī)制起了作用(APP內(nèi)一段時(shí)間內(nèi)無法響應(yīng)用戶的操作,會(huì)被系統(tǒng)kill)。
接著是最重要的堆棧信息,由下到上為最后調(diào)用的順序:
可以很明顯的看到,一個(gè)名為ViewController的對(duì)象在
viewDidLoad
方法中調(diào)用了第35行的testMethodTwo
方法,并執(zhí)行testMethodTwo
方法中的61行時(shí)代碼,在Backtrace第2行可以發(fā)現(xiàn)調(diào)用了未識(shí)別的方法導(dǎo)致崩潰,我們來看下代碼。最終我們找到了崩潰的原因:一個(gè)NSArray調(diào)用了addObject方法,so easy!
一些較常見的異常類型(如果翻譯錯(cuò)誤請(qǐng)指正):
內(nèi)存訪問不良[EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
通常用于訪問了不該訪問的內(nèi)存導(dǎo)致或者嘗試以不允許的方式訪問內(nèi)存(例如只讀屬性,比如在上一篇我們提到的通過KVO更改只讀屬性),并且" Exception SubType"字段會(huì)包含kern_return_t來描述錯(cuò)誤和未正確訪問的內(nèi)存地址。
下面是官方給出的建議:
- 如果
objc_msgSend
或objc_release
接近崩潰線程的Backtraces(堆棧信息回溯)的頂部,則該進(jìn)程可能試圖向釋放的對(duì)象發(fā)送消息,可以使用Zombie Instrument來分析應(yīng)用程序,以更好地了解此次崩潰的情況。- 如果
gpus_ReturnNotPermittedKillClient
接近崩潰線程的Backtraces的頂部,則該進(jìn)程被終止,因?yàn)樗鼑L試在后臺(tái)使用OpenGL ES或Metal進(jìn)行渲染。
異常退出[EXC_CRASH // SIGABRT]
該進(jìn)程異常退出,此異常類型崩潰的最常見原因是向?qū)ο蟀l(fā)送了無法識(shí)別的消息,比如上文中向NSArray發(fā)送了addObject消息。
另外如果App Extensions需要太多時(shí)間來初始化(看門狗機(jī)制),那么App Extensions將終止于此異常類型,如果擴(kuò)展因啟動(dòng)時(shí)掛起而死亡,則生成的崩潰報(bào)告的Exception Subtype將會(huì)是LAUNCH_HANG
,由于擴(kuò)展沒有main
函數(shù),任何花在初始化上的時(shí)間都會(huì)在+load
擴(kuò)展庫(kù)和相關(guān)庫(kù)中的靜態(tài)構(gòu)造函數(shù)和方法中,你應(yīng)該盡可能多地推遲這項(xiàng)工作。
資源限制[EXC_RESOURCE]
該過程超出了資源消耗限制,這是來自操作系統(tǒng)的通知,該進(jìn)程正在使用太多的資源,確切的資源列在Exception SubType字段中。如果Exception Note字段包含
NON-FATAL CONDITION
,則即使生成崩潰報(bào)告,該進(jìn)程也不會(huì)被終止。
- 異常子類型
MEMORY
表示該進(jìn)程已超過系統(tǒng)施加的內(nèi)存限制。- 異常子類型
WAKEUPS
表示進(jìn)程中的線程每秒被喚醒的次數(shù)過多,這迫使CPU醒來的頻率很高,并且消耗電池壽命。
五、總結(jié)
文章寫了一半開始忙了起來,第三第四節(jié)的內(nèi)容現(xiàn)在才補(bǔ)上來(已經(jīng)三天過去啦),如果能給小伙伴帶來一點(diǎn)幫助,那我就很開心了,咱們下周再見。