
點選關注“有贊coder”
擷取更多技術幹貨哦~
作者:楊楊 & 姜豪
部門:電商移動
背景
有贊在基礎保障平台的實踐中完成了 Crash平台 的建設,但是iOS的崩潰日志未經符号化,排查問題比較困難。為了降低iOS App的crash率,快速排查線上crash,疑難crash的跟蹤處理,符号化崩潰日志顯得尤為重要!
一、crash日志的收集與分析
1.1 如何收集crash日志
1.手機上直接看,在隐私-分析與改進 -分析資料,可以找到所有崩潰日志,未符号化。 2.連接配接電腦,通過“音樂”同步到本地 ~/Library/Logs/CrashReporter/MobileDevice/xxx的 iPhone. 缺點:日志沒有符号化,需要自己手動符号化 3.連接配接電腦,打開Xcode-window-Diveces and Simulators。
Xcode會嘗試在本地查找符号表檔案,自動符号化。 以上3種方法都局限于拿得到裝置的情況。 4.檢視别人手機上的crash日志 Xcode-Window-Organizer。
這種方式找符号表會有2種途徑
- 上傳AppStore的時候會讓你勾選上傳符号表「Include App symbols for your Application…」,如果上傳了,蘋果自動幫你在雲端做解析。
- 如果沒有上傳,Xcode嘗試在本地找符号表檔案進行符号化。
缺點:這種方式也隻能收集在手機設定中打開了上傳crash開關,以及TestFlight使用者的crash日志。企業分發或 AdHoc 安裝,需要自行擷取崩潰日志。資訊不全,線程資訊不夠。 5.自己收集crash日志,比如接入KSCrash、plcrashreporter等,但是要自己做符号化。
1.2 crash日志的結構
日志可以分成4個部分,基本資訊,崩潰的原因,所有線程調用,Binary Images (二進制檔案清單)。
1.2.1 基本資訊
1.2.2 崩潰原因
線程
Binary Images
二、如何進行crash日志符号化
crash日志符号化通常是通過
atos
和
symbolicatecrash
這兩個工具來完成。
2.1 atos
atos
是蘋果提供的符号化工具,在Mac OS系統下預設安裝,他的缺點是隻能一個位址一個位址逐個翻譯。我們看下這個工具的使用說明:
使用方法:
atos -arch -o /Contents/Resources/DWARF/ -l
需要傳入這幾個資訊:arch 架構、dSYM路徑、binary image 載入記憶體的初始位址、崩潰的位址。
參數内容可以從crash日志中取得,如下圖所示:
example
$ atos -arch arm64 -o TheElements.App.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc -[AtomicElementViewController myTransitionDidStop:finished:context:]
2.2 symbolicatecrash
symbolicatecrash
是
Xcode
自帶的一個程式,他是對
atos
的封裝,可以翻譯整個crash檔案,有贊就是選擇這個工具來進行
crash
符号化的。
具體的路徑可以通過以下指令搜尋出來:
find /Applications/Xcode.App -name symbolicatecrash -type f
使用方法:
export DEVELOPER_DIR="/Applications/Xcode.App/Contents/Developer" /symbolicatecrash 例子: symbolicatecrash log.crash > result.log // dSYM可以跟多個 symbolicatecrash log.crash -d TheElement.App.dSYM >result.log
下文會對此工具做一個詳細的原理分析。
三、symbolicatecrash符号化原理分析
通過網上找的教程來看,一般是把對應版本的crash日志,dSYM檔案,App檔案都放進一個目錄,然後執行一下指令來進行符号化:
symbolicatecrash log.crash -d TheElement.App.dSYM >result.log
但是我有幾個疑問:
- 如果App打包出來多個dSYM怎麼辦?
- 發現把目錄中的App檔案删了,dSYM删了(源檔案還在),執行指令的時候也沒傳他們,竟然也可以符号化,這怎麼做到的?
- 怎麼樣知道crash日志,dSYM,App是正确的,可以正确做符号化,如果發現某個crash日志沒有被正确符号化,怎麼查這個問題?
- 把dSYM丢了,相同代碼再去編譯一次把dSYM拿出來可以用嗎?
- 我們執行完後發現系統庫也都符号化了,系統的dSYM在哪裡,難道已經包含在App的dSYM中嗎?
- 崩潰日志最下面的Binary Images是幹嘛的?
針對以上這些問題,我們來做下源碼分析一探究竟。
3.1 symbolicatecrash 源碼分析
官方沒有開源,但是網上有類似的實作,是用perl實作的一個腳本。
首先,一個基本原則是需要確定你的電腦上有每個
image
對應的
uuid
的符号表檔案,這樣crash檔案才能被正确解析和符号化出來。
然後我們看下符号化一個crash檔案的流程:
3.1.1 解析所有的Binary Image
這是crash日志中的Binary Image格式0x1cd997000 - 0x1cea7bfff UIKitCore arm64 <40a93e939f8635c1905c7b947c7c2305> /System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore轉換為如下格式'UIKitCore' => { 'extent' => '0x1cea7bfff', 'plus' => '', 'bundlename' => 'UIKitCore', 'uuid' => '40a93e939f8635c1905c7b947c7c2305', 'base' => '0x1cd997000', 'path' => '/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore', 'arch' => 'arm64', 'nextID' => ''}
把每一個Binary Image都存儲為以上形式的對象。
Binary Image的作用是建立UIKitCore與uuid的關系,當需要符号化一個UIKitCore的位址時,會找到對應的uuid,并從檔案系統中查找到這個符号表。這也解釋了上面第6個問題。
3.1.2 解析所有線程
8 TheElement 0x00000001044dcfc0 0x104058000 + 4739008轉換為如下格式'0x00000001044dcfc0 0x104058000 + 4739008' => { 'raw_address' => '0x00000001044dcfc0', 'bundle' => 'TheElement', 'address' => '0x00000001044dcfc0'}
把所有堆棧存儲為以上形式的對象。
3.1.3 翻譯Last Exception Backtrace
這是crash日志中的Last Exception BacktraceLast Exception Backtrace:(0x1a1a9127c 0x1a0c6b9f8 0x1a19adab8 0x1a1a96ac4 0x1a1a9875c 0x10566d498 0x10423ab84 0x1ce255040 0x1cdcfe1c8 0x1cdcfe4e8 0x1cdcfd554 0x1ce28c304 0x1ce28d52c 0x1ce26d59c 0x10437fd20 0x1ce333714 0x1ce335e40 0x1ce32f070 0x1a1a23018 0x1a1a22f98 0x1a1a22880 0x1a1a1d7bc 0x1a1a1d0b0 0x1a3c1d79c 0x1ce253978 0x104283158 0x1a14e28e0)翻譯為:0 libsystem_kernel.dylib 0x00000001a162e0dc 0x1a160b000 + 1435801 libsystem_pthread.dylib 0x00000001a16a7094 0x1a16a5000 + 83402 libsystem_c.dylib 0x00000001a1587f4c 0x1a152d000 + 3725563 libsystem_c.dylib 0x00000001a1587eb4 0x1a152d000 + 3724044 libc++abi.dylib 0x00000001a0c54788 0x1a0c53000 + 60245 libc++abi.dylib 0x00000001a0c54934 0x1a0c53000 + 64526 libobjc.A.dylib 0x00000001a0c6be00 0x1a0c66000 + 240647 TheElement 0x0000000104babb18 0x104058000 + 118771448 TheElement 0x00000001044dcfc0 0x104058000 + 47390089 libc++abi.dylib 0x00000001a0c60838 0x1a0c53000 + 5535210 libc++abi.dylib 0x00000001a0c60434 0x1a0c53000 + 5432411 libobjc.A.dylib 0x00000001a0c6bbc8 0x1a0c66000 + 2349612 CoreFoundation 0x00000001a1a1d11c 0x1a1979000 + 67202813 GraphicsServices 0x00000001a3c1d79c 0x1a3c13000 + 4290814 UIKitCore 0x00000001ce253978 0x1cd997000 + 916108015 TheElement 0x0000000104283158 0x104058000 + 227362416 libdyld.dylib 0x00000001a14e28e0 0x1a14e1000 + 6368
這裡為什麼可以翻譯,因為第一步已經把所有Binary Image存儲起來,上面的每一個位址,都可以找到對應的Binary Image,進而獲得Binary Image的名稱,基位址,以及偏移量。
3.1.4 删除不需要的image
因為crash日志把App用到的所有Binary Image都列舉出來了,而崩潰堆棧中隻用到了一小部分,是以這裡把沒有用到的Binary Image删除。後續要周遊所有images,去找到每個二進制對應的dSYM,這樣做提高了效率。
3.1.5 查找Binary Image的符号表
符号表的類型
- App編譯出來的dSYM ( 一般輸入指令時指定在哪裡,如果沒有會自動去查找)
- 系統庫的符号表 (自動查找),這也解釋了第五個問題,系統符号表和APP符号表是分開的。在 ~/Library/Developer/Xcode/iOS DeviceSupport/os/Symbols 這個路徑再拼上image中的path,就是完整路徑 比如 ~/Library/Developer/Xcode/iOS DeviceSupport/os/Symbols/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore
- 從search path中找 (包括指令行輸入的幾個目錄 和 系統符号表所在目錄)
- mdfind搜尋uuid相同的符号表,這就解釋了上面第1個和第2個問題,會使用uuid去查找,是以指令行中不傳也沒關系。
- 如果還沒找到 傳回空 并删除這個image,與這個image相關的都不能被符号化
判斷比對的條件
- lipo -info 判斷架構是否一緻
- otool 指令打出來macho資訊,找到uuid 并 判斷是否一緻,這解答了上面第3個和第4個問題,隻有uuid相同,才可以被符号化出來。相同代碼重新打一個包出來也不能符号化,因為uuid不同。
3.1.6 執行atos進行符号化
- 周遊所有線程
- 取到每一條的bundle 還有位址 在images中找到符号表路徑
- 執行指令 并記錄符号化後的内容
'0x00000001044dcfc0 0x104058000 + 4739008' => { 'symbolled' => 'CPPExceptionTerminate() (SentryCrashMonitor_CPPException.cpp:179)', 'raw_address' => '0x00000001044dcfc0', 'bundle' => 'TheElement', 'address' => '0x00000001044dcfc0' }
3.1.7 字元串替換 生成最終的報告
逐行開始替換
比如将'0x00000001044dcfc0 0x104058000 + 4739008'替換為'CPPExceptionTerminate() (SentryCrashMonitor_CPPException.cpp:179)'
四、有贊符号化方案
通過上面的原理分析,我們基本掌握了
crash
符号化的步驟,下面介紹下我們有贊是如何做符号化的。
4.1 dSYM符号表儲存
首先,進行符号化必不可少的一個檔案就是
dSYM
符号表,我們需要儲存每次正式釋出的App版本對應的符号表檔案。如下圖所示:
- 打包機(gitlab runner):有贊目前有自己的持續建構平台
,業務方在MBD
上發起打包建構任務後系統會根據算法配置設定到不同的打包機上。更多關于有贊移動MBD
我們在之前做過一次技術沙龍,詳細内容見這裡。CI/CD
- 項目打包完成後會執行一個儲存符号表的腳本,會儲存符号表到本地,并且上傳到雲端做備份。備份完成後調用MBD接口,上報符号表uuid,bundleId,版本号,build号,打包機唯一辨別。
- 由于有多台打包機導緻每次打包産出的符号表分布在不同的打包機上,我們需要建立dSYM檔案與打包機的關系。第一步中的儲存符号表腳本會上報資訊到MBD,MBD把dSYM符号表uuid和打包機唯一辨別做一個映射關系。
- 當發生一個crash時,crash日志中包含符号表uuid,通過uuid查表,就能定位到執行建構的打包機。
4.2 crash上報
dSYM符号表已經儲存下來了,接下來就是crash的上報和解析,crash上報大緻流程見下圖:
- crash資訊通過SDK上報到埋點平台,我們通過Flink監聽到crash資訊的上報,并把它寫入資料庫。
- Flink是實時計算平台提供的用來實時消費上報的資料的程式,支援大并發量的資料。
更多關于crash平台的建設我們近期也發表過一篇文章,詳情見 這裡。
4.3 crash檔案符号化
步驟二中已經上報了crash資訊并展示在了我們的内部平台中,接下來我們需要對此crash檔案結合對應的dSYM進行符号化解析,具體流程如下:
- 在 Crash前端頁面,點選符号化按鈕會發起 MBD 的一次符号化建構,并将 crash 的資訊傳遞給 MBD。
- MBD把crash的uuid拿出來,根據uuid去查 dSYM檔案所在的 打包機,并把任務給到這個打包機。
- 打包機運作腳本,這個腳本的作用是使用symbolicatecrash程式符号化crash日志,并把符号化後的結果通知到MBD。
- MBD 把符号化結果寫入資料庫,并通知Crash後端。
- Crash前端頁面收到通知後重新整理頁面,展示符号化後的結果。
至此,我們完成了crash檔案的符号化解析工作,但是使用過程中暴露出了一些問題:
- 目前每次打包都會産生dSYM檔案并直接儲存在打包機上,MBD每天的打包任務有很多,導緻占用空間浪費資源。我們計劃隻維護符号表的cdn連結,用到時再去下載下傳符号表。
- 這種方案下線一台打包機後,會造成一部分crash日志無法符号化,目前我們正在優化,計劃統一把符号表放到一台打包機上,這樣就能解決這個問題。
- 系統符号表的維護也是一個問題,我們需要在每台打包機上都要加上系統符号表,而且每次蘋果釋出新版都需要拿新的系統符号表過來,維護起來挺麻煩的。目前的解決方案是人工放到打包機上。
總結
至此,我們了解了如何收集crash日志,明白了crash日志中每個部分的意思,符号化的工具,以及如何對crash日志進行符号化。已經可以解答出來上面提出的問題,對符号化的原理有了非常清晰的認識。
我們的符号化方案對于有贊多台打包機環境而言,非常合适,下線一台或者新增一台打包機,可以無縫支援。另外,整套方案非常輕量,能夠快速內建符号化功能,符号化鍊路清晰。
Crash平台擁有符号化crash日志的能力後,極大的提高了大家排查、解決線上問題的效率,提升了App的穩定性。
擴充閱讀:
- 有贊移動Crash平台建設
-
有贊移動 App 一鍵切換網關實踐
-
有贊零售發票列印圖檔二值化方案
- 有贊 Android 崩潰保護的探索及實踐
- 有贊移動 iOS 元件化(子產品化)架構設計實踐
-
有贊Flutter插件開發與釋出
-
有贊移動如何做到并行灰階的複雜場景?
Vol.334