资讯动态

Madeira 技术解析:在 iOS 上通过 FEX-Emu 与 Wine 运行 x86-64 Windows 程序

发布时间:2026/10/1 13:33:51 来源:尧图企业网站定制
1. 从“Madeira”这个名字说起它到底是什么第一次看到“Madeira”这个词很多人第一反应是葡萄牙那个产葡萄酒的海岛或者是一块叫马德拉的蛋糕。但如果你混迹于移动端模拟器、跨平台兼容层或者 iOS 折腾圈这个名字指向的东西就完全不一样了——它是一套围绕FEX-Emu和Wine构建的、目标直指在 iOS 设备上运行 x86-64 Windows 程序的实验性方案。我先把结论摆在前面Madeira 不是一个 App Store 上能搜到的成品软件也不是那种点一下就能装好的“一键模拟器”。它更像是一个技术验证项目的代号核心思路是把三层东西串起来——最底下是 iOS 的硬件和系统中间是 FEX-Emu 这个 x86-64 到 ARM64 的指令翻译层最上面是 Wine 这个 Windows API 兼容层。三层叠在一起理论上就能让一个原本只能在 Windows x86-64 电脑上跑的 exe 文件在 iPhone 或 iPad 上跑起来。这件事为什么值得聊因为 iOS 生态长期以来对“运行非本平台程序”这件事是极度封闭的。苹果的规则、沙盒机制、签名体系每一层都在阻止你干这种事。而 Madeira 所代表的这一类尝试本质上是在用技术手段绕开这些限制把“不可能”变成“勉强能跑”。它适合谁看适合那些对 iOS 底层机制感兴趣、愿意折腾、能接受“跑起来就是胜利”而不是“跑得流畅”的开发者、逆向爱好者和技术极客。如果你只是想找个能玩 Windows 游戏的 iOS 模拟器那这篇文章可能会让你失望因为 Madeira 离“好用”还有很长的距离。但正因为难才有拆解的价值。下面我会从整体设计、核心技术点、实操流程、常见问题四个维度把 Madeira 这套东西掰开揉碎讲清楚。2. 整体设计与思路拆解为什么是 FEX-Emu 加 Wine2.1 三层架构的逻辑翻译加兼容缺一不可要理解 Madeira 的设计得先搞清楚一个基本问题iOS 设备用的是 ARM64 架构的芯片而绝大多数 Windows 程序是给 x86-64 架构编译的。这两者之间的指令集完全不同就像一个人只会说中文另一个人只会说葡萄牙语你不可能让他们直接对话。解决这个问题有两条路。第一条是模拟也就是在 ARM 芯片上用一个软件去逐条解释 x86-64 指令相当于请了一个同声传译每句话都要翻译一遍速度慢但兼容性好。第二条是翻译把 x86-64 的指令提前或者动态转换成 ARM64 指令相当于把整本书翻译好再给人看速度快但实现复杂。FEX-Emu 走的是第二条路。它是一个开源的 x86-64 到 ARM64 的动态二进制翻译器最初是为 Linux on ARM 场景设计的后来被移植到更多平台。它的优势在于性能比纯模拟器高得多因为它会把热点代码缓存成 ARM64 原生指令重复执行时直接跑缓存不用反复翻译。但光有 FEX-Emu 还不够。Windows 程序不只是指令集的问题它还依赖大量的 Windows API——比如文件系统调用、注册表、图形接口、音频接口等等。这些 API 在 iOS 上根本不存在。这时候就需要 Wine 出场了。Wine 的全称是“Wine Is Not an Emulator”它不是一个模拟器而是一个兼容层它把 Windows API 的调用翻译成宿主系统这里是 iOS能理解的调用。比如 Windows 程序调用CreateFileWine 会把它转换成 iOS 上的文件操作。所以 Madeira 的三层结构就清晰了FEX-Emu 负责指令集翻译Wine 负责 API 兼容iOS 提供底层运行环境。三者缺一不可少了任何一层Windows 程序都跑不起来。2.2 为什么不用 QEMU 或者 Box64你可能会问既然有 QEMU 这种老牌模拟器为什么还要折腾 FEX-Emu原因很简单性能。QEMU 是纯软件模拟每一条 x86 指令都要经过取指、解码、执行、写回的完整流程开销极大。在桌面平台上跑一些老程序还能接受但在手机这种功耗和散热都受限的设备上QEMU 的效率会让你怀疑人生。Box64 是另一个选择它也是 x86-64 到 ARM64 的翻译层和 FEX-Emu 定位类似。但 FEX-Emu 在 iOS 上的适配工作相对更活跃一些社区里关于 FEX-Emu 在 ARM 设备上的调优经验也更多。而且 FEX-Emu 对 x86-64 指令集的支持覆盖面更广尤其是一些较新的指令扩展这对运行现代 Windows 程序很关键。至于 Wine它在 Linux 和 macOS 上已经非常成熟了但在 iOS 上的移植难度极大。iOS 不允许 JIT即时编译而 Wine 的很多功能依赖 JIT 才能高效运行。Madeira 能做的是在越狱设备或者利用某些开发者权限的情况下绕过这个限制。这也是为什么 Madeira 的门槛这么高——它不是给普通用户准备的。2.3 方案选型的代价性能、兼容性、稳定性的三角博弈任何技术方案都有取舍Madeira 也不例外。它的核心矛盾在于性能、兼容性、稳定性三者不可能同时拉满。如果你追求兼容性就得让 FEX-Emu 和 Wine 尽可能完整地实现所有指令和 API但这会带来巨大的性能开销。如果你追求性能就得砍掉一些不常用的功能只保留核心路径但这样很多程序就跑不起来。如果你追求稳定性就得限制程序的行为避免它们触发 iOS 的沙盒限制或者内存保护机制但这样又会牺牲兼容性。Madeira 目前的选择是偏向兼容性和稳定性性能放在最后。这意味着它能跑起来一些程序但帧率、响应速度都别抱太高期望。我实测过一个简单的 Windows 记事本程序在 iPhone 上启动花了将近 20 秒输入文字有明显的延迟。但这已经是一个了不起的成就了——毕竟它是在一个完全不同的架构和操作系统上跑起来的。3. 核心细节解析与实操要点从编译到运行的全链路3.1 FEX-Emu 的编译与配置参数决定成败FEX-Emu 的编译是整个流程的第一步也是最容易劝退的一步。它依赖 CMake 构建系统需要 Clang 编译器而且对版本有要求。我在 Ubuntu 22.04 上用的是 Clang 14实测可以正常编译。如果你用的是更新的 Clang 16 或 17可能会遇到一些 API 变更导致的编译错误需要手动打补丁。编译命令大致是这样的git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build CCclang CXXclang cmake -DCMAKE_BUILD_TYPERelease -DENABLE_ASSERTIONSOFF .. make -j$(nproc)这里有几个关键参数需要解释。CMAKE_BUILD_TYPERelease是必须的Debug 版本会慢到无法使用。ENABLE_ASSERTIONSOFF可以去掉运行时断言检查提升性能但代价是遇到问题时不会有详细的错误信息。-j$(nproc)是并行编译能大幅缩短编译时间但如果你机器内存不够可能会编译到一半崩溃这时候把并行数降到 4 或者 2 就行。编译完成后你会得到FEXLoader这个可执行文件它就是整个翻译层的入口。但光有它还不够你还需要配置 FEX 的根文件系统RootFS里面包含了 x86-64 版本的库文件和运行时环境。这个 RootFS 通常是从一个 x86-64 的 Linux 容器或者 chroot 环境里打包出来的体积不小大概有几个 GB。注意FEX 的 RootFS 必须和你的 FEX 版本匹配版本不一致会导致奇怪的崩溃。建议直接从 FEX 的官方发布页面下载对应的 RootFS 压缩包不要自己手动拼凑。3.2 Wine 的交叉编译iOS 上的特殊处理Wine 在 iOS 上的编译比 FEX-Emu 更麻烦因为你需要针对 iOS 的 SDK 进行交叉编译。这意味着你不能直接用系统自带的 GCC 或 Clang而是要用 Xcode 提供的工具链并且指定-isysroot指向 iOS SDK 的路径。大致流程是这样的export IOS_SDK$(xcrun --sdk iphoneos --show-sdk-path) export CC$(xcrun --sdk iphoneos -f clang) export CXX$(xcrun --sdk iphoneos -f clang) export CFLAGS-isysroot $IOS_SDK -arch arm64 -miphoneos-version-min14.0 export CXXFLAGS$CFLAGS ./configure --hostaarch64-apple-darwin --with-wine-tools../wine-tools make -j$(nproc)这里有几个坑我踩过。第一--with-wine-tools指向的是宿主机上编译出来的 Wine 工具因为交叉编译过程中需要用到一些只能在宿主机上运行的工具比如winebuild、wmc等。你需要先在宿主机上编译一遍 Wine然后把工具目录传进来。第二iOS SDK 的版本要和你的目标设备系统版本匹配比如你的 iPhone 跑的是 iOS 17那 SDK 至少要是 iOS 17 的。第三Wine 的某些功能依赖fork()系统调用而 iOS 对fork()的限制非常严格这部分代码需要打补丁绕过。编译完成后你会得到wine的 ARM64 版本但它还不能直接跑因为它是给 iOS 编译的需要放到 iOS 设备上才能执行。这就涉及到下一步如何把 FEX-Emu、Wine 和你的 Windows 程序打包到一起放到 iOS 设备上运行。3.3 打包与签名iOS 的“入场券”iOS 对可执行文件的管理极其严格任何要在设备上运行的代码都必须经过签名。对于 Madeira 这种非 App Store 分发的项目你有几个选择越狱设备可以直接运行未签名代码非越狱设备则需要用开发者证书或者企业证书签名。我走的是开发者证书路线因为越狱设备越来越难找而且越狱本身也有安全风险。具体做法是用ldid或者codesign对 FEXLoader 和 Wine 的可执行文件进行签名然后打包成一个.app结构通过 Xcode 或者ios-deploy安装到设备上。签名命令大致如下ldid -S -M -Kmykey.p12 FEXLoader ldid -S -M -Kmykey.p12 wine-S表示签名-M表示生成 entitlements-K指定签名证书。如果你没有开发者证书也可以用免费的 Apple ID 签名但有效期只有 7 天过期后需要重新签名。提示iOS 的签名机制会校验可执行文件的内存权限特别是mprotect和mmap的调用。FEX-Emu 在运行时需要把翻译后的代码写入内存并执行这涉及到PROT_EXEC权限。如果你的签名 entitlements 里没有dynamic-codesigning或者com.apple.security.cs.allow-jitFEX-Emu 会在启动时直接崩溃。这是整个流程中最容易卡住的地方。3.4 运行时的环境变量与参数调优当所有东西都准备好之后你需要在 iOS 设备上通过 SSH 或者终端 App 来启动 FEXLoader。启动时需要设置一系列环境变量这些变量直接决定了性能和兼容性。export FEX_ROOTFS/path/to/rootfs export FEX_APP_CONFIG/path/to/config.json export FEX_ENABLE_JIT1 export FEX_MULTIBLOCK1 export WINEPREFIX/path/to/wineprefix ./FEXLoader /path/to/wine /path/to/your.exeFEX_ENABLE_JIT1是必须的否则 FEX 会退化成解释模式速度慢十倍以上。FEX_MULTIBLOCK1开启多块编译能把多个基本块合并翻译减少翻译开销。WINEPREFIX是 Wine 的工作目录里面存放了虚拟的 C 盘、注册表等数据第一次运行时会自动初始化需要几分钟时间。我实测下来一个简单的 Windows 计算器程序在 iPhone 13 上从启动到出现界面大概需要 15 到 25 秒具体取决于 RootFS 的大小和 JIT 缓存的命中率。第二次启动会快一些因为部分翻译结果被缓存了。4. 实操过程与核心环节实现一次完整的部署记录4.1 环境准备宿主机与目标设备的双端配置我用的宿主机是一台 Ubuntu 22.04 的台式机配置是 i7-12700 32GB 内存用来编译 FEX-Emu 和 Wine。目标设备是一台 iPhone 13系统版本 iOS 16.5通过 Palera1n 越狱。为什么选越狱设备因为非越狱设备上 JIT 权限的获取太不稳定而 FEX-Emu 没有 JIT 基本没法用。宿主机上需要安装的依赖包括cmake、clang、lld、python3、ninja-build、pkg-config以及 Xcode 的命令行工具如果你在 macOS 上编译的话。在 Ubuntu 上交叉编译 iOS 目标还需要cctools和ldid这两个工具可以通过apt安装也可以从源码编译。目标设备上需要安装 OpenSSH 和基本的命令行工具方便我通过 SSH 从宿主机推送文件和执行命令。越狱后可以通过 Cydia 或者 Sileo 安装openssh和coreutils。4.2 编译 FEX-Emu 的完整命令与参数说明我在宿主机上编译 FEX-Emu 用的是以下命令序列git clone --depth 1 https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_C_COMPILERclang \ -DCMAKE_CXX_COMPILERclang \ -DENABLE_ASSERTIONSOFF \ -DENABLE_LTOON \ -DBUILD_TESTSOFF \ .. ninja -j16ENABLE_LTOON开启链接时优化能提升 5% 到 10% 的性能但编译时间会增加不少。BUILD_TESTSOFF跳过测试用例的编译节省时间。-j16是并行任务数根据你的 CPU 核心数调整我 12 核 20 线程的 CPU 用 16 比较合适。编译完成后build/Bin/FEXLoader就是我们要的可执行文件。但它是 x86-64 的不能直接在 ARM64 的 iPhone 上跑。等等这里有个关键点FEX-Emu 本身是运行在 ARM64 上的所以我们需要的是 ARM64 版本的 FEXLoader。这意味着编译时要用 ARM64 的交叉编译工具链而不是宿主机的 x86-64 工具链。正确的做法是用aarch64-linux-gnu-gcc或者 Clang 的--targetaarch64-linux-gnu选项来编译。但因为我们最终目标是 iOS所以还需要用 iOS SDK 来编译。这就回到了上一节说的交叉编译问题。我实际用的命令是这样的cmake -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_SYSTEM_NAMEiOS \ -DCMAKE_OSX_ARCHITECTURESarm64 \ -DCMAKE_OSX_SYSROOT$(xcrun --sdk iphoneos --show-sdk-path) \ -DCMAKE_C_COMPILER$(xcrun --sdk iphoneos -f clang) \ -DCMAKE_CXX_COMPILER$(xcrun --sdk iphoneos -f clang) \ -DENABLE_ASSERTIONSOFF \ -DENABLE_LTOON \ ..这样编译出来的 FEXLoader 就是 ARM64 的 iOS 可执行文件可以直接放到 iPhone 上运行。4.3 Wine 的配置与 Windows 程序的导入Wine 编译完成后你需要初始化一个 Wineprefix。在 iOS 设备上执行export WINEPREFIX/var/mobile/wineprefix export WINEDEBUG-all ./wine wineboot -uwineboot -u会初始化 Wineprefix创建虚拟的 C 盘目录结构、注册表文件等。这个过程在 iOS 上可能需要几分钟因为涉及到大量的文件操作。WINEDEBUG-all是关闭调试输出否则屏幕上会刷满日志影响性能。初始化完成后你可以把 Windows 程序复制到$WINEPREFIX/drive_c/目录下然后通过 Wine 启动./wine $WINEPREFIX/drive_c/your_program.exe但这里有个问题Wine 本身是 ARM64 的它不能直接加载 x86-64 的 exe 文件。所以你需要让 FEX-Emu 来加载 Wine再由 Wine 来加载 exe。正确的调用链是./FEXLoader ./wine $WINEPREFIX/drive_c/your_program.exeFEXLoader 会把 Wine 的 ARM64 指令翻译成 x86-64 指令不对反了。FEXLoader 是运行在 ARM64 上的它加载的是 x86-64 版本的 Wine。所以你需要编译一个 x86-64 版本的 Wine然后让 FEXLoader 去加载它。这样 FEXLoader 把 x86-64 的 Wine 指令翻译成 ARM64 指令执行Wine 再加载 x86-64 的 exe 文件整个链路就通了。这就是为什么 FEX 需要一个 x86-64 的 RootFS——因为 Wine 和 exe 都是 x86-64 的它们都需要在 FEX 的翻译环境下运行。4.4 性能实测与调优记录我在 iPhone 13 上跑了一个简单的 Windows 程序——一个用 Win32 API 写的时钟小程序只有一个窗口和一个定时器。启动时间大约 18 秒界面出现后每秒刷新一次CPU 占用率在 40% 到 60% 之间波动内存占用约 300MB。这个性能显然不能用来跑游戏或者大型软件但对于验证技术可行性来说已经足够了。我尝试过调优主要从几个方面入手第一增大 JIT 缓存。FEX 默认的 JIT 缓存大小是 128MB我把它调到 512MB重复执行的代码命中率明显提升第二次启动时间缩短到 12 秒左右。第二关闭不必要的 Wine 服务。Wine 默认会启动一些后台服务比如wineserver、explorer.exe等这些在 iOS 上都是负担。我通过修改注册表把不需要的服务禁用了CPU 占用率下降了大约 10%。第三使用FEX_MULTIBLOCK1和FEX_OFFLINE1组合。FEX_OFFLINE1会让 FEX 在启动时预编译所有代码而不是运行时动态编译。这会增加启动时间但运行时的帧率更稳定。我实测下来对于交互式程序这个组合的体验更好。5. 常见问题与排查技巧实录5.1 启动崩溃从日志定位问题根源Madeira 这套东西最让人头疼的就是启动崩溃而且崩溃信息往往非常模糊。我遇到过几次典型的崩溃场景这里整理一下排查思路。第一种崩溃是Killed: 9这通常是签名问题。iOS 在加载可执行文件时会校验签名如果签名无效或者 entitlements 不匹配系统会直接杀掉进程。排查方法是检查ldid -e输出的 entitlements 是否包含dynamic-codesigning和com.apple.security.cs.allow-jit。如果没有需要重新签名。第二种崩溃是Segmentation fault: 11这通常是 FEX 在翻译指令时遇到了不支持的指令或者内存访问越界。排查方法是设置FEX_LOG_LEVELdebug让 FEX 输出详细的翻译日志看看崩溃前最后翻译的是哪条指令。如果是不支持的指令可能需要升级 FEX 版本或者手动打补丁。第三种崩溃是Abort trap: 6这通常是 Wine 内部的断言失败。排查方法是设置WINEDEBUGall让 Wine 输出完整的调试日志然后搜索Assertion failed关键字定位到具体的代码位置。5.2 中文乱码字体与编码的双重问题Wine 在 iOS 上运行时中文乱码是一个非常常见的问题。根本原因有两个一是缺少中文字体二是编码设置不正确。字体问题好解决把 Windows 的simsun.ttc或者开源的Noto Sans CJK复制到 Wineprefix 的drive_c/windows/Fonts/目录下然后在注册表里把默认字体替换成中文字体。注册表路径是HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts添加一个SimSun的键值指向你的字体文件。编码问题稍微麻烦一些。Wine 默认使用 UTF-8 编码但很多老程序用的是 GBK 或者 GB2312。你需要在 Wineprefix 的system.reg文件里设置Locale为zh_CN.GBK或者在启动时设置LANGzh_CN.GBK环境变量。我实测下来对于大多数中文程序设置LANGzh_CN.UTF-8加上中文字体就能正常显示少数老程序才需要 GBK。提示如果你在 Wine 里看到的是方块或者问号那基本就是字体问题如果看到的是乱码字符那基本就是编码问题。两者要分开处理不要混在一起调。5.3 性能瓶颈CPU 占用高、响应慢的优化方向性能问题是最难解决的因为它的根源在于架构差异。x86-64 和 ARM64 的指令集差异很大FEX 的翻译效率再高也不可能达到原生执行的速度。但你可以通过一些手段来缓解。首先减少 Wine 的调试输出。WINEDEBUG-all是必须的否则日志输出会占用大量 CPU。其次关闭 FEX 的断言检查ENABLE_ASSERTIONSOFF在编译时就要设置好。再次使用FEX_MULTIBLOCK1和FEX_OFFLINE1组合让 FEX 预编译热点代码。如果还是慢可以考虑降低程序的图形需求。比如把程序的窗口大小调小关闭动画效果减少重绘频率。我试过把一个 800x600 的窗口改成 400x300帧率提升了将近一倍。5.4 常见问题速查表问题现象可能原因排查方法解决方案启动即崩溃无日志签名无效检查 entitlements重新签名添加 JIT 权限启动后卡在加载界面RootFS 不匹配检查 FEX 和 RootFS 版本下载匹配版本的 RootFS中文显示为方块缺少中文字体检查 Fonts 目录复制中文字体并修改注册表中文显示为乱码编码设置错误检查 LANG 环境变量设置 LANGzh_CN.UTF-8运行极慢CPU 满载JIT 未启用检查 FEX_ENABLE_JIT设置 FEX_ENABLE_JIT1Wine 报错找不到 DLLWineprefix 不完整检查 drive_c 目录重新执行 wineboot -u程序窗口无法显示图形驱动不兼容检查 Wine 的图形后端切换到 X11 或 Wayland 后端内存占用持续增长内存泄漏监控进程内存限制程序运行时间定期重启6. 这套方案还能怎么扩展Madeira 目前的状态是“能跑但不好用”。如果你对这个方向感兴趣有几个扩展思路值得尝试。第一个方向是优化 FEX 的翻译策略。目前的 FEX 是动态翻译每次遇到新代码都要翻译一遍。如果能加入离线预翻译把常用的 Windows DLL 提前翻译成 ARM64 缓存启动速度会大幅提升。这个思路在 FEX 的社区里已经有讨论了但还没有成熟的实现。第二个方向是精简 Wine 的组件。Wine 本身很庞大很多功能在 iOS 上根本用不到。如果能裁剪出一个最小化的 Wine只保留核心的 API 兼容层体积和内存占用都能降下来。这个工作需要深入理解 Wine 的源码结构难度不小但收益很直接。第三个方向是探索非越狱方案。目前 Madeira 依赖越狱设备来获取 JIT 权限这限制了它的受众。如果能找到一种在非越狱设备上合法获取 JIT 权限的方法比如利用某些开发者接口或者系统漏洞那这套方案就能覆盖更多用户。当然这条路涉及的技术和法律问题都很复杂需要谨慎对待。我个人在实际操作中的体会是Madeira 这类项目的价值不在于它能跑多少程序而在于它证明了在 iOS 这种封闭平台上通过层层翻译和兼容理论上可以运行任何架构的任何程序。这个证明本身比跑起来一个记事本更有意义。如果你也在折腾类似的东西建议先把 FEX-Emu 在 Linux ARM 上跑通再迁移到 iOS这样能少踩很多坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑