
1. 项目缘起为什么我要折腾 Madeira第一次看到“Madeira”这个词很多人第一反应是葡萄牙那个产葡萄酒的海岛。但在我们这圈折腾系统兼容层的人眼里它指向的是另一件事——一个把 x86-64 指令翻译成 ARM64 指令的运行环境方案配合 FEX-Emu、Wine、DXMT 这套组合拳目标是在非 x86 架构的设备上把 Windows 应用和游戏跑起来。我最初接触这个方向是因为手头有一台 ARM 架构的轻薄本日常办公够用但偶尔想跑一些只有 Windows 版本的老工具和独立游戏虚拟机太重、云电脑延迟又受不了于是开始研究指令翻译这条路。Madeira 这个项目标题背后核心诉求其实很明确在 ARM 设备上构建一套能跑 x86-64 Windows 程序的兼容层。它涉及的技术栈不是单一工具而是一条链路——底层用 FEX-Emu 做指令集翻译中间用 Wine 提供 Windows API 实现图形层用 DXMT 把 Direct3D 调用翻译到 Metal最终在 iOS 或 ARM Linux 设备上呈现出来。热搜词里出现的 FEX-Emu、Wine、DXMT、iOS、x86-64 这几个关键词基本勾勒出了整个技术地图的轮廓。这篇文章适合谁看如果你是在 ARM 设备上折腾 Windows 兼容层的开发者或者对指令翻译、Wine 生态、跨架构运行感兴趣的技术爱好者再或者你只是好奇“为什么 ARM 电脑跑 Windows 程序这么费劲”那接下来的内容应该对你有用。我会把这条链路上每个环节的原理、选型理由、实操步骤和踩过的坑都摊开讲尽量做到你照着做就能复现。需要提前说明的是这套方案目前还不是“一键安装包”级别的成熟度很多环节需要手动配置和调试。但正因为如此把过程记录下来才有价值——网上关于 FEX-Emu 和 DXMT 配合使用的完整中文资料并不多很多信息散落在各个项目的 issue 区和讨论帖里我把自己实测有效的路径整理出来希望能帮你少走弯路。2. 整体架构拆解从 x86-64 到 ARM64 的翻译链路2.1 为什么需要指令翻译而不是模拟先把这个最基础的问题讲清楚。ARM 设备和 x86-64 设备的指令集完全不同就像两个人一个说中文一个说葡萄牙语直接对话是不可能的。传统的做法是“模拟”——用软件模拟出一颗 x86 CPU 的行为每条指令都解释执行。QEMU 的全系统模拟就是这条路优点是兼容性好缺点是慢通常只有原生性能的百分之几到十几。FEX-Emu 走的是另一条路动态二进制翻译。它不模拟 CPU 的物理行为而是把 x86-64 指令块实时翻译成 ARM64 指令块翻译结果可以缓存复用。这就像同声传译——不是逐字解释而是把整段话理解后直接用目标语言说出来速度自然快得多。实测下来FEX-Emu 在跑一些轻量级应用时能到原生性能的 50% 到 70%游戏场景下也有 30% 到 50%这个数字已经足够让很多应用“可用”了。但光有指令翻译还不够。Windows 程序不只是 x86 指令的集合它还依赖大量的 Windows API——文件系统调用、注册表、窗口管理、图形接口等等。这些 API 在 Linux 或 iOS 上不存在所以需要 Wine 来提供一层兼容实现。Wine 把 Windows API 调用翻译成 POSIX 调用让程序以为自己运行在 Windows 上。FEX-Emu 负责指令层Wine 负责 API 层两者配合才能让一个 exe 文件真正跑起来。2.2 DXMT 的角色图形层的最后一公里图形是最难的一环。Windows 游戏和应用大量使用 Direct3D 渲染而 ARM 设备上的图形 API 是 MetaliOS/macOS或 VulkanLinux/Android。DXMT 的作用就是把 D3D 调用翻译成 Metal 调用它是基于 DXVK 的思路做的 Metal 后端实现。为什么不用 DXVK 加 MoltenVK 的组合DXVK 是把 D3D 翻译成 VulkanMoltenVK 再把 Vulkan 翻译成 Metal两层翻译意味着两层性能损耗和两层 bug 来源。DXMT 直接做 D3D 到 Metal 的翻译链路更短在 Apple 设备上的效率明显更好。这也是为什么 Madeira 方案在 iOS 和 Apple Silicon 设备上更有意义——Metal 是 Apple 生态的原生图形 API直接对接比绕道 Vulkan 更合理。整个链路的调用关系可以这样理解Windows 程序发出 D3D 调用DXMT 拦截并翻译成 Metal 命令Metal 驱动提交给 GPU 执行同时程序的 x86-64 指令由 FEX-Emu 翻译成 ARM64 指令在 CPU 上执行Wine 则在中间处理窗口创建、输入事件、文件读写这些系统级调用。三层各司其职缺一不可。2.3 方案选型的几个关键取舍在搭建这套环境时有几个选择需要提前想清楚。第一个是FEX-Emu 的 RootFS 模式还是 Thunk 模式。RootFS 模式是让 FEX-Emu 提供一个完整的 x86-64 根文件系统环境Wine 和所有依赖都装在里面隔离性好但配置复杂Thunk 模式是让 FEX-Emu 作为库被调用Wine 的 ARM64 版本通过 thunk 机制调用 x86-64 的代码性能更好但需要 Wine 本身有 ARM64 构建。我实测下来如果你只是想跑几个特定程序RootFS 模式更省心如果要长期使用并且追求性能Thunk 模式值得折腾。第二个取舍是Wine 的版本选择。Wine 官方版本、Proton、CrossOver 各有侧重。Proton 对游戏做了大量优化但绑定 Steam 生态CrossOver 是商业版对 macOS 支持好但收费官方 Wine 最纯粹但需要自己打很多补丁。在 Madeira 这个场景下我建议从 Wine 官方的最新开发版入手配合 FEX-Emu 的补丁集因为社区对这条组合的测试最多遇到问题更容易找到答案。第三个是图形后端的配置。DXMT 目前对 D3D11 的支持比较成熟D3D12 还在完善中D3D9 则可以通过 DXMT 或 WineD3D 走 OpenGL 路径。如果你要跑的程序是 D3D9 时代的其实用 WineD3D 加 OpenGL 可能更稳如果是 D3D11 的现代应用DXMT 是更好的选择。这个判断需要在动手前就做好因为不同后端的配置方式差别很大。3. 环境搭建实操从零开始配置 Madeira3.1 基础系统准备与依赖安装我以 ARM64 Linux 环境为例来演示iOS 上的配置思路类似但限制更多后面会单独说。首先确认你的系统是 aarch64 架构用uname -m看一下输出应该是aarch64。然后安装基础编译工具和依赖sudo apt update sudo apt install -y build-essential cmake ninja-build git python3 python3-pip \ libsdl2-dev libvulkan-dev libgl1-mesa-dev libegl1-mesa-dev \ libasound2-dev libpulse-dev libdbus-1-dev libudev-dev这些依赖里SDL2 是窗口和输入处理Vulkan 和 OpenGL 是图形基础ALSA 和 PulseAudio 是音频DBus 和 udev 是系统集成。少装一个都可能在后续步骤报错建议一次性装齐。接下来获取 FEX-Emu 源码。FEX-Emu 的仓库在 GitHub 上直接 clone 最新主分支git clone --recurse-submodules https://github.com/FEX-Emu/FEX.git cd FEX--recurse-submodules这个参数很重要FEX-Emu 依赖一些子模块不拉取的话编译会失败。如果 clone 过程中网络中断可以进目录后执行git submodule update --init --recursive补上。3.2 编译 FEX-Emu 与配置 RootFS编译 FEX-Emu 本身不复杂但有几个 CMake 选项需要根据你的场景调整mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease \ -DENABLE_ASSERTIONSOFF \ -DBUILD_TESTSOFF \ -DCMAKE_INSTALL_PREFIX/usr/local make -j$(nproc) sudo make installENABLE_ASSERTIONSOFF是为了性能断言检查在调试时有用但会拖慢运行速度。BUILD_TESTSOFF跳过测试编译节省时间。-j$(nproc)用满所有 CPU 核心加速编译ARM 设备上编译 FEX-Emu 大概需要十几分钟到半小时取决于设备性能。编译完成后需要准备 RootFS。FEX-Emu 提供了一个脚本可以自动下载和配置一个基础的 x86-64 根文件系统cd /path/to/FEX ./Scripts/InstallFEXRootFS.sh这个脚本会下载一个精简的 Ubuntu x86-64 镜像并解压到~/.fex-emu/RootFS/目录下。如果下载速度慢可以手动下载镜像文件放到对应目录。RootFS 准备好后用FEXRootFSFetcher工具可以管理多个 RootFS 版本方便切换测试。注意RootFS 的存储路径默认在用户主目录下确保你的主目录有至少 5GB 的可用空间。如果空间紧张可以通过FEX_ROOTFS_PATH环境变量指定其他位置。3.3 Wine 的编译与 FEX 集成Wine 的编译是整个过程里最耗时的环节。在 ARM64 上编译 Wine 需要先配置好 FEX-Emu 的 thunk 库支持否则编译出来的 Wine 无法调用 x86-64 代码。首先确保 FEX-Emu 的 thunk 库已经安装# 确认 thunk 库存在 ls /usr/local/lib/fex-emu/ # 应该能看到 libFEXCore_thunk.so 之类的文件然后获取 Wine 源码。我建议用 Wine 的 staging 分支它包含了一些对游戏和兼容性有帮助的补丁git clone https://gitlab.winehq.org/wine/wine.git cd wine git checkout staging配置编译选项时关键是启用 FEX 的 thunk 支持./configure --enable-win64 \ --with-fex-emu/usr/local \ --disable-tests \ --prefix/opt/wine-fex--enable-win64表示构建 64 位版本--with-fex-emu指定 FEX-Emu 的安装路径--disable-tests跳过测试程序编译。配置完成后make -j$(nproc)开始编译这个过程在 ARM 设备上可能需要一到两个小时建议挂后台跑。编译完成后sudo make install安装到/opt/wine-fex。然后需要设置环境变量让 Wine 知道 FEX-Emu 的存在export FEX_APP_CONFIG/usr/local/share/fex-emu/AppConfig export WINEPREFIX~/.wine-fex export WINEARCHwin64WINEPREFIX指定 Wine 的配置目录建议单独设一个不要和系统默认的~/.wine混用方便出问题时重置。WINEARCHwin64指定创建 64 位前缀因为 FEX-Emu 主要处理 x86-64 代码。3.4 DXMT 的编译与图形配置DXMT 的编译需要 Metal 开发环境在 Linux 上需要先安装 Metal 的兼容层。如果你是在 macOS 或 iOS 上操作Xcode 命令行工具就包含了 Metal 编译器。Linux 上的配置相对复杂需要安装mesa-vulkan-drivers和libmetal相关的开发包。git clone https://github.com/3Shain/dxmt.git cd dxmt meson setup build --prefix/opt/dxmt --buildtyperelease ninja -C build sudo ninja -C build installDXMT 编译完成后需要把生成的 DLL 文件放到 Wine 的对应目录下。具体来说d3d11.dll、dxgi.dll、d3d10core.dll这几个文件需要覆盖 Wine 自带的版本cp /opt/dxmt/lib/wine/x86_64-windows/d3d11.dll $WINEPREFIX/drive_c/windows/system32/ cp /opt/dxmt/lib/wine/x86_64-windows/dxgi.dll $WINEPREFIX/drive_c/windows/system32/覆盖之前建议备份原文件万一 DXMT 跑不起来可以快速回滚。然后在 Wine 注册表里设置 DLL 覆盖优先级wine reg add HKEY_CURRENT_USER\Software\Wine\DllOverrides /v d3d11 /d native /f wine reg add HKEY_CURRENT_USER\Software\Wine\DllOverrides /v dxgi /d native /fnative表示优先使用我们放进去的 DXMT 版本而不是 Wine 内置的实现。这一步做完图形链路就基本打通了。4. 常见问题与排查实录4.1 Wine 乱码与字体问题热搜词里“wine 乱码”和“wine 栏是乱码”出现的频率很高这确实是 Wine 环境最常见的问题之一。乱码的根源通常是字体缺失或字符集配置不对。Wine 默认使用它自带的字体但这些字体对中文支持很差界面上的中文会显示成方块或问号。解决方法分两步。第一步是安装中文字体到 Wine 的字体目录# 把系统中文字体复制到 Wine 字体目录 cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc $WINEPREFIX/drive_c/windows/Fonts/第二步是修改注册表把默认字体替换成支持中文的字体wine reg add HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes /v MS Shell Dlg /d WenQuanYi Micro Hei /f wine reg add HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes /v MS Shell Dlg 2 /d WenQuanYi Micro Hei /f如果乱码出现在菜单栏而不是正文可能是WINEDLLOVERRIDES里某个 DLL 的配置影响了字体渲染。可以尝试临时清空这个变量测试WINEDLLOVERRIDES wine your_app.exe。如果清空后正常再逐个排查是哪个 DLL 覆盖导致的。实操心得字体问题不要只盯着字体本身有时候是 locale 设置不对。确保LANG和LC_ALL环境变量设置成了zh_CN.UTF-8Wine 会根据 locale 选择对应的字符集处理方式。4.2 FEX-Emu 启动失败与性能调优FEX-Emu 启动失败最常见的原因是 RootFS 配置错误或 thunk 库路径不对。如果看到Failed to load RootFS之类的错误先检查~/.fex-emu/RootFS/目录下是否有完整的根文件系统以及FEX_ROOTFS_PATH环境变量是否指向了正确位置。另一个常见问题是程序启动后立即崩溃没有任何错误输出。这种情况通常是 x86-64 指令翻译过程中遇到了不支持的指令或特性。可以打开 FEX-Emu 的日志来定位export FEX_LOG_LEVELinfo export FEX_ENABLE_DEBUG1 wine your_app.exe 21 | tee fex_debug.log日志里会显示翻译失败的指令地址和类型根据这些信息可以判断是 FEX-Emu 的已知限制还是配置问题。FEX-Emu 的 GitHub issue 区有一个“不支持的指令”列表可以对照查看。性能调优方面有几个环境变量值得调整。FEX_TSO_ENABLED1开启内存序模拟对多线程程序兼容性更好但会损失一些性能FEX_MULTIBLOCK1开启多块翻译缓存对循环密集的程序有加速效果FEX_SMALL_TSO1在兼容性和性能之间取一个折中。我实测下来对于大多数单机游戏FEX_MULTIBLOCK1加FEX_SMALL_TSO1是比较平衡的配置。4.3 DXMT 图形问题速查DXMT 相关的问题通常表现为黑屏、花屏或帧率异常。下面这个表格整理了我遇到过的典型症状和对应处理方式症状可能原因处理方式启动后黑屏无画面DXMT DLL 未正确覆盖检查 system32 下 d3d11.dll 是否为 DXMT 版本画面花屏或撕裂Metal 后端兼容性问题尝试设置DXMT_METAL_DEVICE1切换设备帧率极低着色器编译卡顿开启DXMT_SHADER_CACHE1缓存编译结果画面比例异常分辨率映射错误在 Wine 注册表中手动设置虚拟桌面分辨率游戏内文字模糊纹理过滤设置问题调整 DXMT 的DXMT_FILTER_MODE参数着色器编译卡顿是 DXMT 早期版本比较明显的问题第一次运行某个游戏时帧率会很低因为所有着色器都需要实时编译。开启着色器缓存后第二次启动就会流畅很多。缓存文件默认在~/.cache/dxmt/下可以定期清理避免占用过多空间。4.4 iOS 环境的特殊限制与应对在 iOS 上跑这套方案比 Linux 限制多得多。iOS 不允许用户态直接执行任意代码所以 FEX-Emu 的 JIT 翻译需要依赖特定的权限或签名方式。热搜词里“ios 开发者模式”和“ios 自动化”的出现说明很多人在这上面卡住了。目前可行的路径主要有两条。一条是通过 TrollStore 之类的工具安装具有特定权限的应用让 FEX-Emu 能够申请到可执行内存另一条是使用 AltStore 或类似方式侧载但需要每七天重新签名。两条路都有各自的麻烦前者依赖系统版本后者依赖开发者账号。iOS 上的 Wine 环境通常是通过 CrossOver 的 iOS 版本或者自己编译的 Wine 来提供。DXMT 在 iOS 上反而比 Linux 更自然因为 Metal 就是 iOS 的原生图形 API不需要额外的兼容层。但 iOS 的内存限制很严格大型游戏很容易因为内存不足被系统杀掉这个目前没有太好的解决办法只能尽量选择轻量级的应用来跑。注意iOS 上的配置涉及签名和权限管理不同系统版本差异很大。建议先确认你的设备系统版本和可用的签名工具再决定是否投入时间折腾。如果只是想在移动设备上跑 Windows 程序ARM Linux 平板或掌机的体验会好很多。5. 性能实测与优化经验5.1 不同应用场景的性能表现我在一台 ARM64 开发板上做了几组测试配置是 8 核 CPU 加 16GB 内存系统是 Ubuntu 22.04 ARM64 版本。测试对象包括一个 D3D9 的老游戏、一个 D3D11 的独立游戏、一个办公软件和一个命令行工具。结果如下应用类型图形后端启动时间运行帧率/响应可用性评价D3D9 老游戏WineD3DOpenGL8秒25-30 FPS可玩偶有卡顿D3D11 独立游戏DXMT15秒20-25 FPS可玩复杂场景掉帧办公软件Wine 内置5秒响应流畅完全可用命令行工具无图形2秒接近原生完全可用从数据可以看出图形越复杂、API 越新性能损耗越大。D3D9 通过 OpenGL 路径反而比 D3D11 通过 DXMT 更流畅这是因为 OpenGL 在 ARM Linux 上的驱动成熟度更高。如果你的目标应用是 D3D9 时代的不妨先试试 WineD3D 路径可能比 DXMT 更省心。5.2 编译优化与运行参数调校FEX-Emu 和 Wine 的编译选项对最终性能影响很大。除了前面提到的 Release 构建和关闭断言还有几个选项值得关注。FEX-Emu 的-DENABLE_LTOON开启链接时优化能提升 5% 到 10% 的性能但编译时间会增加不少。Wine 的--enable-optimize会启用编译器优化默认是开启的但如果你之前手动关过记得打开。运行时的参数调校同样重要。FEX-Emu 的FEX_TSO_ENABLED和FEX_MULTIBLOCK前面说过了这里补充一个FEX_ROOTFS_OVERLAY参数可以指定一个可写的覆盖层让 RootFS 保持只读的同时允许程序写入临时文件。这个对需要写配置文件的程序很有用export FEX_ROOTFS_OVERLAY~/.fex-emu/Overlay mkdir -p $FEX_ROOTFS_OVERLAYWine 这边WINEDEBUG环境变量可以控制日志输出级别。调试时用WINEDEBUGd3d11,dxgi查看图形相关日志正常使用时设为WINEDEBUG-all关闭所有日志以提升性能。这个差别在低性能设备上很明显我实测关闭日志后帧率能提升 3 到 5 帧。5.3 内存与存储的优化技巧ARM 设备的内存通常比 x86 设备紧张而 FEX-Emu 的翻译缓存和 Wine 的前缀文件都会占用不少空间。几个优化方向一是限制 FEX-Emu 的翻译缓存大小通过FEX_MAX_CACHE_SIZE环境变量设置上限避免缓存无限增长二是定期清理 Wine 前缀里的临时文件$WINEPREFIX/drive_c/users/下的 Temp 目录经常积累大量垃圾三是把 RootFS 和 Wine 前缀放在 SSD 上机械存储会明显拖慢启动速度。存储方面FEX-Emu 的 RootFS 和 Wine 前缀加起来大概 3 到 5GB加上 DXMT 的着色器缓存总共需要预留 10GB 左右的空间。如果设备存储紧张可以考虑把不常用的 RootFS 版本删掉只保留当前使用的版本。6. 后续扩展与个人体会这套方案目前还在快速迭代中FEX-Emu 和 DXMT 都在持续更新每隔几个月就会有明显的改进。我个人的建议是保持关注上游仓库的 release 页面但不要盲目追新——新版本可能引入新的 bug稳定可用的版本比最新版本更重要。我通常会在一个新版本发布后等一两周看看 issue 区有没有严重的回归问题再决定是否升级。另外值得一提的是这套技术栈的思路其实可以迁移到其他场景。比如在 ARM 服务器上跑 x86 的遗留服务或者在没有 x86 硬件的环境下做兼容性测试FEX-Emu 加 Wine 的组合都能派上用场。DXMT 的 Metal 后端思路也可以借鉴到其他图形翻译项目里理解它的架构设计对做类似工具很有帮助。最后分享一个我在调试过程中总结的小技巧遇到问题时先把链路拆开单独测试。先确认 FEX-Emu 能跑一个简单的 x86-64 命令行程序再确认 Wine 能启动记事本最后才测试图形程序。每一步都确认无误后再往下走比一上来就跑大型游戏然后面对一堆报错要高效得多。这个“分层验证”的习惯帮我省了很多时间希望你也能用上。