一、符號表
1、概念:debugger Symbols 的簡稱。符號表就是指在Xcode項目編譯后,在編譯生成的.app的同級目錄下生成的同名的.dSYM文件。
符號表是內存地址與函數名,文件名,行號的映射表。 符號表元素如下所示:
<起始地址> <結束地址> <函數> [<文件名:行號>]
.dSYM文件其實是一個目錄,在子目錄中包含了一個16進制的保存函數地址映射信息的中轉文件,所有Debug的symbols都在這個文件中(包括文件名、函數名、行號等),所以也稱之為調試符號信息文件
。
2、作用:符號表就是用來符號化 crash log(崩潰日志)。crash log中有一些方法16進制的內存地址等,通過符號表就能找到對應的能夠直觀看到的方法名之類。
3、獲取途徑:在Archive的時候會生成.xcarchive文件,然后顯示包內容就能夠在里面找到.dsYM文件和.app文件。
- 一般Xcode項目每次編譯后, 都會產生一個新的.dSYM文件和.app文件, 這兩者有一個共同的UUID.
- 注:項目編譯完dSYM文件和app文件在同一個目錄,Xcode Debug 編譯默認不會生成.dSYM文件, Release 編譯才會生成
- 注:為了方便找回Crash對應的dSYM文件和還原堆棧,建議每次構建或者發布APP版本的時候,備份好dSYM文件。
二、符號化文件
1、 Xcode分析
1>、要使用Xcode符號化 crash log,你需要下面所列的3個文件:
①crash報告(.crash文件)
②符號文件 (.dsymb文件)
③應用程序文件 (.app文件)
2>、把這3個文件放到同一個目錄下,打開Xcode的Window菜單下的organizer,然后點擊Devices tab,然后選中左邊的Device Logs。
然后把.crash文件拖到Device Logs或者選擇下面的import導入.crash文件。
這樣你就可以看到crash的詳細log了。
2、使用命令行工具symbolicatecrash
1>、將“.app“, “.dSYM”和 ".crash"文件放到同一個目錄./Crash 下。
2>、在Xcode中找到 symbolicatecrash 工具
使用命令
find /Applications/Xcode.app -name symbolicatecrash -type f
可以輕松找到。找到后你會發現有多個其中
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
對應的是真機, 找到后將 symbolicatecrash 拷貝到 ./Crash
目錄下
3>、切換到 ./Crash目錄下。 打開終端(Terminal)然后輸入如下的命令:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
然后輸入命令:
./symbolicatecrash appName.crash appName.app > appName.log
現在,符號化的crash log就保存在appName.log中了。
如果報No such file or directory : at ./symbolicatecrash line 909.錯誤,嘗試執行
./symbolicatecrash ./*.crash ./*.app.dSYM>Symbol_Crash.crash
3、atos
- atos 是一個可以把地址轉換為函數名(包括行號)的工具, 它和dwarfdump 為mac os自帶工具.
我們使用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:運行時堆棧地址,當前方法的內存地址。
具體示例:
atos -arch arm64 -o CrashDemo.app.dSYM/Contents/Resources/DWARF/CrashDemo -l 0x0000000102838010 0x0000000102838119
這樣crash文件就被符號化完成了,打開符號化如下圖:
三、Crash分析
以上圖為例,大部分字段都是不言而喻的,下面列舉一些有用處的。(在官方文檔都有解釋,這里做歸納與翻譯)
Incident Identifier: 報告的唯一標識符,兩份報告決不會共享同一個事件標識符。
CrashReporter Key:每個設備的匿名標識符,來自同一設備的兩個報告將包含相同的值。
Process:很明顯是我們的進程名稱。
Date/Time 與 Launch Time:報告生成時間與程序開始運行時間
Exception Type:異常類型
Exception Note:不屬于異常類型的附加信息,如果這個字段包含SIMULATED(不是崩潰),那這個進程不是崩潰的,而是在系統的請求下被殺死,通常是看門狗機制起了作用(APP內一段時間內無法響應用戶的操作,會被系統kill)。
Termination Reason:閃退的原因,比如常見的數組越界啊,什么的。
Triggered by Thread:出現問題在哪個線程,這個比較重要,首先確定在哪個線程中出了問題,然后再去定位。
接著是最重要的堆棧信息,由下到上為最后調用的順序:
可以很明顯的看到,一個名為ViewController的對象在viewDidLoad
方法中調用了第35行的testMethodTwo
方法,并執行testMethodTwo
方法中的61行時代碼,在Backtrace第2行可以發現調用了未識別的方法導致崩潰,我們來看下代碼。
最終我們找到了崩潰的原因:一個NSArray調用了addObject方法,so easy!
一些較常見的異常類型(如果翻譯錯誤請指正):
內存訪問不良[EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
通常用于訪問了不該訪問的內存導致或者嘗試以不允許的方式訪問內存(例如只讀屬性,比如在上一篇我們提到的通過KVO更改只讀屬性),并且" Exception SubType"字段會包含kern_return_t來描述錯誤和未正確訪問的內存地址。
下面是官方給出的建議:
- 如果
objc_msgSend
或objc_release
接近崩潰線程的Backtraces(堆棧信息回溯)的頂部,則該進程可能試圖向釋放的對象發送消息,可以使用Zombie Instrument來分析應用程序,以更好地了解此次崩潰的情況。- 如果
gpus_ReturnNotPermittedKillClient
接近崩潰線程的Backtraces的頂部,則該進程被終止,因為它嘗試在后臺使用OpenGL ES或Metal進行渲染。
異常退出[EXC_CRASH // SIGABRT]
該進程異常退出,此異常類型崩潰的最常見原因是向對象發送了無法識別的消息,比如上文中向NSArray發送了addObject消息。
另外如果App Extensions需要太多時間來初始化(看門狗機制),那么App Extensions將終止于此異常類型,如果擴展因啟動時掛起而死亡,則生成的崩潰報告的Exception Subtype將會是LAUNCH_HANG
,由于擴展沒有main
函數,任何花在初始化上的時間都會在+load
擴展庫和相關庫中的靜態構造函數和方法中,你應該盡可能多地推遲這項工作。
資源限制[EXC_RESOURCE]
該過程超出了資源消耗限制,這是來自操作系統的通知,該進程正在使用太多的資源,確切的資源列在Exception SubType字段中。如果Exception Note字段包含
NON-FATAL CONDITION
,則即使生成崩潰報告,該進程也不會被終止。
- 異常子類型
MEMORY
表示該進程已超過系統施加的內存限制。- 異常子類型
WAKEUPS
表示進程中的線程每秒被喚醒的次數過多,這迫使CPU醒來的頻率很高,并且消耗電池壽命。
四、符號化dSYM常見問題
如何查看dsYM文件的UUID?
方法一: 通過命令行查看dSYM文件的UUID
xcrun dwarfdump --uuid <dSYM文件>
方法二:通過符號表文件查看UUID
符號表文件的UUID與dSYM文件的UUID是一致的,因此可以通過符號表工具生成的符號表文件來查看dSYM文件的UUID:
生成符號表文件(.zip) ---> 解壓符號表文件(.symbol) ---> 使用文本編輯器打開符號表文件
其中符號表文件的“UUID”信息即Debug SO文件的UUID,亦是符號表文件的UUID,如果文件較大,建議使用“Sublime Text”等文本編輯器來打開符號表文件。
如何定位dSYM文件?
一般情況下,項目編譯完dSYM文件跟app文件在同一個目錄下,下面以XCode作為IDE詳細說明定位dSYM文件。
-> 進入XCode;
-> 打開工程(已編譯過);
-> 在左欄找到“Product”項;
-> 鼠標右鍵點擊編譯生成的“xxx.app”;
-> 點擊“Show in Finder”;
如下圖所示:
如果有多個dSYM文件,可以在使用工具時指定輸入為dSYM文件所在的目錄或者工程目錄。
Xcode編譯后沒有生成dSYM文件?
XCode Release編譯默認會生成dSYM文件,而Debug編譯默認不會生成,對應的Xcode配置如下:
XCode -> Build Settings -> Code Generation -> Generate Debug Symbols -> Yes
XCode -> Build Settings -> Build Option -> Debug Information Format -> DWARF with dSYM File
開啟Bitcode之后需要注意哪些問題?
-
在點“Upload to App Store”上傳到App Store服務器的時候需要聲明符號文件(dSYM文件)的生成:
在配置符號表文件之前,需要從App Store中把該版本對應的dSYM文件下載回本地(參考“如何找回已發布到App Store的App對應的dSYM文件?”),然后用符號表工具生成和上傳符號表文件。
不需要配置自動生成符號表的腳本了,也不要用本地生成的dSYM文件來生成符號表文件,因為本地編譯生成的dSYM文件的符號表信息都被隱藏了。如果用本地編譯生成的dSYM文件生成符號表文件并配置到Bugly平臺之后,還原出來的結果將是類似于“__hiden#XXX”這樣的符號。
如何判斷dSYM文件是否與Crash的UUID匹配?
Bugly還原Crash堆棧時,需要根據UUID來匹配符號表文件,因此只有上傳的符號表文件的UUID與Crash對應APP的UUID一致時,才能準確地對堆棧進行還原。
查看符號表文件的UUID(“如何查看dSYM文件的UUID?”)
查看Crash對應的APP的UUID
如何找回已發布到App Store的App對應的dSYM文件?
1> 通過Xcode找回
- 打開 Xcode 頂部菜單欄 -> Window -> Organizer 窗口:
- 打開 Xcode 頂部菜單欄,選擇 Archive 標簽:
-
找到發布的歸檔包,右鍵點擊對應歸檔包,選擇Show in Finder操作:
右鍵選擇定位到的歸檔文件,選擇顯示包內容操作:
- 選擇dSYMs目錄,目錄內即為下載到的 dSYM 文件:
2>通過mdfind工具找回
在Bugly的issue頁面查詢到crash對應的UUID:
然后在Mac的Shell中,用mdfind命令定位dSYM文件:
mdfind "com_apple_xcode_dsym_uuids == <UUID>"
注意,使用mdfind時,UUID需要格式轉換(增加“-”): 12345678-1234-1234-1234-xxxxxxxxxxxx
例如,要定位的dSYM的UUID為:E30FC309DF7B3C9F8AC57F0F6047D65F 則定位dSYM文件的命令如下:
mdfind "com_apple_xcode_dsym_uuids == E30FC309-DF7B-3C9F-8AC5-7F0F6047D65F"
|12345678-1234-1234-1234-xxxxxxxxxxxx|
建議每次構建或者發布APP版本的時候,備份App對應的dSYM文件!
參考:
iOS開發符號表(dSYM)知識總結
bugly文檔
iOS Crash收集與分析詳解(基礎篇)
iOS Crash監測及處理上傳