title: "クラッシュログのシンボル化と dSYM 保管パイプライン - OnceMini"
summary: "dSYM の生成、UUID 検証、自動シンボル化スクリプトまで、クラウド Mac mini で過去リリースのクラッシュを追跡できる保管パイプラインの作り方。"
クラッシュログのシンボル化と dSYM 保管パイプライン構築
先週、テスト担当チームから TestFlight のクラッシュログが送られてきた。スタックトレースは 0x1042a3f88 のような16進アドレスばかりで、しかも3バージョン前のビルドで発生したものだった。ローカル Mac の ~/Library/Developer/Xcode/Archives を探し回ったが、そのアーカイブは既に Xcode の自動クリーンアップで消去されていた。こうした事態はチーム内でほぼ四半期ごとに発生する——ローカル開発機のディスクは小さく、CI ランナーは使い終わったら破棄される。dSYM のような「ビルド時にしか作られず、後から取り戻せない」ファイルは、専用のアーカイブノードを必要とする性質を持っている。クラウド Mac mini を週単位・月単位でレンタルし、ディスクを消去せず保持しておけば、まさにこの穴を埋められる。
なぜシンボル化を専用パイプラインにするのか
クラッシュログ内のアドレスは、そのビルドを生成した際の dSYM と組み合わせてこそ関数名や行番号に復元できる。dSYM とバイナリの対応関係は UUID で厳密に固定されており、別バージョンの dSYM を使ってシンボル化してもエラーは出ず、ただ誤った行番号が返ってくるだけだ——これが最も見落とされやすく、事後に発見するのが最も難しい落とし穴である。
CI プラットフォームのランナーは通常一時的なインスタンスで、ビルド完了後に破棄されるため過去の dSYM は残らない。ローカル開発機も各自バラバラに管理しているため、誰のマシンにもチーム全バージョン分の dSYM は揃っていない。解決策は単純明快で、ディスクが消去されず、いつでも SSH でログインしてスクリプトを実行できるマシンを用意し、archive ごとに生成される dSYM をビルド番号ごとに保存する専用のアーカイブノードとすることだ。
環境準備:dSYM をビルド成果物と一緒に確実に残す
まず Xcode プロジェクトのデバッグ情報フォーマットが dwarf-with-dsym(Release 構成ではデフォルトでこの設定)になっていることを確認する。これにより archive 時に独立した .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
archive 完了後、dSYM は実際には .xcarchive/dSYMs/ ディレクトリ内に格納されている。.ipa だけをコピーしないこと——多くのチームが最初に踏む落とし穴は、インストールパッケージをエクスポートする際にこの手順を漏らしてしまうことで、後から救済しようとしても同じ commit で再度ビルドをトリガーするしかなくなる。
アーカイブディレクトリ設計と UUID 検証
クラウド Mac mini 上でビルド番号ごとにディレクトリを作成し、アップロード直後に UUID 検証を行い、正しいファイルが保存されていることを確認する:
dwarfdump --uuid MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
dwarfdump --uuid MyApp.app/MyApp
2つのコマンドが出力する 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 一つと commit・UUID を記録した meta.json を1つ置くだけにする。検索スクリプトはビルド番号から直接特定できるため、毎回手動で grep する必要はない。
シンボル化スクリプト:クラッシュログから読める形のスタックへ
クラッシュログを入手したら、atos でアドレスを一つずつ復元する方法は、symbolicatecrash の全レポートを読み解くよりも、疑わしい特定フレームを素早く特定できる:
atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-arch arm64 \
-l 0x1000 \
0x1042a3f88 0x1042a4210
-l の後ろにはバイナリのロードベースアドレスを指定する。これはクラッシュログの Binary Images セクションから直接コピーできる。アドレスのリストはまとめて渡すことができ、出力には関数名とソースファイルの行番号が付く。ログ全体をまとめて処理する場合は、数行の shell スクリプトで各行からアドレスを抜き出し、コマンドを組み立てて元のファイルに書き戻せばよく、GUI ツールに依存する必要はない。
よくある落とし穴とチェックリスト
- マルチアーキテクチャの混同:M シリーズチップ上で動作するアプリは
arm64単一アーキテクチャしか持たないが、過去のスクリプトによく見られる-arch x86_64パラメータを指定するとatosは変換を行わずに元のアドレスをそのまま黙って返してしまう。 - ロードベースアドレスの計算ミス:
0x100000000のようなデフォルト値を安易に使い、ログの Binary Images セクションで実際のベースアドレスを確認しないのは、誤った行番号を招く2番目に多い原因だ。 - アーカイブノードの権限が広すぎる:dSYM はソースコードレベルのデバッグ情報に相当するため、アーカイブディレクトリはリリース担当者と CI サービスアカウントに読み取り専用権限のみを付与することを推奨する。他チームと同じマシンを共有していると誤って削除・上書きされるリスクがある。
私たちの内部での経験では、アーカイブノードがなかった頃は、3ヶ月前のクラッシュログ1件に対応する dSYM を探すのに2人で半日かかっていた。固定のアーカイブフローを整備した後は、こうした依頼は基本的に5分以内で結果が出せるようになった。
シンボル化パイプラインを日常のリリースに組み込む
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 がまさにこの役割を担うのに適している——月単位で固定のマシンを1台借りて専用でアーカイブとシンボル化スクリプトを実行する方が、この用途のためだけにハードウェアを購入するより遥かに割安で、パッケージビルド用のマシンリソースも占有しない。
よくある質問
ローカルではシンボル化に失敗するのに、専用の保管ノードなら成功するのはなぜですか?
ローカル環境の dSYM は Xcode のクリーンアップや OS 再インストールで消え、チームメンバーの端末にも全バージョンが揃っていません。保管ノードはビルド番号ごとに dSYM を永続保存するため、過去のクラッシュログでも一致する UUID のシンボルファイルが見つかります。
クラッシュログのバイナリと手元の dSYM が同じビルドか確認する方法は?
dwarfdump --uuid で dSYM と .app バイナリの UUID を比較し、完全一致するか確認します。不一致でもエラーは出ず行番号だけが狂うため、最も見落としやすい落とし穴です。
App Store Connect が自動生成する dSYM と自前 CI から出力した dSYM の違いは?
内容は同一ですが、App Store Connect のダウンロードには遅延があり保持数も限られます。自前パイプラインはアーカイブ時点で即保存でき、半年前のバージョンを追跡したい場合に有利です。
専用のMac miniでビルドフローをスムーズに回したいですか?
コンソールでMac miniをレンタルする