前言
我們在開發App過程中,因為連接到控制台,所以遇到問題會很容易找到問題代碼。但是對於線上的App出現Crash的時候,我們不可能通過這種方式,也不現實,所以我們只能通過收集Crash信息,來解決Bug。而這種收集Crash信息並且分析定位到具體代碼的第三方SDK很多。但是今天我們來自己實現一下。
收集 Crash 信息
Apple
提供了NSException
類來幫助我們收集異常信息。
NSException is used to implement exception handling and contains information about an exception — Apple Documentation.
點擊這里來查看官方文檔具體內容。
我們的確可以通過NSException
來收集信息,但是,我們怎么把這個信息保存下來,並且上傳到我們后台服務器,收集起來呢。這就需要用到另一個函數:NSUncaughtExceptionHandler
Sets the top-level error-handling function where you can perform last-minute logging before the program terminates.http://www.90168.org/
意思就是我們可以在App異常退出的之前有一分鍾的時間來處理異常信息,利用這段時間,我們可以把Crash信息寫入本地,也可以上傳到服務器,但是考慮到網絡阻塞原因,我們可能在這一分鍾不能操作完畢,所以我們把上傳放到下一次App啟動時執行。
具體的代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
- (void)lyCarshLog { [self uploadExceptionLog]; NSSetUncaughtExceptionHandler(&catchExceptionLog); } - (void)uploadExceptionLog { if (log != nil) { // 在這里上傳 Crash 信息,上傳完畢后要記得清空。 } } void catchExceptionLog(NSException *exception) { // 獲取 Crash 信息 NSArray *symbols = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSDictionary *userInfo = [exception userInfo]; //... /*另外,我們可能需要一些別的信息,比如說發生 Crash 的設備的系統版本,設備型號,App的版本號*/ struct utsname systemInfo; // 需要導入`sys/utsname.h`頭文件。 uname(&systemInfo); NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; NSDictionary *appInfo = [[NSBundle mainBundle] infoDictionary]; NSString *appVersion = [appInfo objectForKey:@"CFBundleShortVersionString"]; NSString *result = [NSString stringWithFormat:@"CarshReason = %@ \n name = %@ \n userInfo = %@ \n log = %@ \n systemVersion = %f \n deviceInfo = %@ \n appVersion = %@ ",reason,name,userInfo,symbols,[UIDevice currentDevice].systemVersion.floatValue,deviceString,appVersion]; // 把 result 寫入本地。 } |
Crash
信息至此已經收集完畢,等待下次App啟動的時候,我們把本地的Crash信息上傳到服務器就OK了。
處理 Crash 信息 - 符號化(Fully Symbolicated)
我們得到的信息可能如下(Partially Symbolicated):
Tips: 堆棧跟蹤是
自下而上
展示的,也就是最先調用的方法在最下面。
其中:
Binary name
表明代碼所在App
或者Framework
的位置。比如:line 0 是在CoreFoundation
中,line 3 在CrashDemo
中…Address
方法的內存地址。Class name
當前的類名。Method name
當前調用的方法名。Offset
相對加載地址/基地址(load address)
的偏移量。
我們得到這個半符號化(Partially Symbolicated)的日志對我們分析Crash原因的幫助很有限,因為我們可能只能知道__NSArrayI objectAtIndex:
調用出現了問題,但是不能定位到具體代碼。所以我們要把它完全符號化(Fully Symbolicated)。
dSYM
我們需要借助dSYM
來幫助我們完成符號化,對於dSYM
文件的獲取,我們可以通過多種方法,我這里只說一種:
先打開Xcode
,Windows
->Organize
->找到對應的app包,然后右鍵
->Show in finder
,找到appName. xcarchive
->顯示包內容
->把dSYMs拷貝出來(或者就在里面操作)
。
atos
The atos command converts numeric addresses to their symbolic equivalents
我們使用atos
命令來完成符號化,具體命令如下: $ atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>
其中:
- Binary Architecture:
arm64
、armv6
、armv7
armv7s
根據自己的情況來寫。 - Path to dSYM file: dSYM文件的路徑。
- binary image name: 你工程的名字。
- load address: 基地址,如果我們的崩潰日志中沒有這個信息(比如上面的Crash信息中就沒有包含),就需要我們手動去計算這個
load address
:laod address = address to symbolicate - offset
,比如:0x0000000102838119
轉化為十進制為4337139993
,再減去偏移量265
,為4337139728
,在轉化為十六進制0x0000000102838010
- address to symbolicate:當前方法的內存地址。
所以,上圖為例:
1 2 |
$ cd CrashDemo/dSYMs $ atos -arch arm64 -o CrashDemo.app.dSYM/Contents/Resources/DWARF/CrashDemo -l 0x0000000102838010 0x0000000102838119 |
這時命令就會輸出已經符號化過的信息: -[ViewController viewDidLoad] (in CrashDemo) (ViewController.m:45)
其中45
就是問題代碼在ViewController.m
中的具體位置。
其實符號化
的過程有多種方式,你可以參考Apple文檔,對於其中UUID
,只是為了我們找到App對應版本的dSYM
文件,所以如果你能確定兩者的對應,不需要我們再去獲取。而且,使用上面方法,我們可以找到每一個版本對應的dSYM
文件(假如你沒有刪除的話)。
最后
愉快的改Bug吧😳