夜雨聆风学习资料网

ARTICLE · 1225791

安卓上真的可以跑ios,写补丁的是 Claude 和 Codex.

安卓上真的可以跑ios,写补丁的是 Claude 和 Codex.

苹果的 iOS,跑在了一台一百多美元的红米上。

屏幕上先滚过一大段内核日志,然后是苹果 logo,然后是 iOS 的锁屏。进设置看「关于本机」,设备型号写着 iPhone X,系统版本 iOS 16.7.16,没有 Home 键,用的是手势。

这台机器是 Redmi Note 12 4G,芯片是骁龙 685。

而这套东西的补丁,主要是 Claude 和 Codex 写的。

但它现在只用了八个核里的四个。

这回滚的是内核日志

安卓上跑 iOS,过去有三种做法。

主题包换的是图标和壁纸,系统还是安卓;模拟器在虚拟机里跑另一个系统,性能砍半还卡;云手机是把远处的画面传过来,本地什么都没有。

这三种做法有个共同点:iOS 从来没碰过这台手机的硬件。

这次不一样。跑起来的是苹果自己开源的内核 XNU(xnu-8796),编出来之后从内存里启动,直接接管了这台红米的 CPU、内存和存储。上面那层是未经修改的 iPhone X 用户态,iOS 16.7.16(20H392)的 launchd、系统守护进程、backboardd、SpringBoard,一个都没改。

启动链在项目 README 里写得很清楚:

原厂小米 ABL(已解锁)
  → fastboot boot shim-boot.img(boot image v4,只在内存里)
     → Silver 核上的 shim:打印 MMFR0/MIDR,唤醒 Gold 核 0x100,自己停住
        → Gold 核上的 shim:配 GIC、探测速度、在 _end 之后找到 payload
           → XNU + Apple Device Tree + boot-args
              → XNU → UFS 驱动 → userdata 里的 HFS+ 根
                 → dyld + 共享缓存 → launchd → backboardd → SpringBoard

内核自己写了 UFS 3.0 的主机驱动,把 userdata 分区格式化成日志式 HFS+ 当作 iOS 的根分区,读写都通。显示走的是内核的 IOSurface 加 IOMobileFramebuffer,MDP 直接从 4 GiB 以下的连续内存里扫描画面,双缓冲,还做了真正的 cache 维护。

内核日志这东西骗不了人。主题包不会打出 ID_AA64MMFR0,模拟器不会去唤醒 PSCI 的 CPU_ON。

它把自己说成一台 iPhone X

iOS 不是随便什么硬件都认的。它要一份 Apple Device Tree,要一串身份信息。

项目用 tools/mkadt.py 生成了一份描述红米硬件的设备树,但把身份那一栏填成了 iPhone X(iPhone10,3 / D22AP)。所以系统醒来之后,认为自己在跑的是一台 2017 年的苹果手机。

屏幕这里有个不得不做的妥协。iPhone X 的 canvas 是 1125×2436,红米这块面板是 1080×2400。项目按 iPhone X 的尺寸向 iOS 报画布,实际输出时居中缩放到红米的面板上。iOS 以为自己占满了整块屏,其实四周被让掉了一点。

没有 Home 键这件事也是照 iPhone X 来的,所以整个 UI 是手势操作。设置里的「关于本机」那一页,型号、系统版本、序列号格式,全都跟着变了。

页大小判的死刑

这是整件事里最硬的一段。

骁龙 685(SM6225)是 4+4:四个 Kryo 260 Silver(Cortex-A53 那一档,MPIDR 0x0–0x3),四个 Kryo 260 Gold(Cortex-A73 那一档,0x100–0x103)。

README 里有一句原话:

Only Gold has the 16K granule.

翻译过来就是:只有 Gold 核支持 16K 页。

而 iOS 的 XNU,内核和用户态全都跑在 16K 页上。这不是配置项,是这套系统从底层就这么设计的。

麻烦在于小米的 ABL 引导程序偏偏从 Silver 核启动。Silver 核的 ID_AA64MMFR0.TGran16 = 0,它根本翻译不了 16K 页。

于是 shim 做了一件事:用 PSCI 的 CPU_ON 把 Gold 核 0x100 叫醒,让 Silver 核停在 wfi 指令上睡过去。XNU 从此只在这四个 Gold 核上跑。另外四个核不是闲着了,是物理上参与不了。

要把八个核都用上,得把 XNU 编成 4K 页。项目把这条列进了 roadmap,但后面跟了一句风险:iOS 16 的用户态里,malloc、JIT 的 guard page、IOSurface、commpage 布局,全都硬编码了 16K。

一个页大小,判了四个核的死刑。

一路撞过去的四堵墙

README 里有一张表,叫「topaz 和 sweet 有什么不同」,每一行都是一堵撞过的墙,而且写明了撞上去会怎样:没处理好,启动会在没有任何提示的情况下卡死。

第一堵是引导。小米给 topaz 写的 ABL 不理 boot image v1 和 v2,还会把 boot v4 的 ramdisk 丢掉。解法是把 shim 打成 boot v4,再把 XNU 的 payload 附在 shim 内核镜像的 _end 之后。

第二堵是那四个睡着的核,上面说过了。

第三堵最麻烦:这颗芯片是 ARMv8.0,没有 LSE 原子指令,没有 RCpc(ldapr),也没有 PAN。而苹果的 clang 即使给了 -march,在 -arch arm64 下仍然保留 apple-m1 的默认值。解法是内核和 libfirehose_kernel 都用 -mcpu=cortex-a53 编,PAN 关掉,commpage 里那个 casa 改成一对 LDAXR/STXR 循环。

可 iPhone X 的用户态里还留着 LSE 原子和 ldapr,那些代码是苹果编译的,改不了。项目只能在内核的 handle_uncategorized 里做原子指令模拟。

第四堵是中断。XNU 在 Gold 核上跑完 initCPU 之后直接挂住,因为既没有 SGI 也没有定时器中断。解法是 shim 在跳转之前就把 GIC 给所有 Gold 核配好:GICD 分组、redistributor 唤醒、把 SGI 和 PPI 放进 Group 1。

这四堵墙全是不看源码就想不到的地方。项目为了它们打了一百三十多个补丁,编号从 0080 开始,一个一个往上加。

四堵墙之外还有一堆小坑,全都有编号。项目从上游继承了一批补丁(0001 到 0079),自己又从 0080 开始往下加,一路加到了一百三十多号。一个坑一个编号,改什么、为什么改,都写在编号里。

最典型的是 0087。它修的是触屏,问题是 QUP0 SE2 那一路的时钟 RCG 配错了,配错的后果不是触屏不灵,是手机直接挂住。这种 bug 最难查,因为现象跟原因隔着好几层。

还有 0088,加了 LUN0 GPT 的看门狗;0091 做电池电量计和充电状态;0092 到 0103 在做一件很底层的事:让 MDP 直接从连续内存里扫描 iOS 的 surface,配上写合并的控制台、双缓冲的帧,还有真实的 dcache 维护。上游那套代码里,这项 cache 维护在苹果的 SoC 配置下是个空操作,搬到高通上必须补回来。

0113 到 0130 是 Wi-Fi,十八个补丁。0127 和 0128 修的是一个特别隐蔽的问题:IOKit 一直不进入静止状态,wifid 把整个启动卡住。最后加了个 sweetbusy=N 才绕过去。

一百多个补丁,就是一百多个撞过的墙。

谁在写这些补丁

上面的四堵墙,是人和 AI 一起翻过去的。

项目根目录有一份 AGENTS.md,1 KB,是给 AI 的工作守则。五条,每一条都值得抄下来:

第一条,动手之前先读 NOTES.md,里面有项目目标、硬件事实、当前卡在哪、本地文件放哪儿、下一步具体做什么。

第二条最直白:「用户会频繁切换 agent。」所以每过一个里程碑、每次停下之前,都要把当前的交接内容更新进 NOTES.md,记下提交号、CI 运行号、确切的编译和启动结果、以及没做完的活。

第三条是给 AI 的纪律:「区分观察和猜测。」

第四条讲流程:用 gh 跑 GitHub Actions 编译内核,本地先做单文件交叉编译,别浪费一次 CI。

第五条是边界:设备转储、序列号、下载的苹果文件都不许进 git。

NOTES.md 现在有 175 KB。打开第一行是「Agent handoff / active work」,往下全是按时间倒序排的交接记录。里面能看到具体的分工痕迹:Codex task 8 是 RPMh 客户端,Codex task 11 是用 PSCI 做 SMP,drivers by Codex,还有一堆按任务分目录的报告。

最说明问题的是这一句,写在手机工作流的小标题下面:

Phone workflow (user does buttons, Claude runs commands)

用户按按键,Claude 跑命令。 这是整件事里人和机器的真实分工:刷机、进 fastboot、按电源加音量键这些动作归人;读寄存器、写驱动、跑 CI、拉日志归 AI。

另一个仓库 topaz-woa 更进一步。它的 docs/ 下有一份 AGENT_BRIEF_drivers.md,开头直接写「你负责 drivers/ 里的 Windows 驱动,主 agent 负责 UEFI、启动链、分区、刷机和启动槽」,旁边配着 HANDOFF_p8_gpu.md、HANDOFF_p9_audio.md、HANDOFF_wifi_windows.md 一整套交接文档。

所以「靠 AI 移植」这句话不算错,但它容易让人想歪。没有哪一次是 AI 从头到尾自己搞定的。真实过程是:人先判断哪儿不对,把上下文写进交接文档,AI 去读寄存器、改补丁、跑编译,跑完人再上手试。

175 KB 的交接文档,就是这套协作的账本。

调这个系统,改的是一串启动参数

内核编出来只是第一步,让它真的跑起来,还要喂一串 boot-args。项目列了一张表,每一项都对应一个具体的坑:

rd=ufsroot0 ufsrw=1 rootrw=1 把根分区指到 userdata 并且可写;cpus=4 是四个 Gold 核全开,而 cpus=1 是保守回退,跑不动了就退到单核;sweetfb_noconsole=1 让第一帧 iOS 画面之后不再往屏幕上画内核文本,不加这一项,每一行日志都要 CPU 画一遍再滚一遍,慢到没法用;sweetfb_hz=30 是 CPU 拷贝帧的刷新率;sweettouch=0 关掉触屏驱动。

还有一项值得单独说:sweethw=1。它会让上游留下的 SM7150 按键和 USB 驱动也启动,在 topaz 上不安全,因为那套驱动根本没移植过来。

最后两项是 launchd_unsecure_cache=1 和 wdt=-1 wdt_test=no_user_checkin,作用是让 launchd 绕过 AMFI,同时关掉硬件看门狗。这套东西的底色就在这儿:它把苹果的安全模型关掉了大半,才换来能开机。

四堵墙之外还有一堆小坑,全都有编号。项目从上游继承了一批补丁(0001 到 0079),自己又从 0080 开始往下加,一路加到了一百三十多号。一个坑一个编号,改什么、为什么改,都写在编号里。

最典型的是 0087。它修的是触屏,问题是 QUP0 SE2 那一路的时钟 RCG 配错了,配错的后果不是触屏不灵,是手机直接挂住。这种 bug 最难查,因为现象跟原因隔着好几层。

还有 0088,加了 LUN0 GPT 的看门狗;0091 做电池电量计和充电状态;0092 到 0103 在做一件很底层的事:让 MDP 直接从连续内存里扫描 iOS 的 surface,配上写合并的控制台、双缓冲的帧,还有真实的 dcache 维护。上游那套代码里,这项 cache 维护在苹果的 SoC 配置下是个空操作,搬到高通上必须补回来。

0113 到 0130 是 Wi-Fi,十八个补丁。0127 和 0128 修的是一个特别隐蔽的问题:IOKit 一直不进入静止状态,wifid 把整个启动卡住。最后加了个 sweetbusy=N 才绕过去。

一百多个补丁,就是一百多个撞过的墙。

上游开源之后,另一个机型亮了

这个项目不是从零开始的。

它的上游叫 ios-sweet,做的是同一件事,机型是红米 Note 10 Pro(SM7150)。README 里那句致谢写得很客气:「它做了最难的那部分。」

时间线放在一起看:

ios-sweet(Note 10 Pro,SM7150)      2026-10-14 建仓
  └→ ios-topaz(Note 12 4G,SM6225)  2026-10-18
       ├→ ios-x676c(Infinix,联发科 MT6789)  2026-10-20
       └→ GPU 补丁来自 HackiOS(POCO X5 Pro)

上游建仓到下游点亮,中间只隔了 4 天。 再两天,第三个机型出现,而且换到了联发科的芯片上。

四个机型,三个芯片平台。十来天。

还有一件事更能说明这台手机现在是什么状态:同一个人的另一个仓库 topaz-woa,做的是这台红米上的 Windows on ARM,UEFI 补丁、ACPI、驱动,9 月 30 日就建仓了。

一台一百多美元的中端安卓机,一拨人在上面跑 iOS,一拨人在上面跑 Windows。

几个仓库的体量也能说明问题。ios-topaz 476 星、108 个 fork;上游 ios-sweet 828 星、368 个 fork。fork 和 star 的比例分别是 23% 和 44%,这个比例远高于普通项目。点星是收藏,fork 是「我要自己也来一遍」。

而两个仓库都没有许可证。GitHub 上显示 None,意思是默认保留所有权利,不是公共领域。

点亮之后还剩什么

这段清单是一点点补上来的。当时触屏和实体按键都还没好。视频发出一个多小时后,他补了一组照片:手机停在设置页里,触屏能滑、按键能按。再往后两天,Wi-Fi 通了。

这个顺序值得记一下,因为它说明这个项目现在是什么状态:它不是一个成品,是一条每天往前挪一点的进度线。你今天看到的能用清单,明天可能多一行;你在别处读到的「不能用」,可能只是那边的信息停在 3 天前。

截至 10 月 20 日,这台机器上能用的是这些:

触屏(FocalTech FT5452)、音量键、电源键、电池电量与充电状态、显示、SpringBoard 和主屏、设置。

还有一个很多人以为还不行的:Wi-Fi。项目自己写了 TopazModem 这套驱动,从手机原厂的 modem_a 分区经 TrustZone 启动调制解调器固件,走 GLINK/QRTR、WLFW、HTC/WMI/HTT,扫描、关联、WPA2-PSK 的四次握手直接在内核里做完。iOS 那边通过 wifid 看到它,设置里能列出附近的网络、能连上、密码存进钥匙串、重启之后自动重连,Safari 能通过 HTTPS 打开网页。

用不了的是这些:

蓝牙、蜂窝数据、音频、相机、传感器、GPS、SEP。bluetoothd 和 fairplayd.H2 因为对应的硬件不存在,在后台不停重启。这个仓库里也没有 GPU,CoreAnimation 全靠 CPU 渲染,帧率很低,Safari 打开是空白页,因为 WebKit 要 GPU。

还有几个细节:没有实时时钟,日期从 12 月 31 日开始;开机大约 3 分钟;八个核只用四个;安全模型基本关掉,AMFI 和沙箱都是宽松的,AppleKeyStore 是个 stub,没有数据保护。

GPU 其实在手机上已经能画锁屏和主屏了,但那部分补丁来自另一个项目 HackiOS(把 iOS 16 移植到 POCO X5 Pro 的那位作者私下分享的代码),要等对方开源才会并进这个仓库。

所以它点亮了,但它不是一台手机。

有两处得照实说

第一,这个仓库没有许可证。GitHub 上显示的是 None,没有 LICENSE 文件意味着默认保留所有权利,不是随便拿去用。

第二,README 里有一处对不上。它写的是「不写 boot 分区,内核用 fastboot boot 从内存启动」,听起来很安全。但第三方报道指出,作者自己的 recovery 笔记里,iOS 的启动镜像就在 boot_b 槽里,还有重建分区表的步骤,因为那台手机一度完全开不了机。

这两处说法不一致,所以别人的手机到底承担多大风险,没人能替它保证。

顺带说个实操层面的事。内核是从内存启动的,userdata 之外的分区理论上没动,所以把 userdata 重新格回安卓的文件系统,手机应该还能回安卓。但这个「应该」取决于分区表有没有被改过,而那正是上面那处对不上的地方。想动手的人,先把这两件事弄明白再谈刷不刷。

README 末尾还有一张 roadmap,写的是接下来要做什么:GPU 驱动收尾、把 GIC 的修复收窄到真正相关的寄存器再搬进 XNU、从 PMIC 把实时时钟接进来、让八个核都能用、给 6GB 版本做适配(现在内存布局是写死 4GB 的),还有扬声器和 SIM 卡注册。

有些条目后面跟着风险提示。比如八核那一条,作者自己写了:iOS 16 的用户态只在 16K 页上跑过,要改成 4K 页,得把 malloc、JIT、IOSurface、commpage 里的假设一条条查过去。

至于「以后不用买 iPhone」,现在说这话太早。它连蓝牙都没有,开机要三分钟,八个核只醒四个。但它把一条路给蹚出来了:从红米 Note 10 Pro 到 Note 12 4G 用了 4 天,到联发科的 Infinix 又用了 2 天。这条路以前只有两个机型,现在有一张写着每堵墙怎么翻的表。表还在变长,而它上一个版本,只存在于 4 天前的记忆里。

相关学习资料