[譯]《iOS Crash Dump Analysis》- 符號化

2021-02-13 iOS成長指北

本章介紹了 crash dump 的符號化。符號化是將機器地址映射成對擁有原始碼的程式設計師有意義的符號地址的過程。我們希望儘可能看到函數名(加上任何偏移量),而不是看到機器地址。

我們使用 icdab_planets 示例應用程式來演示崩潰。(「IOS Crash Dump Analysis Book Github Resources」 2018)

當處理真實的崩潰時,一般會涉及到很多不同的關聯數據。這些數據可以來自用戶終端,通過設置允許將崩潰報告上傳到 Apple 的終端設備,Apple 擁有的符號信息和我們本地開發環境的配置信息可以相互映射。

為了理解所有信息是如何組合在一起的,最好從一開始開始就自己完成數據轉化任務,因此一旦我們需要診斷符號化問題,我們就已有一定的技術經驗。

構建過程

通常,當我們開發應用程式時,我們會將應用程式的 Debug 版本構建到我們的設備上。而當我們為測試人員、應用程式審核或應用商店構建應用時,我們構建應用程式的 Release 版本。

默認情況下,對於 Release版本,.o 文件的調試信息被存儲在一個單獨的目錄結構中。被稱作 our_app_name.DSYM。

當開發人員發現崩潰時,可以使用這些調試信息來幫助我們理解程序在哪裡出錯了。

當用戶發現我們的應用程式崩潰時,並沒有開發人員在身邊。所以,會生成一份崩潰報告。它包含出現問題的機器地址。符號化可以將這些地址轉換為有意義的原始碼來作為參考。

為了進行符號化,必須擁有對應的 DSYM 文件。

默認情況下,Xcode 被設置為只為 Release 版本生成 DSYM 文件,Debug 版本則不會生成該文件。

構建設置

打開 Xcode,選擇 build settings,搜索 「Debug Information Format」,可以看到如下設置:

SettingMeaningUsually set for targetDWARF調試信息僅在 .o 文件中DebugDWARF with dSYM File除了.o 文件,也會將調試信息整理到 DSYM 文件中Release

在默認設置中,如果我們在測試設備上調試 APP 時,點擊應用圖標並啟動 APP ,那麼如果發生崩潰,我們並沒有在崩潰報告中看到任何符號。這使許多人感到困惑。

這是因為二進位文件的 UUID 和 DSYM 並不匹配。

為了避免這個問題,示例程序 icdab_planets 在 Debug和 Release 兩個 Target 中全都設置為 DWARF with dSYM File 。然後我們就可以進行符號化,因為在 Mac 上會生成一個可供匹配的 DSYM。

關注開發環境的崩潰

icdab_planets 程序被設計為在啟動時由於斷言而崩潰。

如果沒有設置成DWARF with dSYM File,我們會得到一個象徵性的部分符號化的崩潰報告。

從 Windows->Devices and Simulators->View Device Logs 中看到的崩潰報告看起來像這樣(為了便於演示而截斷)

Thread 0 Crashed:
0 libsystem_kernel.dylib
0x0000000183a012ec __pthread_kill + 8
1 libsystem_pthread.dylib
0x0000000183ba2288 pthread_kill$VARIANT$mp + 376
2 libsystem_c.dylib
0x000000018396fd0c abort + 140
3 libsystem_c.dylib
0x0000000183944000 basename_r + 0
4 icdab_planets
0x00000001008e45bc 0x1008e0000 + 17852
5 UIKit
0x000000018db56ee0
-[UIViewController loadViewIfRequired] + 1020

Binary Images:
0x1008e0000 - 0x1008ebfff icdab_planets arm64
<9ff56cfacd66354ea85ff5973137f011>
/var/containers/Bundle/Application/
BEF249D9-1520-40F7-93F4-8B99D913A4AC/
icdab_planets.app/icdab_planets

但是,如果設置成DWARF with dSYM File,崩潰報告則會像這樣:

Thread 0 Crashed:
0 libsystem_kernel.dylib
0x0000000183a012ec __pthread_kill + 8
1 libsystem_pthread.dylib
0x0000000183ba2288
pthread_kill$VARIANT$mp + 376
2 libsystem_c.dylib
0x000000018396fd0c abort + 140
3 libsystem_c.dylib
0x0000000183944000 basename_r + 0
4 icdab_planets
0x0000000104e145bc
-[PlanetViewController viewDidLoad] + 17852
(PlanetViewController.mm:33)
5 UIKit
0x000000018db56ee0
-[UIViewController loadViewIfRequired] + 1020

報告的第0、1、2、5行在兩種情況下是相同的,因為我們的開發環境具有正在測試的 iOS 版本的符號信息。在第二種情況下,Xcode 將查找 DSYM 文件以闡明第 4 行。它告訴我們這是在 PlanetViewController.mm 文件中的第33行。是:

assert(pluto_volume != 0.0);

DSYM 結構

DSYM 文件嚴格來說是一個目錄層次結構:

icdab_planets.app.dSYM
icdab_planets.app.dSYM/Contents
icdab_planets.app.dSYM/Contents/Resources
icdab_planets.app.dSYM/Contents/Resources/DWARF
icdab_planets.app.dSYM/Contents/Resources/DWARF/icdab_planets
icdab_planets.app.dSYM/Contents/Info.plist

只是將通常放在 .o 文件中的 DWARF 數據,複製到另一個單獨的文件中。

通過查看構建日誌,我們可以看到 DSYM 是如何生成的。它實際上只是因為這個命令: dsymutil path_to_app_binary -o output_symbols_dir.dSYM

著手符號化

為了幫助我們熟悉 crash dump 報告,我們可以演示實際上符號化是如何工作的。在第一段報告中,我們想要了解:

4 icdab_planets
0x00000001008e45bc 0x1008e0000 + 17852

如果我們能在崩潰時準確的知道代碼的版本,我們就可以重新編譯我們的程序,但是在 DSYM 設置打開的情況下,我們只能在在發生崩潰後獲取一個 DSYM 文件。

crash dump 告訴我們崩潰發生時程序在內存中的程序在內存中的地址信息。這告訴我們其他地址(TEXT)位置相對偏移量。

在crash dump 報告的底部,我們有一行0x1008e0000 - 0x1008ebfff icdab_planets。所以 icdab_planets 的位置從 0x1008e0000 開始。

運行命令 atos 查看你感興趣的位置信息:

# atos -arch arm64 -o
./icdab_planets.app.dSYM/Contents/Resources/DWARF/
icdab_planets -l 0x1008e0000 0x00000001008e45bc
-[PlanetViewController viewDidLoad] (in icdab_planets)
(PlanetViewController.mm:33)

崩潰報告工具基本上就是使用 atos命令來符號化崩潰報告,以及提供其他與系統相關的信息。

如果想要更加深入的了解符號化過程我們可以通過 Apple Technote 來獲取其進一步的描述。(「CrashReport Technote 2123」 2004)

逆向工程方法

在上面的例子中,我們具有crash dump 的原始碼和符號,因此可以執行符號化。

但是有時在我們的項目中,包含了第三方的二進位框架,我們並沒有原始碼。如果框架提供者提供了相應的符號信息讓用戶可以進行 crash dump 分析,這當然是很好的。但是當符號信息不可用時,我們仍然可以通過一些逆向工程的手段來取得進展。

與第三方合作時,故障的診斷和排查通常需要更多的時間。我們發現良好的編寫且具體的錯誤報告可以加速很多事情。以下方法可以為你提供所需的特定信息。

我們將使用工具一章中提到的 Hopper 工具來演示我們的方法。

啟動 hopper,選擇 File->Read Executable to Disassemble。我們使用 examples/assert_crash_ios/icdab_planets中的二進位文件作為示例。

我們需要 「rebase」 反彙編程序,以便它在崩潰時顯示的地址與程序的地址相同。選擇 Modify->Change File Base Address。為了保持一致,輸入 0x1008E0000。

現在我們可以看到崩潰代碼了。地址 0x00000001008e45bc 實際上是設備在跟蹤堆棧中執行函數調用後將 return 到的地址。儘管如此,它仍被記錄在此。選擇 Navigate-> Go To Address and Symbol 並輸入 0x00000001008e45bc 。

我們看到的總體會如下所示

放大這一行,我們能看到

這確實顯示了 assert 方法的返回地址。再往上看,我們看到判斷了 Pluto 的體積不能為零。這只是 Hopper 非常基本的使用示例。接下來我們將使用 Hopper 演示其最有趣的功能——將彙編代碼生成偽代碼。這降低了理解崩潰的心理負擔。如今,大多數開發人員很少查看彙編代碼,所以就這個功能就值得我們為該軟體付出代價。

現在至少對於當前的問題嗎,我們可以編寫一個錯誤報告,指明由於 Pluto 的體積為零,導致代碼崩潰了。對框架的提供者來說,這就足以解決問題了。

在更複雜的情況下,想像我們使用了一個發生崩潰的圖片轉換庫。圖片可能有多種像素格式。使用 assert 可以讓我們注意到某些特定的像素格式。因此,我們可以嘗試其他的像素格式。

另一個例子是 security 庫。安全代碼通常會返回通用錯誤代碼,而不是特定的故障代碼,以便將來進行代碼增強並避免洩漏內部細節(安全風險)。安全庫中的 crash dump 程序可能會指出安全問題的類型,並幫助我們更早地更正傳遞到庫中的某些數據結構。

相關焦點

  • [譯]《iOS Crash Dump Analysis》- Siri崩潰
    crash dump 為我們提供了有用的答案:Application Specific Information: objc_msgSend() selector name: didUnlockScreen:現在我們必須對崩潰的三個方面 what, where 和 when 做出一個近似的解答。
  • [譯]《iOS Crash Dump Analysis》- 崩潰報告
    當 App 發生奔潰時,ReportCrash從作業系統的崩潰過程中提取相關信息,並生成拓展名為.crash的文本文件。當符號信息可用時,Xcode 將符號化奔潰報告然後顯示符號化以後的名稱而不是機器地址。這就提高了報告的可閱讀性,更容易理解。App 已經製作了一份詳細的文檔來解釋 crash dump 的全部結構。
  • [譯]《iOS Crash Dump Analysis》- 錯誤的內存崩潰
    由於問題是由於符號化造成的,所以明智的做法是清除構建目錄,然後進行一次乾淨的構建。有時,Xcode更新會將我們切換到不兼容的新目標文件格式。值得與另一個項目(可能是微不足道的測試程序)一起檢查性能。還有其他內存分析工具,例如我們正在運行的方案的診斷選項,因此可以用不同的方式進行內存分析。有關更多信息,請參見下一章內存診斷 。
  • 有贊crash平臺符號化實踐
    日誌符號化crash日誌符號化通常是通過 atos 和 symbolicatecrash 這兩個工具來完成。是 Xcode 自帶的一個程序,他是對 atos 的封裝,可以翻譯整個crash文件,有贊就是選擇這個工具來進行 crash 符號化的。
  • iOS 崩潰日誌在線符號化實踐
    DWARF 中的數據是高度壓縮的,可以通過 dwarfdump、otool 等命令提取其中的可讀信息。/Versions/A/Resources/symbolicatecrash運行 symbolicatecrash 前一般需要先運行:export DEVELOPER_DIR="/Applications/XCode.App/Contents/Developer"首先將崩潰日誌、 dSYM 以及 symbolicatecrash
  • iOS實錄14:淺談iOS Crash
    所以我們在分析Crash前需要將這些十六進位地址轉化成方法名稱和行數,改過程被稱為符號化。說明2:符號化Crash日誌需要獲取對應的應用二進位文件以及生成二進位文件時產生的 .dSYM 文件(符號表)。必需完全匹配才行。否則,日誌將無法被完全符號化。
  • [譯]《iOS Crash Dump Analysis 2》- Failed Crashes
    #define SIZE 4096#define SHM_NAME "map-jit-memory"#define PT_TRACE_ME 0int ptrace(int, pid_t, caddr_t, int); + (void)crashThenStallCrashReporting:(BOOL)stall { int fd
  • [譯]《iOS Crash Dump Analysis》- 運行時崩潰
    libdispatch.dylib 0x00000001814076b8 _dispatch_semaphore_dispose$VARIANT$mp + 761 libdispatch.dylib 0x00000001814067f0 _dispatch_dispose$VARIANT$mp + 802 icdab_sema_ios
  • 淺談 iOS Crash(一)
    所以我們在分析Crash前需要將這些十六進位地址轉化成方法名稱和行數,改過程被稱為符號化。說明2:符號化Crash日誌需要獲取對應的應用二進位文件以及生成二進位文件時產生的 .dSYM 文件(符號表)。必需完全匹配才行。否則,日誌將無法被完全符號化。
  • [譯]《iOS Crash Dump Analysis》- 應用程式中止崩潰
    方便的是,其他工程師已經在框架上使用 class-dump 工具來生成所有 Objective-C 的類定義,並將它們存儲在 GitHub上。他們使用 class-dump工具。這使得 Objective-C 的所有私有框架符號都很容易搜索到。我們可以找到關於 _isDispatchData的定義。
  • iOS Crash 分析攻略
    如果這個version偏高,用系統的symbolicatecrash命令不能符號化日誌,一般如果看到是204, 改成104之後用symbolicatecrash就可以符號化了蘋果收集的日誌,Xcode會自動幫我們符號化,如果你沒有發布包,比如是別人電腦打包的發布包,或者是一些平臺上打的包,只需要你把 xcarchive  拷貝到 $HOME/Library/Developer/Xcode/Archives 目錄下之後,Xcode 就可以自動幫你符號化了。
  • [譯]《iOS Crash Dump Analysis》- 內存診斷
  • 深入理解iOS Crash Log
    注意,crash log中的二進位文件會有一個唯一的uuid,dsym文件也有一個唯一的uuid,這兩個文件的uuid對應到一起才能夠進行符號化。如果你在上傳到App Store的時候,選擇了上傳dsym文件,那麼從XCode中看到的崩潰日誌是自動符號化的。
  • 使用Crash工具分析 Linux dump文件
    DiskdumpDiskdump 是另外一個內核崩潰內存轉儲的內核補丁,它由塔高 (Takao Indoh) 在 2004 年開發出來。與 LKCD 相比,Diskdump 更加簡單。當系統崩潰時,Diskdump 對系統有完全的控制。為避免混亂,它首先關閉所有的中斷;在 SMP 系統上,它還會把其他的 CPU 停掉。然後它校驗它自己的代碼,如果代碼與初始化時不一樣。
  • 使用 Crash 工具分析 Linux dump 文件
    DiskdumpDiskdump 是另外一個內核崩潰內存轉儲的內核補丁,它由塔高 (Takao Indoh) 在 2004 年開發出來。與 LKCD 相比,Diskdump 更加簡單。當系統崩潰時,Diskdump 對系統有完全的控制。為避免混亂,它首先關閉所有的中斷;在 SMP 系統上,它還會把其他的 CPU 停掉。然後它校驗它自己的代碼,如果代碼與初始化時不一樣。
  • [譯]《iOS Crash Dump Analysis 2》- Apple Silicon Mac
  • # iOS進階 # 崩潰與日誌分析
    1、iOS設備可以直接查看路徑:ios 10之後:設置 -> 隱私 -> 分析 -> 數據分析ios 10之前:設置 -> 隱私 -> 診斷與用量上面那行被符號化後的版本如下 :2   demo     0x00029288 ViewController.crashAction(Any) -> () (ViewController.swift:36)Xcode符號化崩潰日誌時,需要訪問與App Store上對應的應用二進位文件以及生成二進位文件時產生的 .dSYM 文件。必需完全匹配才行。
  • iOS Crash 殺手排名
    殺手 NO.1:NSInvalidArgumentException 異常出現這個crash的原因有很多,選取了崩潰次數較多的crash。crash 日誌1-1-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[3]crash日誌拿到了,怎麼復現該現象呢?
  • 愛奇藝 Xcrash 是怎麼捕獲 crash 的
    Xcrash是愛奇藝在2019年4月開源在GitHub上的穩定性日誌收集框架,它能為android收集java crash、native crash、anr日誌。 4   //create and open log file  打開log文件 5   if((xc_crash_log_fd = xc_common_open_crash_log(xc_crash_log_pathname, sizeof(xc_crash_log_pathname), &xc_crash_log_from_placeholder)) < 0) goto
  • xCrash 詳解與源碼分析
    xc_trace_libart_runtime_dump 開始 dump相關代碼如下: if(xc_trace_is_lollipop) xc_trace_libart_dbg_suspend(); xc_trace_libart_runtime_dump