crashdx について
crashdxは、Appleプラットフォームの開発者やサポートチーム向けのクラッシュレポート診断エンジンです。シンボリケーションとルールベースの仮説ランキングを組み合わせ、根拠をレポート内の場所に結び付けます。根拠が不十分な場合は結論を保留します。
主な機能
- 01.ipsレポートの解析とシンボリケーション
- 02ルールベースのクラッシュ仮説を順位付け
- 03根拠をレポート内のパスに対応付け
- 04階層別のJSON出力
- 05SwiftライブラリとCLIの提供
活用シーン
- 01Appleアプリのクラッシュ原因を調査
- 02診断に引用された根拠を確認
- 03CIでクラッシュ解析を処理
- 04MCPクライアントからレポートを調査
MCP 対応
`crashdx-mcp`サーバーは、stdio経由のMCPツール`crashdx_analyze`と`crashdx_symbolicate`で同じ解析・シンボリケーション処理を公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
crashdx
Apple プラットフォーム向けのクラッシュ診断エンジンです。.ips クラッシュレポートを解析し、dSYM と照合してシンボリケートし、シンボリケート済みスタックトレースだけでなく、根拠を引用した順位付きの診断を生成します。
依存関係のない Swift ライブラリ(CrashDXCore は Foundation のみをインポート)、CLI(crashdx)、MCP サーバー(crashdx-mcp)として提供され、エージェントと人間が同じエンジンを利用できます。
その理由
シンボリケート済みスタックトレースからわかるのはプロセスがどこで終了したかであり、なぜ終了したかではありません。crashdx はシンボリケーションの上に第 2 段階を追加します。決定的なルールベースの根拠抽出(ウォッチドッグの予算、jetsam テーブル、レジスターとメモリーの状態、lastExceptionBacktrace/asi)を行い、それぞれを裏付ける具体的な事実を引用し、生のレポート内の該当箇所を示す、競合する仮説を順位付けして提示します。
意図的に守っている特性は 2 つあります。
- すべての主張を追跡できます。 事実には元のレポート内の JSON パスが付いているため、どの判定も信頼するのではなく検証できます。
- 結論が出ない場合は「結論が出ません」と示します。 判定には、最有力仮説が強く裏付けられ、かつ次点を明確に上回っている必要があります。そうでない場合は、順位付きの候補と、結論を確定するために必要な情報が提示されます。
エンジンは LLM を呼び出しません。検証可能な事実と順位付きの解釈を生成し、説明文の作成はそれを利用する側に委ねます。アーキテクチャの全容については、docs/DESIGN.md をご覧ください。
出力例
以下はすべて、このリポジトリのフィクスチャから得られた未編集の実際の出力です(省略箇所には印を付けています)。
パスはリポジトリのルートからの相対パスで、--dsym フラグはリポジトリに含まれる dSYM を指定するため、そのまま再現できます。バイナリをインストールしていない場合は、swift run crashdx ... を使用してください。
判定
$ crashdx analyze Tests/CrashDXCoreTests/Fixtures/nsexcrash.ips \
--dsym Tests/CrashDXCoreTests/Fixtures/nsexcrash.dSYM
process: nsexcrash
bug_type: 309
os: macOS 26.3.1 (25D2128)
exception: EXC_CRASH (SIGABRT)
terminated: Abort trap: 6
faulting thread (15 frames):
libsystem_kernel.dylib __pthread_kill
libsystem_pthread.dylib pthread_kill
libsystem_c.dylib abort
libc++abi.dylib __abort_message
libc++abi.dylib demangling_terminate_handler()
libobjc.A.dylib _objc_terminate()
libc++abi.dylib std::__terminate(void (*)())
libc++abi.dylib __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception*)
libc++abi.dylib __cxa_throw
libobjc.A.dylib objc_exception_throw
CoreFoundation -[NSException raise]
nsexcrash throwingHelper() (main.swift:8)
nsexcrash doWork() (main.swift:12)
nsexcrash main (main.swift:15)
dyld start
last exception backtrace (7 frames):
CoreFoundation __exceptionPreprocess
libobjc.A.dylib objc_exception_throw
CoreFoundation -[NSException raise]
nsexcrash throwingHelper() (main.swift:8)
nsexcrash doWork() (main.swift:12)
nsexcrash main (main.swift:15)
dyld start
symbolication (engine: crashSymbolicator):
nsexcrash symbolicated
libsystem_kernel.dylib no_dsym
libsystem_pthread.dylib no_dsym
...
DIAGNOSIS: Uncaught Objective-C exception (NSException) (strong, score 5)
A lastExceptionBacktrace is present and an objc_exception_throw frame appears in it (or on the
faulting thread) — together these are pathognomonic for an uncaught NSException: `-raise` walked up
through objc_exception_throw and was never caught, so the runtime called terminate and aborted. The
asi "Terminating app due to uncaught exception" message is only corroboration when present; plain
CLI/Foundation processes never write it, so its absence is not evidence against this hypothesis.
evidence: lastExceptionBacktrace is present with 7 frame(s); lastExceptionBacktrace frame 1 matches
sentinel 'objc-exception-throw': objc_exception_throw; Faulting thread frame 9 matches
sentinel 'objc-exception-throw': objc_exception_throw
inspect: nsexcrash throwingHelper() (main.swift:8)
confirm: Symbolicate the app frames in lastExceptionBacktrace to find the throw site; Check the
asi/console log for the exception name and reason, if available
also considered: cxx-terminate (moderate, 2), abort-generic (weak, 1)
evidence 行では、ルールのスコア付けに使われた根拠として最大 4 つの事実を引用し、それを超える場合は累計を示します。各事実には元のレポート内の該当箇所へのパスが付いており、--json --tier standard で確認できます。also considered は順位付けされたリストの残りすべてを示すため、判定がどの候補をどれだけ上回ったかを確認できます。
結論が出ない場合
最有力仮説が強く裏付けられず、かつ次点を明確に上回っていない場合は、ラベルではなく候補が提示されます。
$ crashdx analyze Tests/CrashDXCoreTests/Fixtures/synthetic/wild-address.ips \
--dsym corpus/fixtures/nullderef/nullderef.dSYM
process: synthetic-wild-address
bug_type: 309
os: macOS 26.3.1 (25D2128)
exception: EXC_BAD_ACCESS (SIGSEGV)
terminated: Segmentation fault: 11
faulting thread (4 frames):
nullderef readThroughNullPointer() (main.swift:9)
nullderef run() (main.swift:13)
nullderef main (main.swift:16)
dyld start
symbolication (engine: crashSymbolicator):
nullderef symbolicated
dyld no_dsym
DIAGNOSIS: INCONCLUSIVE — competing hypotheses:
1. Wild pointer or use-after-free [wild-or-uaf-address] (moderate, score 3)
The faulting address is outside the null page, and vmregioninfo reports it is not inside any known
VM region at all — consistent with either a WILD pointer (an uninitialized or garbage value used as
an address) or a USE-AFTER-FREE into memory the OS has since unmapped. This is deliberately a
lower-confidence hypothesis than null-dereference: absence of region information doesn't distinguish
between these two causes.
evidence: Faulting address: 0x1deadbeef; Exception type: EXC_BAD_ACCESS
inspect: nullderef readThroughNullPointer() (main.swift:9)
confirm: Re-run with Address Sanitizer enabled to catch the use-after-free at the free/access site;
Enable NSZombies (Malloc Scribble/Guard Malloc) to turn this into an immediate,
informative crash; Profile with Instruments' Allocations tool, recording reference counts
その他のクラッシュの種類
同じ形式がルールセット全体に適用されます。さらに 4 つのフィクスチャから、説明と次の手順を省略した判定行と根拠行を示します。
# synthetic/watchdog-scene-create.ips
DIAGNOSIS: Watchdog timeout (main thread stall) (strong, score 6)
evidence: Termination code: 2343432205 (0x8badf00d); Termination namespace: FRONTBOARD; Watchdog
event: scene-create, allowance 19.97s
# synthetic/jetsam-per-process-limit.ips
DIAGNOSIS: Jetsam memory kill (strong, score 5)
evidence: Exception subtype: RESOURCE_TYPE_MEMORY; Jetsam per-process-limit indicator:
per-process-limit 350808KB exceeds task limit 335872KB
# synthetic/stack-overflow.ips
DIAGNOSIS: Stack overflow (strong, score 5)
evidence: Faulting address 0x3000 is near the stack region: vmregioninfo names a STACK GUARD region;
within 500 bytes of sp (0x31f4); Faulting thread has 5 consecutive frames with identical
symbol 'recurse(_:)' starting at frame 0 — a recursion signature
# crashspike-unsymbolicated.ips, with --dsym Tests/CrashDXCoreTests/Fixtures/crashspike.dSYM
DIAGNOSIS: Swift runtime fatal trap (strong, score 6)
evidence: Exception type: EXC_BREAKPOINT; Signal: SIGTRAP; Faulting thread frame 0 matches sentinel
'assertion-failure': _assertionFailure(_:_:file:line:flags:)
これらの各例でも、最初の例に示した完全な説明、inspect ポイント、confirm 手順が出力されます。
JSON
--json は、構造化された AnalysisReport として同じ分析を出力します。これは MCP サーバーが返す内容であり、CI で解析する内容でもあります。以下は、最初の例と同じクラッシュの出力を抜粋したものです(省略箇所は /* ... */ で示しています)。実際の出力はキーをソートした 1 行の minified 形式なので、jq または python3 -m json.tool にパイプしてください。ここでは読みやすいように整形し、順序を入れ替えています。
{
"schemaVersion": "0.2",
"tier": "summary",
"diagnosis": {
"status": "verdict",
"verdict": {
"id": "uncaught-objc-exception",
"title": "Uncaught Objective-C exception (NSException)",
"category": "objc-exception",
"explanation": "A lastExceptionBacktrace is present and an objc_exception_throw frame ...",
"supporting": [
{ "factID": "leb.present", "weight": 1 },
{ "factID": "leb.sentinel.objc-exception-throw", "weight": 3 },
{ "factID": "frames.sentinel.objc-exception-throw", "weight": 1 }
],
"contradicting": [],
"inspect": [
{ "frameIndex": 3, "leb": true, "symbol": "throwingHelper()",
"sourceFile": "main.swift", "sourceLine": 8 }
],
"confirmFurtherBy": [ /* ... */ ]
},
"hypotheses": [ /* all 3, each with its band + score */ ]
},
"event": {
"bugType": "309", "exceptionType": "EXC_CRASH", "signal": "SIGABRT",
"terminationIndicator": "Abort trap: 6", "terminationNamespace": "SIGNAL"
},
"faultingThread": { "index": 0, "queue": "com.apple.main-thread", "triggered": true,
"frames": [ /* ... */ ] },
"symbolication": { "engine": "crashSymbolicator", "images": [ /* per-image outcome + uuid */ ] },
"process": { "name": "nsexcrash", "osVersion": "macOS 26.3.1 (25D2128)",
"captureTime": "2026-07-23 00:22:01.2793 +0000" }
}
inspect ポイントの "leb": true に注目してください。スロー箇所は障害スレッドではなく lastExceptionBacktrace から復元されており、レポートではこの違いが保持され、ひとまとめにされていません。
--tier standard(レポートのティアを参照)を指定すると、diagnosis.factsConsidered が追加されます。これにより、すべての factID が人間に読みやすい記述と、生のレポート内の sourcePath に対応付けられます。
{ "id": "leb.sentinel.objc-exception-throw",
"statement": "lastExceptionBacktrace frame 1 matches sentinel 'objc-exception-throw': objc_exception_throw",
"sourcePath": "lastExceptionBacktrace[1]" }
Fact の sourcePath は省略できないため、診断で引用されるすべての事実について、読み取られたレポート内の箇所までたどることができます。
両方のティアのコミット済みゴールデンスナップショットは、Tests/CrashDXCoreTests/Fixtures/(nsexcrash-summary-golden.json、nullderef-standard-golden.json)にあります。完全な省略なしの形式を確認できます。
要件
- macOS 14 以降
- Swift 6.2 以降のツールチェーン。マニフェスト自体の
swift-tools-version:6.0はそのままですが、MCP サーバーの固定された依存関係が最大6.2を宣言しており、SwiftPM はパッケージグラフ全体を解決するため、CrashDXCoreを含むすべてのターゲットで、実質的な最低バージョンは 6.2 です。 - Xcode(Command Line Tools だけでは不可):シンボリケーションでは、Xcode の
CoreSymbolicationDT.frameworkに含まれる Apple のCrashSymbolicator.pyを、xcode-select -pで検出して実行します。これがない場合、crashdx はatosにフォールバックしますが、 解決できるソースの場所は少なくなります。解析と診断はどちらの環境でも動作します。 /usr/bin/python3と/usr/bin/dwarfdump(どちらも Xcode のコマンドラインツールに付属)。CrashSymbolicator.pyの実行と dSYM の UUID の検証に使用します。
サポート対象のレポート
実際の対応範囲を示します。お使いのプラットフォームで密かに性能が落ちるクラッシュ診断ツールより、対応できないことを明示するツールのほうが優れているためです。
- arm64 のクラッシュレポートは完全にサポートされています:Apple silicon Mac、iOS/iPadOS/watchOS/ tvOS デバイス、および Apple silicon 上でホストされるシミュレーターが対象です。フィクスチャコーパスは この範囲をカバーしています。
- x86_64 レポート(Intel Mac、Rosetta で変換されたプロセス、Intel 上のシミュレーター)は解析と
シンボリケーションが可能で、エンジンは
x86_THREAD_STATEレジスターと 4 KB のページサイズを読み取ります。 コーパスには Intel のフィクスチャがないため、この経路は実際のレポートで検証されておらず、想定に基づくものです。 - クラッシュレポートのみ(
bug_type309/109)が対象です。Jetsam イベントレポート(JetsamEvent-*.ips、bug_type298)とハング/stackshot レポート(bug_type288)は、スレッドも例外オブジェクトもない別のペイロード形式です。crashdx はエラーなく解析しますが、分析に使える事実がないため、判定不能と報告します。
プライバシー
crashdx はすべての処理をお使いのマシン上で行い、いかなる種類のネットワーク通信も行いません。クラッシュ
レポート、dSYM、およびそこから派生するデータはすべてローカルに保持されます。.ips ファイルには識別情報
(crashReporterKey、起動/スリープ復帰 UUID、デバイスモデル、パスに含まれるユーザー名)が含まれるため、
バグレポートに添付する場合は、事前に情報を削除してください。
インストール/ビルド
git clone https://github.com/r00tify/crashdx.git
cd crashdx
swift build -c release
バイナリは .build/release/ に生成されます。PATH に追加するには:
sudo mkdir -p /usr/local/bin
sudo cp .build/release/crashdx .build/release/crashdx-mcp /usr/local/bin/
(または、~/.local/bin など、すでに所有している場所にコピーしてください。)
使い方
macOS のクラッシュレポートは ~/Library/Logs/DiagnosticReports/ にあります(または Console.app →
クラッシュレポート)。iOS では、「設定」→「プライバシーとセキュリティ」→「解析と改善」→
「解析データ」、または TestFlight/App Store のクラッシュの場合は Xcode の Organizer を確認してください。
crashdx analyze report.ips # human-readable summary + diagnosis
crashdx analyze report.ips --json # structured AnalysisReport JSON
crashdx analyze report.ips --json --tier full # ...with every thread included
crashdx symbolicate report.ips --dsym path/to.dSYM # just symbolicate, print enriched .ips
crashdx --version
--dsym には .dSYM バンドル、.xcarchive、または再帰的に検索するディレクトリを指定できます
(複数回指定可能)。--no-spotlight を指定すると Spotlight による検出をスキップし、
--no-archives を指定すると Xcode のアーカイブディレクトリをスキップします。すべてのオプションについては、
crashdx <subcommand> --help を実行してください。--json と --tier は analyze のみに適用されます。
symbolicate は常に完全な .ips を出力します。
どちらのサブコマンドも、対応する dSYM を Spotlight と Xcode のアーカイブから自動的に検索します。
外部から入手したレポート(ユーザーからの提出、TestFlight、CI アーティファクト)の場合は、
ビルドの dSYM を --dsym で明示的に指定してください。
レポートから取得した文字列(プロセス名、シンボル、パス、例外テキスト)は、人間向けのサマリーを出力する前に
エスケープされます。C0 制御文字、DEL、双方向テキスト制御文字はそのまま出力されず、
\x0A/\u{202E} のように表示されるため、細工されたレポートによって DIAGNOSIS: 行を偽造されたり、
ターミナルの表示を書き換えられたりすることはありません。--json と symbolicate はレポートの文字列を
そのまま出力します。これらの出力はターミナルテキストではなく、データとして扱ってください。
異なるビルドの dSYM は使用されず、拒否されます。古い dSYM は確信を持って誤ったシンボルを返すため、
何も返さないよりも悪い結果になります。このケースは uuid_mismatch として報告され(reason に該当するパスが含まれます)、
no_dsym とは区別されます。対処法が異なるためです。すでに手元にあるかもしれないファイルを探すのではなく、
レポートのビルド UUID と一致するアーカイブを探してください。
レポートの詳細レベル
--tier は出力するレポートの詳細度を制御します。診断結果は常にレポート全体から算出されるため、変更されません。
| 詳細レベル | 内容 |
|---|---|
summary(デフォルト) | 障害が発生したスレッド(最大15フレーム)、lastExceptionBacktrace、診断結果と優先順位付きの仮説 |
standard | フレーム数の上限を解除し、バイナリイメージ、アプリのフレームを含むその他のスレッド、factsConsidered(各仮説の根拠)を追加 |
full | アプリのフレームを含まないスレッドも含め、残りのすべてのスレッドを追加 |
終了コード
0 成功 · 64 使用方法のエラー · 入力を .ips レポートとして解析できなかった場合、またはシンボリケーションに失敗した場合は 65 · ファイルが見つからない、または読み取れない場合は 66。
診断は stderr に出力され、stdout にはレポート出力(および --help/--version)が出力されます。
MCP サーバー
crashdx-mcp は、crashdx_analyze および crashdx_symbolicate ツールとして、stdio(改行区切りの JSON-RPC)経由で同じパイプラインを提供します。Claude Code では次のように使用できます。
claude mcp add crashdx /usr/local/bin/crashdx-mcp
.claude/skills/crash-triage/ には対応する crash-triage エージェントスキルが含まれており、診断の読み方と、クラッシュの種類ごとに特有の解釈上の注意点を説明しています。
ライブラリとして使用する
https://github.com/r00tify/crashdx.git を Package.swift に追加し、CrashDXCore プロダクトに依存させて、AnalyzePipeline.analyze(path:tier:dsymPaths:useSpotlight:searchArchives:) を呼び出します。これは、2つのバイナリが使用するのと同じエントリーポイントです。
CrashDXCore がインポートするのは Foundation のみですが、依存する前に知っておくべき制限が2つあります。
- macOS 専用です。 シンボリケーションでは
Foundation.Process経由でatos/dwarfdump/mdfindを実行しますが、これらは iOS には存在しません。このパッケージを iOS ターゲットに追加すると、コンパイル開始前の依存関係の解決時に失敗します。 - SwiftPM は単一のプロダクトではなくパッケージグラフ全体を解決するため、このパッケージに依存すると、MCP サーバーの依存関係(swift-sdk と、その推移的な NIO/collections/log パッケージ)が、インポートしていなくても
Package.resolvedに固定されます。
AnalysisReport と診断モデルは Sendable なので、レポートのディレクトリを想定どおり並行して解析できます。
プロジェクトの構成
Sources/CrashDXCore/ Parsing, symbolication, and the diagnosis engine (imports only Foundation)
Sources/crashdx/ CLI
Sources/crashdx-mcp/ MCP server (depends on swift-sdk)
Tests/ Unit + integration tests, golden snapshots, crash fixtures
corpus/ Ground-truth crash fixtures and their verified findings
docs/DESIGN.md Diagnosis engine architecture
Scripts/ Fixture scrubbing and the CI privacy guard
テスト
swift test
コントリビューション
CONTRIBUTING.md を参照してください。最も重要なルールは、クラッシュレポートと dSYM はコミットする前に必ず機密情報を削除することです。これらにはデバイス識別子とユーザー名が含まれます。Scripts/scrub-fixture.py は .ips ファイルを処理し、Scripts/check-fixtures-scrubbed.sh(CI でも実行されます)は、情報が残っていないか確認します。
セキュリティとプライバシー
crashdx はネットワークへの接続を行わないため、マシンの外に情報が送信されることはありません。脆弱性の報告方法と、共有する前に .ips ファイルに実際に何が含まれているかについては、SECURITY.md を参照してください。
ライセンス
MIT。LICENSE を参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。