Xcode MCP 助力运行时二进制分析

iOS 客户端最头疼的一类 Crash:栈里全是系统库,无现成源码可参考。静态反汇编工具多数时候只能推导不能证实,且丢失运行时信息这样的关键线索,面对有些疑难杂症,运行时二进制分析在所难免。

而这样的分析需要环境构造、GUI 操作、业务复现等,AI 难以完全承担,更合理的是工程师与 AI 协同分析。

IDA 虽然可调试 iOS,但操作复杂体验差,适配落后;lldb-mcp 没有 GUI 更适合自动化;新版 Xcode 的 mcpbridge 提供 LLDB 调用通道,似乎可以做到无缝协同。

头皮发麻的系统栈

举一个 Crash 报告例子:

objectivec 复制代码
EXC_BAD_ACCESS(SIGSEGV)
Trapped mach exception code 0x1, subcode 0x1d

0   libdispatch.dylib     dispatch_source_set_timer + 36
1   CFNetwork             URLConnectionLoader::loadWithWhatToDo(NSURLRequest*, _CFCachedURLResponse const*, long, URLConnectionLoader::WhatToDo) + 959
2   CFNetwork             URLConnectionLoader::loadWithWhatToDo(NSURLRequest*, _CFCachedURLResponse const*, long, URLConnectionLoader::WhatToDo) + 959
3   CFNetwork             invocation function for block in URLConnectionLoader::continueWithCacheLookupResult(NSURLRequest*, _CFCachedURLResponse const*, NSURLRequestCachePolicy) + 99
4   CFNetwork             invocation function for block in URLConnectionLoader::withExistingProtocolAsync(void (URLProtocol*) block_pointer) const + 39
5   CFNetwork             invocation function for block in QCoreSchedulingSet::performAsync(void () block_pointer) const + 51
6   libdispatch.dylib     _dispatch_call_block_and_release + 31
7   libdispatch.dylib     _dispatch_client_callout + 15
8   libdispatch.dylib     _dispatch_lane_serial_drain + 739
9   libdispatch.dylib     _dispatch_lane_invoke + 439
10  libdispatch.dylib     _dispatch_workloop_invoke + 1611
11  libdispatch.dylib     _dispatch_root_queue_drain_deferred_wlh + 291
12  libdispatch.dylib     _dispatch_workloop_worker_thread + 691
13  libsystem_pthread.dylib  _pthread_wqthread + 326

Thread 65 crashed with ARM Thread State (64-bit):
    x0:0x0000000121be6f80    x1:0x0000019d410181e8    x2:0xffffffffffffffff    x3:0x000000003b9aca00
    x4:000000000000000000    x5:0x0000000000000020    x6:000000000000000000    x7:000000000000000000
    ...
   fp:0x000000016dfa2690    lr:0x00000001b5ad9968    sp:0x000000016dfa2650
    pc:0x00000001d9455fe0  cpsr:0x0000000020000000

全是系统库,没有一帧是业务代码,线索只有寄存器现场和 subcode。

古法时代我们怎么做

首要目标:确认 Crash 的对象类型,以及其来源。

在古法时代,我们可能尝试做的事情:

  1. 静态反汇编:没有 dSYM,先拿 crash report 里的二进制 UUID 找到对应版本的系统库,拖进 Hopper / Ghidra 跳到偏移处读指令或伪代码,由于缺失运行时信息,很多场景只能做大量猜测无法直接证实
  2. 寻找源代码:少量基础库能在 Apple 开源站找到,版本还未必对得上;更多核心组件(比如 CFNetwork 内部那批 C++ 类)闭源,公网根本没有,只能对着汇编硬啃
  3. 运行时汇编分析 :起 LLDB、attach、下断点、触发复现,之后全靠人:
    • 读寄存器推参数语义,比如 x0 是指针还是常量,是 OC 对象还是 C++ 对象
    • 读内存推对象布局,比如 +0x58 偏移是什么成员,每个数据类型占用多少字节
    • 读汇编分支推测走向,写寄存器、写内存改变指令执行路径
    • OC 对象要顺着 isa 扒 class 和方法列表,block 对象要挖出引用表分析内存计数
    • 循环推测验证:每一步都是猜测。库版本对不对、对象类型是什么、偏移含义、故障机制(初始化未完成还是释放后清零)------猜错任何一环,整条推理链推倒重来

问题复杂程度不同,短则几十分钟,长则数天。而其中有大量复杂的机械劳动 ------------ 这正是 AI 该接管的部分。

如何使用 Xcode MCP

接入很简单:Xcode > Settings > Intelligence 打开「Allow external agents to use Xcode tools」,然后在 Agent 客户端注册 Xcode 自带的 mcpbridge:

json 复制代码
{
  "mcpServers": {
    "xcode": {
      "command": "xcrun",
      "args": ["mcpbridge"]
    }
  }
}

接上之后,里面 InvokeDebuggerCommand 就是 lldb 操作通道,AI 就能在活动调试会话里下断点、读寄存器、读内存、改执行路径------操作实时同步在 Xcode 界面上,人随时可以观察和接管。分工自然形成:人负责触发复现、收敛假设、拍板结论;AI 负责断点取证、读汇编、批量验证。

但直接让 AI 干会踩坑:continue 是异步的,返回 resuming 不代表已暂停,必须轮询 process status 等停止;lldb 命令可换行串联批量执行,省掉大半往返......这些纪律沉淀为一个简单的 skill。

skill 不写任何指令含义、系统类内存布局等通识性东西,仅写:

  • 通道纪律:轮询、调用形态、批量命令,通道特性,模型不知道,漏写链路走不通
  • 场景经验 :无符号库的 slide 偏移换算、objc_msgSend 之后参数从 x2 开始------「场景 × 动作」的经验,模型有通识但可能不会组合

工程师与 AI 协同分析

Xcode Run 起来后,结合 Agent 的观感是这样的(图中 Agent 通过 MCP 调用 Xcode LLDB 进程去执行指令并回执):

Agent 执行了半小时的样子(模型拥挤时段),这期间需要人工做一些 GUI 交互,简要分析流程如下:

perl 复制代码
> 线索只有崩溃报告:`subcode 0x1d`、`dispatch_source_set_timer + 36`、寄存器现场,栈内无应用帧。

1. **授权与会话探测**|`XcodeOpenWorkspace` 触发授权,从两个 workspace id 里挑出真正带活动调试会话的那个,`process status` 确认 PID 63623 在跑|执行 11 条命令、写 0 B|读 1 个空文件、0 B 内存
2. **定位触发入口**|在 27 个 `.xcodeproj` 里定位 demo,读 `ViewController.m`、`ScenarioRunner.m` 确认「案例2」按钮发网络请求|执行 4 条命令、写 0 B|读 2 个源码文件约 90 行、0 B 内存
3. **模块与符号定位**|`image list` + `image lookup -n/-rn`:本机没有独立的 C 符号 `dispatch_source_set_timer`,实现是 `_dispatch_source_set_runloop_timer_4CF`|执行 6 条命令(含 2 次失败)、写 0 B|读 约 250 条符号、0 B 内存
4. **反汇编定位故障指令**|`disassemble -n ... -c 16` 拿到 `+32 mov x19,x0` → `+36 ldr x25,[x0,#0x58]` → `+40 ldrb w8,[x25,#0x1d]`,`0x1d` 正是这条指令的立即数|执行 1 条命令、写 0 B|读 32 条反汇编、0 B 内存
5. **溯源:对象由谁创建**|反汇编 `QCoreSchedulingSet::createDispatchTimer`,见其尾调用 `dispatch_source_create` 创建 TIMER 源|执行 2 条命令、写 0 B|读 24 条反汇编、0 B 内存
6. **溯源:谁在调 set_timer**|反汇编 `URLConnectionLoader::scheduleTimeoutTimer`,见 `+92` 从 loader 取源、`mov x2,#-1`、组装 `0x3b9aca00`,与报告 x2/x3 逐位一致|执行 2 条命令、写 0 B|读 40 条反汇编、0 B 内存
7. **确认导入桩**|读 `__auth_stub` 的 `adrp/add/ldr/braa`,确认 CFNetwork 的 set_timer 调用最终跳进 4CF|执行 3 条命令(含 1 次失败)、写 0 B|读 10 条反汇编、0 B 内存
8. **断点一轮:4CF 函数入口**|命中 UIKit/BaseBoard 键盘定时器,读栈判定为共享 API 噪音;顺带 `po` + 内存读首次确认 `OS_dispatch_source` 与 `[x0+0x58]=ds_refs`,随后禁用|执行 5 条命令、写 0 B|读 8 帧栈、9 个寄存器、2 处内存 64 B
9. **断点二轮:set_timer 导入桩**|仍命中 BaseBoard,判定它是共享缓存全局桩、非 CFNetwork 独占,禁用|执行 4 条命令、写 0 B|读 8 帧栈、0 B 内存
10. **断点三轮:CFNetwork 唯一调用点**|断在 `scheduleTimeoutTimer + 152`,放行后两个 `NSURLSession-work` 线程同时命中|执行 6 条命令、写 0 B|读 8 帧栈、0 B 内存
11. **正常态现场取证**|`bt`/`register read`/`po $x0`/`memory read` 坐实对象是 timer 源、`[x0+0x58]` 有效、`ds_refs+0x1d = 0x02`|执行 9 条命令、写 0 B|读 21 个寄存器、2 个对象描述、2 处内存 48 B
12. **注入复现**|不改 x0(保住「x0 有效」这一报告特征),只清 `[x0+0x58]`,回验后继续 → `EXC_BAD_ACCESS (address=0x1d)`|执行 5 条命令、写 1 处 8 B|读 1 处 8 B
13. **崩溃现场记录与清理**|读 `bt 8`、12 个寄存器、PC 附近 6 条指令,与报告逐项对拍后删除 3 个断点|执行 4 条命令、写 0 B|读 6 条反汇编、12 个寄存器、8 帧栈
14. **栈差异复核**|`thread list` + 切线程 `bt 12/20` 补全 11 帧,反汇编 `continueWithCacheLookupResult` block 证明报告那条链本机存在|执行 8 条命令(含 1 次失败)、写 0 B|读 26 条反汇编、21 帧栈

## 结论

对象:`OS_dispatch_source` 的 timer 源 ------ CFNetwork 的连接超时定时器;崩点在读它内部 `ds_refs + 0x1d` 的标志字节。

来源:CFNetwork 内部创建、loader 持有。不可用不是传参坏,是对象内部 `[x0+0x58]` 被清空(框架内 UAF / 生命周期竞态或内存踩踏);本机清掉这 8 字节,崩出同样的 `subcode 0x1d`,复现成立。

单从这一次任务来说,人工参与度低,因为整体上下文紧凑,业务没有割裂,但要把根因查到,还需要工程师结合业务和场景做更多分析。拿到这个结论,就可以继续下一步的排查,看 loader 对于 source 的生命周期管理具体是怎样的,是底层本身的 BUG 还是上层调用 API 干预到了,后续过程很复杂这里不展开了。

可以看到,Agent 全程下发了约 70 条调试命令、阅读约 140 条反汇编指令和 250 条符号,往进程里写了 8 个字节,期间做了大量"经验老到"的决策 ------------ 这些是资深工程师也要折腾很久的机械环节。

skill 提供的流程与纪律让分析没有走弯路:定位 → 触发 → 读现场 → 语义化,四步各自命中;70 条命令零抢跑、零状态错乱。

总结

AI 完成的是动态二进制分析这个环节 ------------ 对象是什么、来源在哪、如何复现。再往上的业务归因、修复决策,仍要工程师拿着这个结论回到业务里继续查。这套工具也不止于崩溃分析:三方 SDK 的黑盒行为确认、系统 API 的运行时验证、偶现问题的现场取证,都是同一套工作流。

在这样的场景,工程师可能不再需要记忆大量的汇编和操作系统固化知识,而是理解原理和方法论。工程师来做核心决策,AI 作为方案贡献者和重复劳动执行者。

相关推荐
Vol火山43 分钟前
从买工具到建能力:数字化部门如何把 AI 变成可复制的生产力
人工智能
莱茶荼菜1 小时前
【slam公式】
人工智能·线性代数·机器学习
Henry-SAP1 小时前
SAP PP中的核心业务流程——离散制造、重复制造、流程制造三大模式解析
前端·数据库·人工智能·制造
Zhao-Jichao1 小时前
AI Prompting for Everyone 课程笔记 - P20 实验与期末项目 Lab and Final Project
前端·人工智能·笔记
敢敢のwings1 小时前
TACDYN-WAM 深度解读:机器人如何预测接触变化,并据此生成动作
人工智能·机器学习·机器人
沉默王二1 小时前
阿里版Codex,再次惊艳到我了。
人工智能·openai·agent
正在走向自律1 小时前
【1/5】人工智能训练师 一级/高级技师 考证学习方向:从“可评审的交付”到“可定义的标准与可传承的体系”
人工智能·人工智能训练师·一级/特级·ai技术负责人·数据体系负责人·行业解决方案架构·技术评审与标准制定
时文鲜读1 小时前
算力基建提速,数据中心安全建设与工业可靠性如何同步升级?
大数据·人工智能·安全
坐吃山猪1 小时前
果蝇AI07-Mac部署踩坑
人工智能·深度学习·机器学习·果蝇ai