

一句话看懂
项目地址:https://github.com/Acelogic/Snapintosh
Snapintosh 让搭载高通 Snapdragon X2 Elite 芯片的华硕 Zenbook A16 跑起 macOS 27 桌面。它不是虚拟机,而是运行在 ARM EL2(CPU 特权等级中介于固件和操作系统之间的一层)的监控器,直接在裸机上接管硬件,把 Apple Silicon 平台的调用契约在非 Apple 芯片上重新实现,骗过未经修改的 macOS 内核。项目当日新增 451 星,目前共 542 星,Fork 32 次,主语言 Python,但核心代码以 C、Objective-C、C++、Assembly、Swift 为主。
它解决什么问题
macOS 只为 Apple 自家芯片编译,内核启动时会调用只存在于 Apple Silicon 上的底层接口,比如 SPTM(安全页表监视器)和 TXM(信任执行监视器)。普通 ARM 笔记本没有这些接口,内核一启动就会死机。Snapintosh 在 EL2 层伪造这些接口,让内核镜像以为仍运行在真正的 Apple 硬件上。
macOS 需要的存储控制器 ANS2、中断控制器 AIC、内存管理单元 DART 都是 Apple 自研模块,骁龙平台不存在对应硬件,Snapintosh 用软件模拟这些设备的寄存器行为。GPU 方面更棘手,macOS 图形渲染走 Metal,设备上的显卡是高通 Adreno X2,两者着色器指令集完全不同,项目为此写了运行时编译器,把 Metal 中间代码翻译成 Adreno 能执行的指令。
这个项目解决跨芯片架构的操作系统移植问题,在没有源码、不修改内核二进制的前提下,靠底层模拟层把一套硬件伪装成另一套。
核心概念速览
EL2 monitor:ARM 架构把 CPU 特权分成几个 Exception Level,EL2 通常跑虚拟化监控器。Snapintosh 的 monitor 介于固件代理 q1n1 和运行在 EL1 的 macOS 内核之间,拦截并响应内核发出的平台调用,向上呈现完整的 Apple Silicon 契约。
SPTM/TXM:负责内存安全和代码签名校验的子系统,正常由 Apple 芯片固件实现。Snapintosh 的 monitor 模拟这些调用的返回行为,让内核在做安全检查时拿到预期结果。
16 KiB 到 4 KiB 影子页表:XNU 内核按 16 KiB 为单位管理内存页,但骁龙 X2 的硬件 MMU 只认 4 KiB 页。shadow 目录的代码做页表翻译,把内核的 16 KiB 页映射转换成硬件实际认可的 4 KiB 页表项。
air2ir3:着色器编译器,把 Apple 的中间表示 AIR 转换成 Mesa 项目 ir3 后端认识的指令,ir3 后端是给 Adreno GPU 系列写的开源驱动组件。有了它,WindowServer 调用 Metal 画界面时,着色器才能在 Adreno X2 上跑起来。
架构拆解

系统分五个模块:compat、shadow、gpu/adreno-kext、gpu/air2ir3、drivers。
compat 是监控器核心,实现 SPTM/TXM 调用契约、CPU 和中断模拟、设备模型,还要在 GPU 指令环和 SMMU 映射之间做中介,同时提供 NVMe 存储后端支持。
shadow 单独处理页表翻译,页表遍历器要正确处理内核各种粒度的内存映射请求,再落到硬件认可的 4 KiB 页表项。
gpu/adreno-kext 放了两块代码,kext 子目录是内核扩展 AdrenoX2,plugin 子目录是用户空间加载的 AdrenoMetal.bundle,两者分别负责内核态和用户态的 GPU 操作。
gpu/air2ir3 是独立编译器工程,依赖 Mesa 的 ir3 后端完成最终指令生成,靠编译产物对接。
drivers 目录是几个体量较小的 kext,SnapI2CHID 管键盘触控板,snap-qcc2072 管 Wi-Fi 芯片,还有一个启动阶段的帧缓冲驱动。
调用链是:固件把机器切到 EL2,q1n1(Asahi Linux m1n1 代理协议的 fork 版本)接管这一层,通过 USB-C 的 NCM 网络协议和 Mac 主机通信。Mac 主机上的 cycle.py 把编译好的监控器镜像传过去,monitor 启动后调用影子页表模块做地址翻译,同时中介 GPU 指令提交,转发给 AdrenoX2 kext,kext 再和用户态的 AdrenoMetal.bundle 配合,后者调用 air2ir3 完成着色器编译。
关键实现走读
看两段代码,SPTM 类型转换和 GPU 参数校验,对应监控器的两个核心职责。
第一段来自 compat/sptm.c:
c
int sptm_mac_apple_retype(uint64_t old_type,uint64_t new_type)
{
static const uint64_t words[48]={
[11]=0x8000042ee3f7c802UL,[14]=0x800,[15]=0x800,[16]=0x800,[17]=0x800,
[18]=0x80800,[19]=0x800,[20]=0x800,[21]=0x800,[24]=0x800,[25]=0x800,
[32]=0x800,[33]=0x800,[34]=0x800,[35]=0x800000800UL,[37]=0x800,[41]=0x800,
[42]=0x1802000000000000UL};
return old_type<48 && new_type<64 && ((words[old_type]>>new_type)&1);
}
这是内存页类型转换的合法性检查。XNU 内核给每一页标注类型(内核专用、用户可写、设备映射等),转换类型前要先问 SPTM 是否允许。这里用位图表示所有合法转换路径,old_type 是行索引,new_type 是列索引,查表取一个比特就知道允许不允许。表里的数字是从真实 Apple 芯片的 SPTM 固件行为逆向推导出来的。用位运算查表而不是写一长串 if-else,是性能和代码体积的权衡。没有它,内核做内存保护升级或降级操作时会立刻被拒绝,整个启动流程会在内存初始化阶段卡死。
第二段来自 compat/adreno.c:
c
int adreno_adopt(const struct adreno_ops *o, const struct adreno_params *p,
struct adreno_state *s, uint64_t mem_low, uint64_t mem_high)
{
if (s->quiesce) return ADRENO_E_PARAMS;
uint64_t end = p->arena_base + p->arena_size, root = p->cb0_ttbr0 & ADRENO_PTE_ADDR;
const uint64_t in[4][2] = {{p->pool_pa, ADRENO_POOL_BYTES}, {p->ring_pa, p->ring_dwords * 4},
{p->fence_pa, 4}, {root, 0x1000}};
int e = ADRENO_E_PARAMS;
if ((p->flags & ~(uint64_t)ADRENO_F_ALL) || !(p->flags & ADRENO_F_ENABLE) ||
(p->gicd & 0xffff) || !p->arena_base || ((p->arena_base | p->arena_size | p->pool_pa) & 0xfff) ||
end < p->arena_base || (p->arena_base < mem_high && mem_low < end) ||
(p->fence_pa & 3) || (p->fence_va >> 30) || p->ring_wptr >= p->ring_dwords || p->ring_dwords > 0x2000 ||
(root >= p->pool_pa && root < p->pool_pa + ADRENO_POOL_BYTES))
goto out;
这是 GPU 初始化时的参数校验。adreno_adopt 在接手 GPU 控制权之前,确认传入的内存布局参数正确,内存池地址、指令环大小、fence 同步槽地址、页表根地址,每一项都要检查对齐、范围和相互重叠。GPU 操作物理内存地址,一旦某个地址算错或和已有内存区域重叠,轻则渲染花屏,重则写坏内核数据导致系统崩溃。这里把所有检查条件压缩进一个 if 判断,arena 结束地址不能小于起始地址,fence 地址必须 4 字节对齐,ring 队列不能超过 0x2000 大小,页表根地址不能落在内存池区间内。如果跳过检查直接初始化 GPU,上层传入参数有误时 GPU 就会按错误地址读写显存,结果是画面错乱或整机死机。
动手上手

项目本身不提供可直接运行的二进制,固件和系统镜像需要用户自己准备。README 给出的前置步骤是下载 IPSW 固件包并解包:
bash
python3 tools/fetch-ipsw.py <url> [destination]
python3 tools/extract-firmware.py <ipsw> j700ap
第一条命令从指定链接下载 IPSW 镜像包,第二条命令从 IPSW 里把 j700ap 设备标识对应的固件组件抽取出来,J700 是该机型对应的内部代号。
接下来搭建 GPU 编译器开发环境,air2ir3 依赖 Mesa 的 ir3 后端,需要 Homebrew 安装 LLVM、Meson、Ninja 工具链,再用项目自带的 mesa-setup.sh 拉取并打补丁 Mesa 源码。
完整的开机流程还需要 Mac 主机通过 USB-C 连接 A16 笔记本,由 q1n1 建立 USB NCM 网络链路,再用 tools/cycle.py 把编译好的监控器和 kext 上传到设备。
应用场景
第一类场景是在 A16 上启动 macOS 桌面。操作方式是用 Mac 主机通过 USB-C 连接设备,cycle.py 上传监控器和各个 kext,q1n1 负责把 XNU 内核启动起来。这条路径目前能跑到桌面并且 GPU 加速生效,但交互谈不上流畅,而且必须接 AC 电源。
第二类场景是研究 Apple Silicon 的兼容性。研究者可以观察 SPTM/TXM 契约具体是怎么被模拟出来的,以及各驱动模块的实际行为,用来理解 Apple 平台底层机制。这套环境仅限于这一台特定型号硬件。
第三类场景是调试 Metal 着色器的跨平台编译。air2ir3 在运行时把 AIR 中间代码翻译成 Mesa ir3 后端认识的指令,适合研究图形编译器实现的人参考。
独立分析

这个项目值得关注的地方在于它选择的实现路径,在裸机 EL2 层直接接管硬件,而不是走虚拟化中间层。对比常见的 QEMU 虚拟化方案,Snapintosh 没有 CPU 软件渲染这条退路,GPU 加速完全依赖对 Adreno X2 硬件寄存器的直接操作,调试难度明显更高,但换来的是真机性能而不是虚拟化损耗。
跟 Asahi Linux 的 m1n1 比较会更有意思。q1n1 本身就是 m1n1 的 fork,提供同样的 proxy 协议,但服务对象从 Apple Silicon 变成了骁龙 X2,说明底层调试代理协议本身具备跨芯片移植的可能性。项目还提到参考了 jprx 的 qemu-sptm 工程来获取真实 SPTM/TXM 行为的 ground truth,这是典型的逆向工程方法,先在受控环境观察真实硬件的响应,再在目标硬件上重建同样的响应。
当日新增 451 星对一个刚创建没几天、仅支持单一型号笔记本的实验性项目来说是相当高的关注度,这背后大概率是硬件改机和黑苹果社区的传统关注力在起作用。
局限与风险
硬件适配范围极窄,只支持华硕 Zenbook A16 这一个具体型号,X2E-96-100,寄存器地址和时钟参数都是针对这台机器专门调校的。
功能缺口不小,音频、摄像头、蓝牙、睡眠、外接显示这几项都还没实现,目前只能当一台没有声音也不能休眠的桌面机使用。
交互流畅度没有达到稳定的目标帧率,README 和状态文档都提到桌面能跑、GPU 加速能跑,但流畅度还在打磨阶段。
启动方式依赖外部设备,必须有一台 Mac 主机通过 USB-C 连接才能把监控器和内核传上去,没有独立引导能力。
项目本身不含任何 Apple、高通、华硕的固件和系统镜像,用户需要自己从正规渠道获取 IPSW 文件和设备固件,这一步法律和合规风险需要使用者自己把握。
结论卡片

适合谁:对 ARM 架构下的 macOS 兼容性感兴趣的研究者,愿意折腾实验性硬件项目的技术爱好者,刚好手头有这台特定型号华硕笔记本的人。
不适合谁:需要稳定日常使用 macOS 的用户,生产环境场景,没有 Mac 主机或者用的是其他型号硬件的人。
跟进建议:这是一个技术深度很高但受众很窄的实验项目,需要自备 IPSW 和固件才能跑,功能还有明显缺口,适合先看 docs/STATUS.md 了解当前实现到哪一步,再决定是否投入时间配置环境。