
一句话看懂
项目地址:https://github.com/tylertms/Stela

Stela 在 Intel/AMD PC 上直接启动 macOS。它包含 ARM64 到 x86-64 的指令翻译器、NVIDIA GPU 的 Metal 驱动和 Apple Silicon 硬件模型,用 Rust 实现从 UEFI 引导到系统运行的完整链路。当前收获 1367 Star,67 Fork,30 个未关闭 Issue,单日新增 777 Star,代码库 65.9 MB Rust、5.4 MB C、1.8 MB Python。
它解决什么问题
macOS 从 2020 年起转向 Apple Silicon,系统二进制只包含 ARM64 指令。普通 PC 使用 x86-64 处理器无法直接执行,虚拟机需要额外套层,传统 Hackintosh 只能装 Intel 版 macOS。Stela 让 PC 硬件像真实 Mac 一样从 USB 启动,系统看到的是 M2 Mac mini,所有 ARM64 指令在运行时翻译成 x86-64 本地代码。
需要 64 位 UEFI 固件且关闭 Secure Boot,CPU 支持 AVX2,8 GB 以上内存,NVIDIA GTX 16 或 RTX 20/30/40/50 系列显卡,NVMe 或 AHCI SATA 磁盘 60 GB 以上空间。当前不支持 Wi-Fi 和音频,输入设备限于 USB 键鼠、PS/2 设备及部分触摸板。
核心概念速览
arm64e to x86-64 translator:将苹果芯片指令实时转为 PC 指令的编译器。Stela 采用 AOT(Ahead-Of-Time)与 JIT(Just-In-Time)混合策略,覆盖 3000 余种 AArch64 指令编码,映射寄存器到 x86-64 寄存器,缓存翻译后的代码块以提升重复执行速度。
AIR to NIR:Metal 着色器中间表示到 Mesa 中间表示的编译。macOS 图形系统使用 Metal API,着色器编译为 AIR(Apple Intermediate Representation)。Stela 的 Metal 驱动直接将 AIR 编译为 Mesa NIR,支持 Metal3 和 Apple7 GPU 家族规范,最终生成 NVIDIA GPU 可执行代码。
translation helper:翻译块调用的内存访问辅助函数。软件 TLB(Translation Lookaside Buffer)先查找访客虚拟地址到主机物理地址的映射,缺失时遍历访客页表填充 TLB,返回主机可直接访问的物理地址。
bounce slot:不连续页跨越时的两页中转槽。访客访问跨越页边界时,两个物理页在主机内存可能不连续,window 结构的两页叶表项映射访客的两帧,让跨页访问到达连续的主机内存区域。
架构拆解

stela-boot-core 是 UEFI 启动核心,处理硬件拓扑发现、内存管理、中断控制器、页表初始化,完成从物理硬件到虚拟 Apple Silicon 环境的映射。
stela-compiler 实现 ARM64 到 x86-64 的翻译编译器。AOT 阶段静态扫描常见代码路径,JIT 阶段动态翻译新遇到的指令序列。翻译块以基本块为单位,遇到分支、异常或未翻译区域时结束。
stela-driver-gpu 提供 NVIDIA GPU 的 Metal 驱动。接收 Metal 指令流,将 AIR 着色器编译为 Mesa NIR,通过 stela-mesa 子模块与 GPU 交互,模拟 Apple7 GPU 家族特性。
stela-engine 是运行时引擎,包含 MMU、软件 TLB、设备服务。引擎调度翻译器执行代码块,处理页错误、对齐错误、系统调用,维护访客操作系统状态。
stela-cli 是命令行工具,提供 setup、update、boot-log 子命令。setup 制作启动 USB,用户选择设备、安装器、macOS 版本和 Mac 型号后擦除制作,boot-log 导出启动日志用于调试。
关键实现走读
rust
extern "sysv64" fn translate(saved: &mut Saved) -> u64 {
let state = state();
state.follow_native();
state.follow_pan(flags_pan());
let address = saved.rcx;
// SAFETY: the two bytes the stub placed after its CALL, in the arena.
let [bytes, flags] = unsafe { (saved.ret as *const [u8; 2]).read_unaligned() };
let write = flags & WRITE != 0;
let view = state.current as u8;
let current = state.root_of(state.current);
state.helper_calls += 1;
for _ in 0..FILLS {
match state.tlb.lookup(address, u64::from(bytes), write, view) {
Lookup::Direct(host) => return layout::physical(host),
// A page-crossing atomic crosses its 16-byte granule too: the
// engine takes its Alignment fault.
Lookup::Split { .. } if flags & ATOMIC != 0 => {
abandon(state, saved, address, write, FaultCause::InvalidOpcode)
}
做了什么: translate 函数是翻译块的内存访问辅助。翻译后的代码遇到内存访问时调用此函数,传入 rcx 寄存器保存的访客虚拟地址,返回地址后两字节包含访问宽度和读写标志。函数尝试 FILLS 次 TLB 查找,命中时返回主机物理地址,跨页原子访问时触发对齐错误。
为什么这样写: 访客页错误或对齐错误发生时,引擎必须知道哪条访客指令引发错误才能正确设置访客异常上下文。translate 的调用点对应一条访客内存访问指令,返回地址后的两字节编码访问参数,让引擎可以重建指令语义。跨 16 字节粒度的原子访问让引擎抛出对齐错误,因为 x86-64 原子指令保证粒度内原子性,跨粒度访问无法直接映射。
没有它会怎样: 翻译器要为每次内存访问内联 TLB 查找代码,翻译块体积膨胀数倍,指令缓存压力增大。错误处理无法区分访问参数,引擎无法向访客操作系统报告精确错误位置。
rust
fn post(request: &Request) {
let this = ptr::from_ref(request).cast_mut();
let mut head = REQUESTS.load(Ordering::Relaxed);
loop {
request.next.set(head);
match REQUESTS.compare_exchange_weak(head, this, Ordering::Release, Ordering::Relaxed) {
Ok(_) => return,
Err(current) => head = current,
}
}
}
做了什么: post 函数将 GPU 请求无锁插入全局链表头。当前头节点读入 head,新请求的 next 指向它,compare_exchange_weak 原子比较并交换 REQUESTS,成功则返回,失败则重试。
为什么这样写: 非 boot CPU 的 PE 投递调用后自旋等待完成,期间不能持有锁阻塞其他 PE。无锁栈结构让多个 PE 并发投递请求,boot CPU 的调度线程从头取走整条链表处理,不需要对每个请求加锁。
没有它会怎样: 使用互斥锁保护链表,非 boot CPU 投递请求时持锁,boot CPU 取链表时竞争同一把锁,PE 调度退化为串行,系统吞吐量下降。
动手上手

制作启动 USB:
bash
sudo ./stela setup
终端提示选择 USB 设备、macOS 安装器路径、系统版本和 Mac 型号,擦除 USB 后写入引导程序和系统镜像。
从 USB 启动 PC,BIOS/UEFI 设置中关闭 Secure Boot,调整启动顺序将 USB 置顶。重启后进入 macOS 安装界面,按屏幕提示完成安装。
更新 Stela 组件:
bash
sudo ./stela setup update stela
导出启动日志:
bash
sudo ./stela setup boot-log --output log.txt
遇到启动失败时,日志文件包含翻译器、引擎、驱动的详细输出,提交 GitHub Issue 时附上此文件。
从源码构建:
bash
git clone https://github.com/tylertms/Stela
cd Stela
cargo xtask dist
首次构建需编译 Linux、Mesa、LLVM 源码,耗时数小时。构建产物在 target/dist 目录,需要 Rust 工具链、Python 3.10+、LLVM 21-23 或 Xcode Clang。
应用场景
在符合要求的 PC 启动 macOS。准备支持 AVX2 的 Intel/AMD CPU、NVIDIA RTX 显卡、8 GB 以上内存的台式机或笔记本,关闭 Secure Boot 后从 Stela USB 启动。适合想体验 macOS 但没有 Mac 硬件的用户,或需要在多种硬件配置测试软件兼容性的开发者。
调试 Apple Silicon 软件。开发针对 M 系列芯片的应用,在 Stela 提供的 M2 硬件模型上测试,编译 ARM64 二进制后直接运行,观察翻译器性能和系统行为。
研究 ARM64 翻译性能。分析 3000 余种 AArch64 指令编码的翻译策略,观察 AOT 与 JIT 混合编译的缓存命中率,测量不同指令模式的翻译开销。
独立分析

translate 函数实现精确块结束机制。翻译块是连续指令序列的编译单元,遇到分支或异常时必须停止。引擎可以从辅助函数的调用点回溯到对应的访客指令,重建访客 CPU 状态,向访客操作系统报告正确的程序计数器和错误原因。这是虚拟机正确性的基础。
post 函数设计为 round trip 避免占用核心。macOS 在多核 M2 上调度任务,非 boot CPU 的核心执行应用代码时调用 GPU 驱动,驱动投递请求后让出核心继续执行其他代码,boot CPU 的专用线程批量处理 GPU 请求。这种设计让 GPU 调用不阻塞应用核心,多个应用并发渲染时吞吐量高。
Lookup::Split 分支处理跨页原子操作对齐错误。ARM64 允许跨页原子指令但要求对齐到操作数大小,x86-64 原子指令保证 16 字节粒度内原子性但跨粒度行为未定义。翻译器检测到跨页原子访问时放弃翻译,让引擎向访客抛出对齐异常。
与虚拟机方案对比,Stela 直接 UEFI 启动,虚拟机需宿主操作系统调度,理论上直接启动减少一层抽象。与传统 Hackintosh 对比,Stela 需 ARM64 翻译层,传统方案安装 Intel 版 macOS 无需翻译。
局限与风险
项目正在开发中,启动慢且部分硬件可能无法引导。硬件兼容性依赖具体型号,即使满足 AVX2、NVIDIA RTX 要求,不同主板 UEFI 实现差异可能导致启动失败。
当前不支持 Wi-Fi 和音频。网络依赖有线以太网,输入设备限于 USB 键鼠、PS/2 设备及部分触摸板,蓝牙外设和触摸屏不可用。
需要关闭 Secure Boot,这会降低启动链安全性。NVIDIA GPU 要求限制了 AMD Radeon 和 Intel Arc 用户。GPL-2.0 许可证要求分发时提供源码,商业产品集成 Stela 需公开修改部分。
结论卡片

适合人群: 想在 x86-64 PC 尝试 macOS 的技术爱好者,符合硬件要求且接受启动慢和功能缺失。需兼容性测试的开发者,在非 Apple 硬件验证 ARM64 应用行为。对底层 ARM64 翻译感兴趣的研究者。
不适合人群: 需要稳定生产环境的用户,项目开发中启动失败风险高。依赖 Wi-Fi/音频的场景。不符合硬件要求的设备,缺少 AVX2、NVIDIA GPU 或 8 GB 内存无法启动。
后续跟踪: 关注 GitHub Issues 与提交记录,硬件支持范围和驱动完整度会持续改进。创建 2026 年 10 月,最近推送 11 月,更新频率高但未发布稳定版本。