1. 雲主機
  2. 部落格
  3. 崩潰日誌符號化與 dSYM 歸檔流水線搭建

崩潰日誌符號化與 dSYM 歸檔流水線搭建

CI/CD 實踐 ·約 6 分鐘閱讀

崩潰日誌符號化與 dSYM 歸檔流水線搭建

測試團隊上週丟來一份 TestFlight 當機記錄,堆疊裡全是 0x1042a3f88 這種十六進位位址,回報的還是三個版本之前的建置。你翻遍本機 Mac 的 ~/Library/Developer/Xcode/Archives,那次封存早被 Xcode 自動清空了。這種事幾乎每季都會在團隊裡重演一次——本機開發機硬碟空間有限、CI 執行器用完即銷毀,dSYM 這種「建置時獨一無二、事後再也生不出來」的檔案,天生就需要一個專職的封存節點。雲端 Mac mini 可以按週或按月租用,磁碟不會被自動清理,正好補上這一環。

為什麼符號化要獨立成一條流水線

當機記錄裡的位址,只有配上那次建置產出的 dSYM 才能還原成函式名稱與行號,而 dSYM 與二進位檔之間的對應關係靠一個 UUID 嚴格鎖定——拿錯版本的 dSYM 去符號化並不會報錯,只會安靜地給出錯誤的行號。這是最容易被忽略、事後也最難察覺的坑。

CI 平台上的執行器通常是臨時實例,建置完就銷毀,不會保留歷史 dSYM;本機開發機各自為政,誰的電腦也湊不齊團隊所有版本的封存檔。解法其實很直白:找一台磁碟不會被清空、隨時能 SSH 進去跑腳本的機器,把每次封存產出的 dSYM 按 build 編號存下來,專職當這條流水線的歸檔節點。

環境準備:讓 dSYM 跟著建置產物一起落地

先確認 Xcode 專案的偵錯資訊格式設為 dwarf-with-dsym(Release 設定預設就是這個),封存時才會獨立產出 .dSYM 包:

xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath build/MyApp-$(git rev-parse --short HEAD).xcarchive \
  DEBUG_INFORMATION_FORMAT=dwarf-with-dsym

封存完成後,dSYM 實際藏在 .xcarchive/dSYMs/ 目錄下,別只拷貝 .ipa——很多團隊第一個踩的坑就是匯出安裝包時漏了這步,事後想補救只能重新觸發一次同一 commit 的建置。

歸檔目錄設計與 UUID 校驗

在雲端 Mac mini 上依 build 編號建立目錄,每次上傳後立刻做一次 UUID 校驗,確保存下來的是對的檔案:

dwarfdump --uuid MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
dwarfdump --uuid MyApp.app/MyApp

兩條指令輸出的 UUID 必須逐字元一致。建議把歸檔記錄整理成一張簡單的表,方便日後回溯:

build git commit dSYM UUID(arm64) 上傳時間
214 a91f3c2 6B2E1A4F-... 07-18 09:12
215 c0d7e91 9F1D8C33-... 07-25 14:03
216 e3a4f10 2C77B0A9-... 08-01 10:47

目錄結構固定為 /archive/dsym/{build}/,每層只放一個 dSYM 加一份 meta.json 記錄 commit 與 UUID,查找腳本靠 build 編號直接定位,不用每次都手動 grep。

符號化腳本:從當機記錄到可讀堆疊

拿到當機記錄後,用 atos 逐個位址還原,比翻整份 symbolicatecrash 報告更快定位到單一可疑的堆疊訊框:

atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
     -arch arm64 \
     -l 0x1000 \
     0x1042a3f88 0x1042a4210

-l 後面接的是二進位檔的載入基址,可以直接從當機記錄的 Binary Images 段落抄過來;位址列表可以一次傳多個,輸出就是函式名稱加原始碼行號。要批量處理整份記錄時,寫一支幾行的 shell 腳本逐行擷取位址、拼出指令、再寫回原檔即可,不必依賴任何圖形化工具。

常見踩坑與檢查清單

  • 多架構混淆:M 系列晶片上執行的 App 只有 arm64 一套架構,但歷史遺留腳本裡常見的 -arch x86_64 參數會讓 atos 靜默地直接回傳原始位址,完全不做任何轉換。
  • 載入基址算錯:直接套用 0x100000000 這類預設值糊弄過去,不去記錄的 Binary Images 段落核對真實基址,是第二常見的錯誤行號來源。
  • 歸檔節點權限過寬:dSYM 屬於原始碼層級的偵錯資訊,建議歸檔目錄只給發行負責人與 CI 服務帳號唯讀權限,避免和其他團隊共用一台機器時被誤刪或覆蓋。

我們內部的經驗是:沒有歸檔節點之前,一份三個月前的當機記錄平均要花兩個人半天東拼西湊對應的 dSYM;有了固定的歸檔流程之後,這類請求基本上五分鐘內就能出結果。

把符號化流水線接進日常發行流程

在 fastlane 的發行 lane 末尾加一步,把剛產出的 dSYM 同步到歸檔節點,而不是等到出問題才手忙腳亂去找:

lane :release do
  build_app(scheme: "MyApp")
  sh("scp -r ../build/MyApp.app.dSYM dev@archive-node:/archive/dsym/#{ENV['BUILD_NUMBER']}/")
end

這一步的前提是歸檔節點長期在線、磁碟空間充足,臨時 CI 實例做不到這一點,常駐的雲端 Mac mini 正好可以擔任這個角色——按月租一台固定機器專門跑歸檔與符號化腳本,比為了這一個用途去自購硬體划算得多,也完全不佔用打包用的建置機資源。

常見問題

為什麼本機電腦符號化常常失敗,而專門的歸檔節點能成功?

本機開發機的 dSYM 會隨 Xcode 清理或重裝系統遺失,且團隊成員各自機器上的版本不齊全;歸檔節點把每次 archive 產出的 dSYM 按 build 號持久保存,任何歷史版本的崩潰日誌都能就地找到匹配 UUID 的符號檔。

如何確認崩潰日誌用的二進位和手上的 dSYM 是同一份構建?

用 dwarfdump --uuid 比對 dSYM 與 .app 二進位的 UUID,兩者必須逐字元一致;不一致代表拿錯了歷史版本的 dSYM,符號化出來的堆疊行號會是錯的但不會報錯,這是最容易被忽略的坑。

App Store Connect 自動產生的 dSYM 和自己 CI 匯出的有什麼差別?

兩者內容一致,但 App Store Connect 上的下載有延遲且只保留有限版本;自建歸檔流水線在 archive 階段就落盤,時效更快也能無限期保留,適合需要追溯半年前版本崩潰的場景。

需要一台獨享 Mac mini 跑通你的建置流程嗎?

前往控制台租用 Mac mini