关于 crashdx
crashdx 是面向 Apple 平台开发者和支持团队的崩溃报告诊断引擎。它结合符号化与基于规则的假设排序,并将证据关联回原始报告;证据不足时会给出无法确定的结果。
主要功能
- 01解析并符号化 .ips 报告
- 02对规则诊断假设排序
- 03将证据追溯至报告路径
- 04按层级输出 JSON
- 05提供 Swift 库和命令行工具
适用场景
- 01调查 Apple 应用崩溃原因
- 02核对诊断引用的证据
- 03在 CI 中解析崩溃分析
- 04通过 MCP 客户端检查报告
MCP 相关
`crashdx-mcp` 服务通过 stdio MCP 工具 `crashdx_analyze` 和 `crashdx_symbolicate` 暴露相同的分析与符号化流程。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
crashdx
适用于 Apple 平台的崩溃诊断引擎:解析 .ips 崩溃报告,根据 dSYM 符号化,并生成引用证据、按可信度排序的诊断,而不只是符号化后的堆栈跟踪。
它以无依赖的 Swift 库(CrashDXCore 仅导入 Foundation)、命令行工具(crashdx)和 MCP 服务(crashdx-mcp)的形式提供,让 Agent 和人类使用同一套引擎。
为什么选择 crashdx
符号化后的堆栈跟踪能告诉你进程在哪里崩溃,却不能说明原因。crashdx 在符号化之上增加了第二个阶段:确定性、基于规则的证据提取(看门狗预算、jetsam 表、寄存器和内存状态、lastExceptionBacktrace/asi),以此生成一组按可信度排序的竞争性假设。每个假设都会引用支持它的具体事实,并指出这些事实在原始报告中的位置。
它刻意坚持两项原则:
- 每项判断都可追溯。 事实会携带指向原始报告的 JSON 路径,因此每项结论都能得到核验,而不是只能盲目信任。
- 确实无法确定时,就会说“无法确定”。 只有当最优假设得到强有力支持,且明显领先第二名时,才会给出结论;否则会列出按可信度排序的候选项,以及如何确认结论。
引擎不会调用大模型:它会生成可验证的事实和按可信度排序的解释,并将叙述留给使用这些结果的工具。完整架构请参阅 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 行引用规则评分所依据的支持性事实,最多列出四项;更多事实则会显示累计总数。每项事实都附有指向原始报告的路径,可通过 --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
其他崩溃类型
同一格式适用于整个规则集。以下是另外四个测试样例的诊断结论和证据行,解释和后续步骤已省略:
# 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 中会解析的内容。以下是与第一个示例相同的崩溃数据,内容已缩略(/* ... */ 表示省略部分)。实际输出是单行、经过压缩且键已排序的 JSON,因此可以通过管道传给 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" }
}
请注意检查位置上的 "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中的 AppleCrashSymbolicator.py,并通过xcode-select -p定位。没有 Xcode 时,crashdx 会退回使用atos,它能解析的源代码位置较少。无论哪种情况,解析和诊断都能正常工作。 /usr/bin/python3和/usr/bin/dwarfdump(两者均由 Xcode 的命令行工具提供),用于运行CrashSymbolicator.py和验证 dSYM UUID。
支持的报告
明确说明支持范围,因为在你的平台上悄悄降低表现的崩溃诊断器,还不如坦诚说明限制:
- 完全支持 arm64 崩溃报告:Apple 芯片 Mac、iOS/iPadOS/watchOS/tvOS 设备,以及在 Apple 芯片上运行的模拟器。这些是测试样本库覆盖的范围。
- 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 → Crash Reports 中查看);在 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 bundle、.xcarchive 或用于递归搜索的目录(可重复指定);--no-spotlight 会跳过基于 Spotlight 的发现,--no-archives 则会跳过 Xcode 的归档目录。运行 crashdx <subcommand> --help 查看完整选项列表。请注意,--json 和 --tier 仅适用于 analyze;symbolicate 始终会输出完整的 .ips。
两个子命令都会自动搜索 Spotlight 和 Xcode 的归档,以查找匹配的 dSYM。对于外部报告(用户提交、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 用法错误 · 65 无法将输入解析为 .ips 报告,或符号化失败 · 66 文件不存在或不可读。
诊断信息输出到 stderr;stdout 输出报告内容(以及 --help/--version)。
MCP 服务
crashdx-mcp 通过 stdio(以换行符分隔的 JSON-RPC)提供与 crashdx_analyze 和 crashdx_symbolicate 工具相同的处理流程。使用 Claude Code 时:
claude mcp add crashdx /usr/local/bin/crashdx-mcp
配套的 crash-triage Agent Skill 位于 .claude/skills/crash-triage/,介绍如何阅读诊断结果,以及各类崩溃特有的解读陷阱。
作为库使用
将 https://github.com/r00tify/crashdx.git 添加到 Package.swift,依赖 CrashDXCore 产品,并调用 AnalyzePipeline.analyze(path:tier:dsymPaths:useSpotlight:searchArchives:)。两个可执行程序都使用同一个入口。
CrashDXCore 除了 Foundation 外不导入任何内容,但依赖它之前有两个限制值得了解:
- 仅支持 macOS。 符号化会通过
Foundation.Process调用atos/dwarfdump/mdfind,而 iOS 上不存在Foundation.Process。将此软件包添加到 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 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。