教你如何對ios崩潰(crash)日誌做符號化
一、場景
客戶端的開發流程都相似,如android,搞ios開發就要不停地發版本,隨之而來的就是各種版本的崩潰日誌(稱為crash log)。如果不能好好地管理,那麼開發人員很快就會在crash log和版本的海洋裡迷失方向。解決崩潰問題是移動應用開發者最日常的工作之一。如果是開發過程中遇到的崩潰,可以根據重現步驟除錯,但線上版本就無能為力了。
國內,一般的公司是沒有對ios crash符號化分析系統的,能夠對ios 的crash日誌做符號化做得好的公司比較少,比較好的是 “騰訊bugly”,連結:http://bugly.qq.com 。ios的日誌分析,做符號化,並定位到發生崩潰的原因,對app的開發百利無一害,功德無量。
關注問題本身,客戶端開發和後臺服務開發管理日誌有很大的不同:
如果是伺服器報了個異常,對於java的服務,RD登上機器,看看錯誤的日誌,看到“crash by...” 找出原因,改程式碼,重新編譯打包部署一下就算修復了,假如是分散式的叢集,服務是可以不間斷的提供服務的。
如果是客戶端開發,在實際的專案開發中,崩潰問題,依賴xcode編輯器,依賴於系統記錄的崩潰日誌或錯誤堆疊,在本地開發除錯階段,是沒有問題的。如果在釋出的線上版本出現崩潰問題,開發者是無法即時準確的取得錯誤堆疊的。需要適當的時期將crash上報到服務端,由服務端處理收集分析。客戶端需要重新發版才能修復舊版的crash。
下面對ios的crash 管理做一下介紹,因為android開發是java語言,不需要做額外的處理。
二、例子
下面線上iphone的原始crash日誌:
### 1.程序資訊 ### Incident Identifier: 87164E05-84A8-40F2-886D-14F90C9D3F47 CrashReporter Key: TODO Hardware Model: iPhone7,2 Process: imeituan [945] Path: /var/mobile/Containers/Bundle/Application/96DC918D-60C8-4C49-A51D-C0D71D0FCB4D/imeituan.app/imeituan Identifier: com.meituan.imeituan Version: 895 Code Type: ARM-64 Parent Process: ??? [1] ### 2.基本資訊 ### Date/Time: 2016-02-01 16:14:24 +0000 OS Version: iPhone OS 9.2.1 (13D15) Report Version: 104 ### 3.異常資訊 ### Exception Type: SIGABRT Exception Codes: #0 at 0x181f3c140 ### 4.執行緒回溯 ### Crashed Thread: 0 Application Specific Information: *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 25 beyond bounds [0 .. 24]' ### 5.Crash呼叫堆疊,需要需要將堆疊的二進位制轉成可讀的### Last Exception Backtrace: 0 CoreFoundation 0x0000000182399900 0x182274000 + 1202432 1 libobjc.A.dylib 0x0000000181a07f80 0x181a00000 + 32640 2 CoreFoundation 0x000000018227f828 0x182274000 + 47144 3 imeituan 0x0000000101ab593c 0x1000b8000 + 27253052 4 imeituan 0x0000000100521a68 0x1000b8000 + 4627048 5 UIKit 0x00000001873dd31c 0x187078000 + 3560220 6 UIKit 0x00000001873dd484 0x187078000 + 3560580 7 UIKit 0x00000001873cc7e8 0x187078000 + 3491816 8 UIKit 0x00000001873e1fb0 0x187078000 + 3579824 9 UIKit 0x000000018717708c 0x187078000 + 1044620 10 UIKit 0x0000000187087778 0x187078000 + 63352 11 QuartzCore 0x0000000184a96b2c 0x184a88000 + 60204 12 QuartzCore 0x0000000184a91738 0x184a88000 + 38712 13 QuartzCore 0x0000000184a915f8 0x184a88000 + 38392 14 QuartzCore 0x0000000184a90c94 0x184a88000 + 35988 15 QuartzCore 0x0000000184a909dc 0x184a88000 + 35292 16 QuartzCore 0x0000000184a8a0cc 0x184a88000 + 8396 17 CoreFoundation 0x0000000182350588 0x182274000 + 902536 18 CoreFoundation 0x000000018234e32c 0x182274000 + 893740 19 CoreFoundation 0x000000018234e75c 0x182274000 + 894812 20 CoreFoundation 0x000000018227d680 0x182274000 + 38528 21 GraphicsServices 0x000000018378c088 0x183780000 + 49288 22 UIKit 0x00000001870f4d90 0x187078000 + 511376 23 imeituan 0x0000000100112628 0x1000b8000 + 370216 24 ??? 0x0000000181e1e8b8 0x0 + 0 … ### 6.動態庫資訊 ### Binary Images: 0x1000b8000 - 0x10275ffff +imeituan arm64 <e38d01be571931b7a4c3d9dcbf28e821> /var/mobile/Containers/Bundle/Application/96DC918D-60C8-4C49-A51D-C0D71D0FCB4D/imeituan.app/imeituan 0x10d76c000 - 0x10d7dbfff AGXMetalG4P arm64 <f76b11f8d06338f99ae4704aba08111c> /System/Library/Extensions/AGXMetalG4P.bundle/AGXMetalG4P 0x182058000 - 0x18225dfff libicucore.A.dylib arm64 <5c1540546de5350ab314c1d4c8a46d1b> /usr/lib/libicucore.A.dylib … 0x182274000 - 0x1825ecfff CoreFoundation arm64 <121118a9a44d3518b99f3ebfd8806f69> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation 0x1825f0000 - …
主要有6部分組成:
- 1.程序資訊
- 2.基本資訊
- 3.異常資訊
- 4.執行緒回溯
- 5.Crash呼叫堆疊(全是地址資訊,需要使用符號表轉成可讀的)
- 6.動態庫資訊(第5部分依賴的庫)
ios的符號化也主要是根據第5和第6部分進行符號化,只有把第五部分後面的二進位制的地址資訊對映成程式碼資訊,才能發生crash的原因。
下面是上面的crash日誌符號化後的結果:
### 1.程序資訊 ###
Incident Identifier: 87164E05-84A8-40F2-886D-14F90C9D3F47
CrashReporter Key: TODO
Hardware Model: iPhone7,2
Process: imeituan [945]
Path: /var/mobile/Containers/Bundle/Application/96DC918D-60C8-4C49-A51D-C0D71D0FCB4D/imeituan.app/imeituan
Identifier: com.meituan.imeituan
Version: 895
Code Type: ARM-64
Parent Process: ??? [1]
### 2.基本資訊 ###
Date/Time: 2016-02-01 16:14:24 +0000
OS Version: iPhone OS 9.2.1 (13D15)
Report Version: 104
### 3.異常資訊 ###
Exception Type: SIGABRT
Exception Codes: #0 at 0x181f3c140
### 4.執行緒回溯 ###Crashed Thread: 0
Application Specific Information:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 25 beyond bounds [0 .. 24]'
### 5.Crash呼叫堆疊,需要需要將堆疊的二進位制轉成可讀的###
Last Exception Backtrace:
0 CoreFoundation __exceptionPreprocess + 124
1 libobjc.A.dylib objc_exception_throw + 56
2 CoreFoundation -[__NSArrayM removeObjectAtIndex:] + 0
3 imeituan -[SAKFetchedResultsController objectAtIndexPath:] (SAKFetchedResultsController.m:60)
4 imeituan -[DEFHomePageViewController tableView:cellForRowAtIndexPath:] (DEFHomePageViewController.m:663)
5 UIKit -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 692
6 UIKit -[UITableView _createPreparedCellForGlobalRow:willDisplay:] + 80
7 UIKit -[UITableView _updateVisibleCellsNow:isRecursive:] + 2360
8 UIKit -[UITableView _performWithCachedTraitCollection:] + 104
9 UIKit -[UITableView layoutSubviews] + 176
10 UIKit -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 656
11 QuartzCore -[CALayer layoutSublayers] + 148
12 QuartzCore CA::Layer::layout_if_needed(CA::Transaction*) + 292
13 QuartzCore CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32
14 QuartzCore CA::Context::commit_transaction(CA::Transaction*) + 252
15 QuartzCore CA::Transaction::commit() + 512
16 QuartzCore CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 80
17 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
18 CoreFoundation __CFRunLoopDoObservers + 372
19 CoreFoundation __CFRunLoopRun + 928
20 CoreFoundation CFRunLoopRunSpecific + 384
21 GraphicsServices GSEventRunModal + 180
22 UIKit UIApplicationMain + 204
23 imeituan main (main.m:34)
24 ??? 0x0000000181e1e8b8 0x0 + 0
### 6.動態庫資訊 ###
Binary Images:
0x1000b8000 - 0x10275ffff +imeituan arm64 <e38d01be571931b7a4c3d9dcbf28e821> /var/mobile/Containers/Bundle/Application/96DC918D-60C8-4C49-A51D-C0D71D0FCB4D/imeituan.app/imeituan
0x10d76c000 - 0x10d7dbfff AGXMetalG4P arm64 <f76b11f8d06338f99ae4704aba08111c> /System/Library/Extensions/AGXMetalG4P.bundle/AGXMetalG4P
0x182058000 - 0x18225dfff libicucore.A.dylib arm64 <5c1540546de5350ab314c1d4c8a46d1b> /usr/lib/libicucore.A.dylib
…
0x182274000 - 0x1825ecfff CoreFoundation arm64 <121118a9a44d3518b99f3ebfd8806f69> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
0x1825f0000 -
…
那怎麼將第5部分進行符號化呢?那就需要符號表了,什麼是符號表呢?符號表有什麼用?符號表怎麼生成?符號表怎麼用?
三、ios crash 日誌符號化過程
(1)什麼是符號表
符號表就是指在Xcode專案編譯後,在編譯生成的二進位制檔案.app的同級目錄下生成的同名的.dSYM檔案。.dSYM檔案其實是一個目錄,在子目錄中包含了一個16進位制的儲存函式地址對映資訊的中轉檔案,所有Debug的symbols都在這個檔案中(包括檔名、函式名、行號等),所以也稱之為除錯符號資訊檔案。一般地,Xcode專案每次編譯後,都會生成一個新的.dSYM檔案。因此,App的每一個釋出版本,都需要備份一個對應的.dSYM檔案,以便後續除錯定位問題。
專案每一次編譯後,.app和.dSYM成對出現,並且二者有相同的UUID值,以標識是同一次編譯的結果。
UUID值可以使用dwarfdump —uuid來檢查:
dwarfdump --uuid **.app.dSYM (**為你app)
例如:
(2)符號表有什麼用
在Xcode開發除錯App時,一旦遇到崩潰問題,開發者可以直接使用Xcode的偵錯程式定位分析。
但如果App釋出上線,開發者不可能進行除錯,只能通過分析系統記錄的崩潰日誌來定位問題,在這份崩潰日誌檔案中,會指出App出錯的函式記憶體地址,而這些函式地址是可以在.dSYM檔案中找到具體的檔名、函式名和行號資訊的,這正是符號表的重要作用所在,也是為什麼要進行符號表進行管理,並紀錄這是哪個版本的符號表。
Xcode的Organizer檢視崩潰日誌時,也自動根據本地儲存的.dSYM檔案進行了符號化的操作。並且,崩潰日誌也有UUID資訊,這個UUID和對應的.dSYM檔案是一致的,即只有當三者的UUID一致時,才可以正確的把函式地址符號化。
(3)符號表怎麼生成
Xcode專案預設的配置是會在編譯後生成.dSYM,開發者無需額外修改配置。因為我也沒有進行過ios開發經驗,每個版本的符號表都是客戶端的負責人給我的。這裡省略。
(4)符號表怎麼用
符號表的作用是把崩潰中的函式地址解析為函式名等資訊。如果開發者能夠獲取到崩潰的函式地址資訊,就可以利用符號表分析出具體的出錯位置。
Xcode提供了幾個工具來幫助開發者執行函式地址符號化的操作。
例如,我們用上面的例子,從上面原始日誌的第5部分挑出crash的地址,崩潰問題的函式地址堆疊如下:
Last
Exception Backtrace:
0 CoreFoundation 0x0000000182399900 0x182274000 + 1202432
1 libobjc.A.dylib 0x0000000181a07f80 0x181a00000 + 32640
2 CoreFoundation 0x000000018227f828 0x182274000 + 47144
|
第一步:從第6部分找出所依賴的動態庫的地址
“CoreFoundation”的動態庫是“/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation”:
0x182274000 - 0x1825ecfff CoreFoundation
arm64 <121118a9a44d3518b99f3ebfd8806f69> /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
|
第二步:在符號表的該目錄下驗證uuid是不是對應上
執行命令dwarfdump --uuid 來驗證:
執行命令:
dwarfdump
--uuid /Users/jenkins/data/apps/OSSDKSymbols/ 9.2 . 1 \
\(13D15\)/Symbols/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
輸出:
UUID:
121118A9-A44D- 3518 -B99F-3EBFD8806F69
(arm64) /Users/jenkins/data/apps/OSSDKSymbols/ 9.2 . 1 (13D15)/Symbols/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
UUID:
910B0F17-490F-3D92- 9833 -A9D4AADABBDB
(armv7s) /Users/jenkins/data/apps/OSSDKSymbols/ 9.2 . 1 (13D15)/Symbols/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
|
發現“121118A9-A44D-3518-B99F-3EBFD8806F69”和“121118a9a44d3518b99f3ebfd8806f69”是吻合的(忽略大小寫和‘-’),說明這個crash是在該版本中的。可以進行下面的符號化操作。
第三步:用atos工具進行符號化
atos實際是一個可以把地址轉換為函式名(包括行號)的工具,atos 語法:atos -o dysm檔案路徑 -l 模組load地址 呼叫方法的地址,上面的0x182274000就是模組load地,0x0000000182399900就是呼叫方法的地址。
例如執行命令:
執行命令:
atos
-o /Users/jenkins/data/apps/OSSDKSymbols/ 9.2 . 1 \
\(13D15\)/Symbols/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation -l 0x182274000 0x0000000182399900
輸出:
__exceptionPreprocess
(in CoreFoundation) + 124
|
發現輸出的結果是:__exceptionPreprocess (in CoreFoundation) + 124,說明符號化成功
至此符號就成功了,這個例子只是ios的os的crash日誌符號化,還有就是app層面crash,上面全部的的符號化的結果都是根據這幾個步驟來完成的。
四、服務架構
上面的符號化可以知道,我們需要"dwarfdump" 和 “atos” 命令,這是mac os 上帶的符號化的工具,同時還依賴xcode的版本,這是ubantu和centos系統所不支援的,所以crash log的符號化必須是在mac os上進行的,所以crash符號化服務必須在mac os進行,物理機可以是mac mini、imac或macbook。
至於上面crash業務服務,可以使用一般的ubantu或centos 系統都是可以的。
那麼問題來了,一般來說我們釋出的服務都是在ubantu或是centos系統上的,怎麼把服務部署在mac os機器上呢?其實都差不多,java因為有JVM,服務是跨平臺的,只是需要Runtime呼叫本地的“dwarfdump" 和 “atos”命令。無非就是編譯、打包、部署,當然了還有初始化機器,建立釋出賬號,建立路徑,管理服務程序等繁瑣的事情,這樣就可以把java服務釋出到遠端的mac os機器上,問題就可以解決了。