最初始的需求是:怎么定位線上的奔潰。
那就是捕獲NSException
创橄,收集發(fā)送到后臺(tái)統(tǒng)一處理弟翘〕嬉福或者接入第三方奔潰統(tǒng)計(jì)的庫(kù),讓他們把這些都做了稀余。
這里面都有一個(gè)繞不過(guò)的問(wèn)題是悦冀,得到的信息里有些是未符號(hào)化、顯示為內(nèi)存地址的信息:
怎么把地址轉(zhuǎn)為對(duì)應(yīng)的方法/函數(shù)就是符號(hào)化的任務(wù)睛琳。為啥叫符號(hào)化盒蟆?在編譯鏈接系統(tǒng)中,函數(shù)师骗、變量這些都被認(rèn)為是符號(hào)历等,有一個(gè)叫符號(hào)表的東西:
在符號(hào)表中,程序源代碼中的每個(gè)標(biāo)識(shí)符都和它的聲明或使用信息綁定在一起辟癌,比如其數(shù)據(jù)類型寒屯、作用域以及內(nèi)存地址。
所以解析過(guò)程需要的材料就是符號(hào)表黍少,用它就可以用地址反向定位它的符號(hào)寡夹,得到需要的方法名。
- 對(duì)于我們的APP厂置,符號(hào)表就在dSYMs文件里菩掏,dSYMs應(yīng)該就是debug symbols的縮寫。
- 對(duì)于系統(tǒng)庫(kù)昵济,如UIKit智绸,在它們的framework里野揪。/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/UIKit.framework
奔潰信息的獲取
- 手機(jī)直接導(dǎo)入:window --> device --> view device logs. 這里面是格式化的crash文件,而且用Xcode查看時(shí)會(huì)自動(dòng)符號(hào)化传于。
- 在App Store上查看囱挑,這個(gè)需要用戶在設(shè)置里同意了上傳奔潰信息。
- 前面兩種幾乎沒(méi)什么暖用沼溜,對(duì)于線上的奔潰信息平挑,得自己獲取:
//沒(méi)有捕獲的異常會(huì)調(diào)用這里設(shè)置的函數(shù)
NSSetUncaughtExceptionHandler(analyzeAndSaveException);
void analyzeAndSaveException(NSException *exception){
NSArray<NSString *> *symbols = [exception callStackSymbols];
NSString *reason = [exception reason];
NSDictionary *userInfo = [exception userInfo];
NSString *name = [exception name];
//按某種格式存到本地系草,然后上傳到服務(wù)器
}
測(cè)試了下通熄,在這個(gè)函數(shù)執(zhí)行結(jié)束前,程序是不會(huì)狗帶的找都,網(wǎng)絡(luò)請(qǐng)求這種需要等待回調(diào)的肯定是不行了唇辨,但寫到本地文件里還是可以的。
重點(diǎn)就是這個(gè)callStackSymbols
能耻,是引起奔潰的函數(shù)的調(diào)用棧赏枚,比如:
這里就是部分符號(hào)化的,4-17這些都是顯示的內(nèi)存地址晓猛。如果是打包后安裝的APP饿幅,調(diào)用棧跟上面直接連接xcode運(yùn)行的又不同,關(guān)鍵性的第三點(diǎn)是這樣的:
"3 CrashAnalyze 0x00000001000dbda8 CrashAnalyze + 32168"
符號(hào)化命令
atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>
- -arch 指定對(duì)應(yīng)的cpu架構(gòu)
- -o 指定符號(hào)表路徑戒职,對(duì)APP本身的方法是
dSYM文件路徑/Contents/Resources/DWARF/app名稱
,對(duì)系統(tǒng)方法是/Users/shiwei/Library/Developer/Xcode/iOS\ DeviceSupport/10.3.3\ \(14G60\)/Symbols/System/Library/Frameworks/庫(kù)名.framework/庫(kù)名
栗恩,注意路徑有個(gè)版本區(qū)別。 - -l 指定load address,就是這個(gè)庫(kù)加載的地址
- 最后的是需要被符號(hào)化的地址
從命令里可以看出洪燥,奔潰日志需要這些信息:
- 機(jī)型磕秤,有了這個(gè)就知道cpu架構(gòu)了
- 系統(tǒng)版本,有系統(tǒng)版本可以知道對(duì)應(yīng)的系統(tǒng)庫(kù)的版本捧韵,但最好是每個(gè)庫(kù)的UUID市咆,ID總是不會(huì)錯(cuò)的。
- 每個(gè)庫(kù)的加載地址纫版。
以前也有一些符號(hào)化和dSYMs文件的文章和工具床绪,我試了下,好像沒(méi)用了其弊。區(qū)別是沒(méi)有考慮load address的問(wèn)題癞己,具體的我沒(méi)仔細(xì)考證。
怎么知道是哪個(gè)庫(kù)的方法梭伐?
- 調(diào)用棧里的每條前面都有庫(kù)的名字痹雅,自己APP的代碼就是APP的名稱
- 我觀察到一點(diǎn):加載地址是有區(qū)間的,比如加載地址從小到大是:libA --- libB --- libC,而需要符號(hào)化的地址落在libB和libC之間糊识,那么就是libB的函數(shù)了绩社。
怎么獲取加載地址摔蓝?
#import <mach-o/dyld.h>
.....
uint32_t numImages = _dyld_image_count();
for (uint32_t i = 0; i < numImages; i++) {
const struct mach_header *header = _dyld_get_image_header(i);
const char *name = _dyld_get_image_name(i);
const char *p = strrchr(name, '/');
if (p) {
NSLog(@"module=%s, address=%p", p + 1, header);
}
}
怎么獲取UUID?
iOS 如何獲取 Mach-O 的 UUID
測(cè)試:
//對(duì)APP本身
atos -arch arm64 -o ~/Desktop/CrashAnalyze.app.dSYM/Contents/Resources/DWARF/CrashAnalyze -l 0x100068000 0x10006fd8c
-[ViewController triggerException] (in CrashAnalyze) (ViewController.m:61)
//對(duì)UIKit
atos -arch arm64 -o /Users/shiwei/Library/Developer/Xcode/iOS\ DeviceSupport/10.3.3\ \(14G60\)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit -l 0x196f2d000 0x0000000196f71bd4
-[UIControl sendAction:to:forEvent:] (in UIKit) + 80
系統(tǒng)庫(kù)的符號(hào)化更多看這里
最后
這些東西如果要自己手動(dòng)處理確實(shí)很麻煩,因?yàn)楸匾臄?shù)據(jù)和材料都可以上傳給服務(wù)器愉耙,所以做一套系統(tǒng)贮尉,自動(dòng)上報(bào)crash信息,后臺(tái)根據(jù)提供的符號(hào)表或dSYMs文件朴沿,再加上各個(gè)系統(tǒng)庫(kù)的各個(gè)版本的符號(hào)表猜谚,就可以讓程序自動(dòng)去處理這些。
這樣一個(gè)自動(dòng)化的系統(tǒng)才更有價(jià)值吧赌渣!
然后看了下Bugly做了這樣的處理魏铅,Bugly iOS 符號(hào)表配置
Understanding and Analyzing Application Crash Reports
iOS Crash分析必備:符號(hào)化系統(tǒng)庫(kù)方法
符號(hào)表