平常開放中,咱們不免遇到一些 crash。大部分狀況下,Xcode 能夠幫助咱們找到問題所在,但也有些狀況,Xcode 給咱們反饋的是一些看不懂的地址,大大增長了咱們分析問題的難度。ios
下面,就來介紹幾種能讓看不懂的地址,變得看的懂的方式。git
dSYM 是保存十六進制函數地址映射信息的中轉文件,咱們調試的 symbols 都會包含在這個文件中。每次編譯項目的時候都會生成一個新的 dSYM 文件,咱們應該保存每一個正式發佈版本的 dSYM 文件,以備咱們更好的調試問題。通常是在咱們 Archives 時保存對應的版本文件的,裏面也有對應的 .dSYM
和 .app
文件。路徑爲:程序員
~/Library/Developer/Xcode/Archives
複製代碼
.dSYM
文件默認在 debug 模式下是不生成的,咱們去 Build Settings -> Debug Information Format 下,將 DWARF
修改成 DWARF with dSYM File
,再從新編譯下就能生成 .dSYM
文件了,直接去項目工程的 Products
目錄下找就行。github
引用 《程序員的自我修養》中的解釋:bash
在連接中,咱們將函數和變量統稱爲符號(Symbol),函數名或變量名就是符號名(Symbol Name)。咱們能夠將符號看做是連接中的粘合劑,整個連接過程正是基於符號纔可以正確完成。app
因此,所謂的 symbols 就是函數名或變量名。函數
symbolicatecrash
是 Xcode 自帶的 crash 日誌分析工具,咱們須要先找到它:工具
find /Applications/Xcode.app -name symbolicatecrash -type f
複製代碼
執行完後會返回幾個路徑,個人是:oop
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/DVTFoundation.framework/symbolicatecrash
複製代碼
咱們到這個路徑下把 symbolicatecrash
拷貝出來,放到一個文件夾下。測試
咱們能夠隨便寫段強制 crash 的代碼:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSArray *arr = @[];
arr[100];
}
複製代碼
接着用真機打個包。打好包以後,不要用 Xcode build,直接用打好的包運行咱們能致使 crash 的代碼,這樣就生成好 .crash
日誌文件了。
以後,咱們去 Xcode -> Window -> Devices and Simulators 或者快捷鍵 Command + Shift + 2
找到對應時間點的 .crash 文件,右擊 Export Log。
.app
文件可使用真機編譯下,去 項目 Products
目錄下獲取,也能夠去 Archives 目錄下獲取。
將 .dSYM
、.crash
及 symbolicatecrash
放到同一個文件下,執行命令:
./symbolicatecrash .crash文件路徑 .dSYM文件路徑 > 名字.crash
複製代碼
將 .app
、.crash
及 symbolicatecrash
放到同一個文件下,執行命令:
./symbolicatecrash .crash文件路徑 .app/appName 路徑 > 名字.crash
複製代碼
可能會報錯誤:
Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 69.
複製代碼
執行下命令就行:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
複製代碼
而後再從新生成下新的 .crash
文件就行。
咱們能夠對比下沒有符號化和符號化的文件,下面是我本身測試的例子,iPhone5 iOS 10.2
, 可能會有所不一樣:
Last Exception Backtrace:
0 CoreFoundation 0x1df60df2 __exceptionPreprocess + 126
1 libobjc.A.dylib 0x1d1c3072 objc_exception_throw + 33
2 CoreFoundation 0x1dee62f2 -[__NSArray0 objectAtIndex:] + 105
3 DreamDemo 0x0008088e 0x7c000 + 18574
4 UIKit 0x2319eb44 forwardTouchMethod + 289
5 UIKit 0x2319ea10 -[UIResponder touchesBegan:withEvent:] + 29
6 UIKit 0x23041c58 -[UIWindow _sendTouchesForEvent:] + 1599
7 UIKit 0x2303ca62 -[UIWindow sendEvent:] + 2657
8 UIKit 0x2300d870 -[UIApplication sendEvent:] + 315
9 UIKit 0x237a8998 __dispatchPreprocessedEventFromEventQueue + 2615
10 UIKit 0x237a25de __handleEventQueue + 829
11 CoreFoundation 0x1df1c716 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 7
12 CoreFoundation 0x1df1c220 __CFRunLoopDoSources0 + 433
13 CoreFoundation 0x1df1a4f6 __CFRunLoopRun + 757
14 CoreFoundation 0x1de6952e CFRunLoopRunSpecific + 481
15 CoreFoundation 0x1de6933c CFRunLoopRunInMode + 99
16 GraphicsServices 0x1f640bf8 GSEventRunModal + 151
17 UIKit 0x230789a2 -[UIApplication _run] + 569
18 UIKit 0x230730cc UIApplicationMain + 145
19 DreamDemo 0x000834cc 0x7c000 + 29900
20 libdyld.dylib 0x1d633506 _dyld_process_info_notify_release + 23
複製代碼
問題也能看出來,可是由於第三行(DreamDemo)並無符號化,致使咱們並不肯定具體調用位置。
再來看看符號化以後的:
Last Exception Backtrace:
0 CoreFoundation 0x1df60df2 __exceptionPreprocess + 126
1 libobjc.A.dylib 0x1d1c3072 objc_exception_throw + 33
2 CoreFoundation 0x1dee62f2 -[__NSArray0 objectAtIndex:] + 105
3 DreamDemo 0x0008088e -[ViewController touchesBegan:withEvent:] + 18574 (ViewController.m:84)
4 UIKit 0x2319eb44 forwardTouchMethod + 289
5 UIKit 0x2319ea10 -[UIResponder touchesBegan:withEvent:] + 29
6 UIKit 0x23041c58 -[UIWindow _sendTouchesForEvent:] + 1599
7 UIKit 0x2303ca62 -[UIWindow sendEvent:] + 2657
8 UIKit 0x2300d870 -[UIApplication sendEvent:] + 315
9 UIKit 0x237a8998 __dispatchPreprocessedEventFromEventQueue + 2615
10 UIKit 0x237a25de __handleEventQueue + 829
11 CoreFoundation 0x1df1c716 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 7
12 CoreFoundation 0x1df1c220 __CFRunLoopDoSources0 + 433
13 CoreFoundation 0x1df1a4f6 __CFRunLoopRun + 757
14 CoreFoundation 0x1de6952e CFRunLoopRunSpecific + 481
15 CoreFoundation 0x1de6933c CFRunLoopRunInMode + 99
16 GraphicsServices 0x1f640bf8 GSEventRunModal + 151
17 UIKit 0x230789a2 -[UIApplication _run] + 569
18 UIKit 0x230730cc UIApplicationMain + 145
19 DreamDemo 0x000834cc main + 29900 (main.m:15)
20 libdyld.dylib 0x1d633506 _dyld_process_info_notify_release + 23
複製代碼
能夠發現,第三行被解析出來了,這樣咱們就能很清晰的知道具體的頁面了。
symbolicatecrash 能夠幫助咱們很好的分析 crash 日誌,可是有它的侷限性 --- 不夠靈活。咱們須要 symbolicatecrash
、.crash
及 .dSYM
三個文件才能解析。
相對於 symbolicatecrash, atos
命令更加靈活,特別是你須要對不一樣渠道的 crash 文件,寫一個自動化的分析腳本的時候,這個方法就極其有用。
可是這種方式也有個不方便的地方:一個線上的 App,用戶使用的版本存在差別,而每一個版本所對應的 .dSYM
都是不一樣的。必須確保 .crash
和 .dSYM
文件是匹配的,才能正確符號化,匹配的條件就是它們的 UUID 一致。 在這以前,先介紹下 UUID:
UUID 是由一組 32 位數的十六進制數字所構成。每個可執行程序都有一個 build UUID 惟一標識。.crash
日誌包含發生 crash 的這個應用的 build UUID 以及 crash 發生時,應用加載的全部庫文件的 build UUID。
執行命令:
grep --after-context=2 "Binary Images:" *crash
複製代碼
輸出:
T.crash:Binary Images:
T.crash-0x7c000 - 0x87fff DreamDemo armv7 <d009f8671129397a8aab9cb2b8e506ff> /var/containers/Bundle/Application/DEEBE571-D512-4E8F-B712-ED4D19CE64F9/DreamDemo.app/DreamDemo
T.crash-0xa9000 - 0xd4fff dyld armv7s <cd60ff3403063c0aa8e54dff11e42527> /usr/lib/dyld
複製代碼
看到上面的輸出 d009f8671129397a8aab9cb2b8e506ff
就是 DreamDemo
項目的 UUID。
執行命令:
dwarfdump --uuid DreamDemo.app.dSYM
複製代碼
輸出:
UUID: D009F867-1129-397A-8AAB-9CB2B8E506FF (armv7) DreamDemo.app.dSYM/Contents/Resources/DWARF/DreamDemo
複製代碼
執行命令:
dwarfdump --uuid DreamDemo.app/DreamDemo
複製代碼
輸出:
UUID: D009F867-1129-397A-8AAB-9CB2B8E506FF (armv7) DreamDemo.app/DreamDemo
複製代碼
能夠發現這兩個文件的 UUID 是相同的,也就是匹配的,只有知足這種條件,才能正確的解析!
咱們現回顧下未解析前的堆棧:
2 CoreFoundation 0x1dee62f2 -[__NSArray0 objectAtIndex:] + 105
3 DreamDemo 0x0008088e 0x7c000 + 18574
4 UIKit 0x2319eb44 forwardTouchMethod + 289
5 UIKit 0x2319ea10 -[UIResponder touchesBegan:withEvent:] + 29
複製代碼
執行命令:
xcrun atos -o DreamDemo.app.dSYM/Contents/Resources/DWARF/DreamDemo -arch armv7 -l 0x7c000
複製代碼
接着輸入 0x0008088e
地址,終端輸出以下:
能夠發現,正確的解析出來了!
除了搭配 .dSYM
文件,咱們也可使用 .app
文件來解析:
執行命令:
xcrun atos -o DreamDemo.app/DreamDemo -arch armv7 -l 0x7c000
複製代碼
一樣輸入 0x0008088e
地址,效果是同樣的。
直接操做 atos
命令畢竟是有點不方便,GitHub 上有個工具,能夠輔助咱們解析 dSYMTools ,這是個 Mac 客戶端,界面長這樣:
使用起來也很方便,咱們只須要把對應的 dSYM
文件拖進去,它會自動識別 UUID。咱們對應的輸入參數地址就行:
細心的人能夠發現,咱們上面的解析都是針對 DreamDemo
,這個本身的項目的。其實不少系統方法的堆棧之因此能解析出來,是由於已經有了系統庫的符號化文件,存放目錄以下:
/用戶/用戶名稱xxx/資源庫/Developer/Xcode/iOS DeviceSupport
複製代碼
這些庫的版本都是和 .crash
文件中是對應的:
OS Version: iPhone OS 10.2 (14C5077b)
複製代碼
一旦我把這個 10.2 (14C5077b)
系統的符號化庫刪掉,.crash
文件就會變成這樣:
Last Exception Backtrace:
0 CoreFoundation 0x1df60df2 0x1de5f000 + 1056242
1 libobjc.A.dylib 0x1d1c3072 0x1d1bc000 + 28786
2 CoreFoundation 0x1dee62f2 0x1de5f000 + 553714
3 DreamDemo 0x000bc66e -[ViewController touchesBegan:withEvent:] + 18030 (ViewController.m:78)
4 UIKit 0x2319eb44 0x22ffe000 + 1706820
5 UIKit 0x2319ea10 0x22ffe000 + 1706512
6 UIKit 0x23041c58 0x22ffe000 + 277592
7 UIKit 0x2303ca62 0x22ffe000 + 256610
8 UIKit 0x2300d870 0x22ffe000 + 63600
9 UIKit 0x237a8998 0x22ffe000 + 8038808
10 UIKit 0x237a25de 0x22ffe000 + 8013278
11 CoreFoundation 0x1df1c716 0x1de5f000 + 775958
12 CoreFoundation 0x1df1c220 0x1de5f000 + 774688
13 CoreFoundation 0x1df1a4f6 0x1de5f000 + 767222
14 CoreFoundation 0x1de6952e 0x1de5f000 + 42286
15 CoreFoundation 0x1de6933c 0x1de5f000 + 41788
16 GraphicsServices 0x1f640bf8 0x1f637000 + 39928
17 UIKit 0x230789a2 0x22ffe000 + 502178
18 UIKit 0x230730cc 0x22ffe000 + 479436
19 DreamDemo 0x000bf332 main + 29490 (main.m:15)
20 libdyld.dylib 0x1d633506 0x1d630000 + 13574
複製代碼
能夠明顯的發現,系統庫的堆棧變成了一堆地址。
新版本,每當咱們手機連上 Xcode 時,都會把當前版本的系統符號庫自動導入到 /用戶/用戶名稱xxx/資源庫/Developer/Xcode/iOS DeviceSupport
目錄下。可是 iOS 版本那麼多,以前舊的系統符號庫該怎麼獲取呢?有人已經整理好了 iOS-System-Symbols,咱們只須要根據 .crash
文件的版本信息,下載對應的系統符號化文件到目錄下便可。
.crash
日誌堆棧解析,可是因爲依賴 symbolicatecrash
、.crash
以及 .dSYM
三個文件,或者 .app
、.crash
及 symbolicatecrash
三個文件,致使不太靈活。atos
命令只須要 .crash
和 .dSYM
,或者 .crash
和 .app
,知道對應的堆棧地址,就能解析,方便自動化腳本分析,可是 crash 堆棧可能須要本身實現收集。