1. 程式人生 > >教你如何對ios崩潰(crash)日誌做符號化

教你如何對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機器上,問題就可以解決了。