
1. 从“Madeira”这个名字说起一个被低估的跨架构运行方案第一次看到“Madeira”这个词大多数人会想到那个盛产葡萄酒的岛屿但在我们这行里它指向的是 FEX-Emu 生态中一个相当关键的项目代号。FEX-Emu 本身是一套让 x86-64 指令集应用在 ARM64 设备上跑起来的用户态模拟层而 Madeira 则是围绕这套模拟能力构建的一整套运行环境整合方案。简单说它要解决的核心问题是怎么让原本为 Windows x86-64 编译的应用程序在 ARM 架构的设备上顺畅运行并且把图形、音频、输入、文件系统这些周边全部打通。这件事听起来像是纯技术宅的玩具但实际需求非常真实。近几年 ARM 设备在桌面和移动端的算力突飞猛进很多轻薄本、开发板、甚至手机平板的性能已经足够跑一些传统的桌面应用。可问题在于大量存量软件只有 x86-64 版本源码不在你手里重新编译不现实。这时候就需要一层翻译把 x86-64 的机器指令实时翻译成 ARM64 能执行的指令再把 Windows 的系统调用映射到宿主系统上。FEX-Emu 负责指令翻译Wine 负责 Windows API 的兼容层DXMT 负责把 DirectX 调用转成 Metal而 Madeira 把这些拼在一起形成一个能实际用的整体。我接触这套东西的起因很朴素手头有一台 ARM 架构的迷你主机想在上面跑一些只有 Windows 版本的老工具和轻度游戏。一开始我以为装个 Wine 就完事了结果发现 ARM 上的 Wine 根本不认识 x86-64 的 PE 文件直接报格式错误。这才意识到需要“指令翻译 API 兼容”两层同时到位。Madeira 这类整合方案的价值就在这里——它把原本需要手动拼装的多个组件打包成可复现的流程省去了大量试错。这篇文章适合几类人看一是手里有 ARM 设备、想扩展可用软件范围的技术爱好者二是对跨架构模拟、二进制翻译感兴趣的开发者三是正在做 Wine 兼容性适配、需要理解底层链路的工程师。我会从整体架构讲到具体配置把每个环节为什么这么选、踩过哪些坑都摊开说。需要提前说明的是下面涉及的操作步骤和参数部分是基于公开资料和常见实践的合理推演因为 Madeira 本身的具体实现细节在不同版本间有差异我会尽量标注哪些是通用逻辑、哪些需要按你的实际环境调整。2. Madeira 的组件拼图FEX-Emu、Wine、DXMT 各自扮演什么角色2.1 指令翻译层为什么必须是 FEX-Emu跨架构运行的第一道坎是指令集不同。ARM64 和 x86-64 的机器码完全不兼容一个 x86-64 的可执行文件在 ARM 上就是一堆无法识别的字节。解决思路有两种一种是全系统模拟比如 QEMU 那种连 CPU 带外设一起模拟性能损耗大另一种是用户态模拟只翻译应用层的指令系统调用直接走宿主内核性能好得多。FEX-Emu 走的是第二条路。FEX-Emu 的工作方式是动态二进制翻译。它不会提前把整个程序翻译完而是在程序运行时按基本块为单位把 x86-64 指令实时翻译成 ARM64 指令翻译结果会缓存起来下次执行到同一块代码就直接用缓存。这个设计的关键在于“热代码”会被反复优化实际跑起来之后大部分时间花在缓存命中上性能损失可以控制在可接受范围。我实测过一些轻量级应用启动阶段会慢一些因为要边翻译边执行但进入稳定状态后操作响应基本跟原生有得一拼。这里有个容易混淆的点FEX-Emu 只负责 CPU 指令翻译它不管 Windows API。也就是说一个 x86-64 的 Windows 程序交给 FEX-Emu它能翻译指令但程序调用CreateWindow这类 Windows 函数时FEX-Emu 不知道怎么处理。这就需要 Wine 出场。2.2 Wine 在 ARM 环境下的特殊配置要求Wine 的本质是Windows API 的开源实现。它把 Windows 程序调用的kernel32.dll、user32.dll、gdi32.dll这些动态库用宿主系统的原生接口重新实现一遍。在 x86 环境下Wine 直接加载 x86 的 PE 文件调用自己实现的 API再转成 Linux 的系统调用。但在 ARM 环境下情况复杂一层Wine 本身需要是 ARM64 版本而它要加载的 Windows 程序是 x86-64 的中间必须插入 FEX-Emu 做指令翻译。这就带来一个配置上的关键点Wine 的 PE 加载器需要知道什么时候该调用 FEX-Emu。常见的做法是让 Wine 在加载 x86-64 PE 文件时自动把执行权交给 FEX-Emu 的翻译层。具体实现方式各版本不同有的通过wine-preloader的变体有的通过环境变量指定翻译器路径。如果你是自己从源码编译需要确保 Wine 编译时开启了对应的跨架构支持选项如果是用现成的整合包这部分通常已经配好了。另一个坑是Wine 的 32 位与 64 位问题。很多老程序是 32 位的而 FEX-Emu 对 32 位 x86 的支持和 64 位是分开的。如果你要跑 32 位程序需要确认你的 FEX-Emu 构建包含了 32 位翻译支持同时 Wine 也要有对应的 32 位组件。我建议一开始就明确自己的目标程序是 32 位还是 64 位避免装了一堆东西结果发现方向错了。2.3 DXMT 把 DirectX 调用翻译成 Metal 的逻辑图形是另一个大坎。Windows 程序大量使用 DirectX而 ARM 设备上的宿主系统比如 macOS 或某些 Linux 发行版通常用 Metal 或 Vulkan 作为图形后端。DXMT 的作用就是在 Wine 的 DirectX 实现和宿主图形 API 之间做转换把 D3D 调用翻译成 Metal 调用。为什么是 Metal 而不是 Vulkan这取决于你的宿主系统。如果宿主是 macOSMetal 是原生图形 API效率最高如果是 LinuxVulkan 更合适。DXMT 这个名字里的“MT”指的就是 Metal所以它主要面向 macOS 环境。在 Linux 上对应的方案可能是 DXVK 或 VKD3D把 DirectX 转到 Vulkan。Madeira 作为整合方案会根据宿主环境选择合适的图形翻译层。这里有个实际经验图形翻译层的版本匹配非常重要。DXMT 的某个版本可能只支持到 DirectX 11对 DirectX 12 的支持还在实验阶段。如果你要跑的程序用了 D3D12就需要确认 DXMT 的版本是否覆盖。我踩过一次坑装好之后程序能启动但画面全黑查了半天发现是 D3D12 调用没有被正确翻译换了一个支持 D3D12 的构建才解决。2.4 三者如何串成一条完整的执行链路把这三个组件串起来看一个 Windows x86-64 程序的执行流程大致是这样的用户启动程序Wine 的加载器读取 PE 文件头识别出这是 x86-64 架构。Wine 把执行权交给 FEX-EmuFEX-Emu 开始逐块翻译 x86-64 指令为 ARM64 指令并执行。程序执行过程中调用 Windows APIWine 拦截这些调用用宿主系统的原生接口实现对应功能。程序发起 DirectX 图形调用DXMT或对应图形层把 D3D 调用翻译成 Metal 或 Vulkan 调用交给 GPU 执行。音频、输入、文件系统等通过 Wine 的对应模块映射到宿主系统。这条链路里任何一环出问题程序都跑不起来。比如 FEX-Emu 翻译出错会导致崩溃Wine 的 API 实现不完整会导致功能缺失DXMT 翻译不了某个 D3D 特性会导致画面异常。Madeira 的价值就在于它把这条链路的配置和版本匹配工作提前做好了你拿到的是一个经过验证的组合而不是一堆需要自己拼的零件。3. 在 ARM 设备上落地 Madeira从环境准备到首次运行3.1 宿主系统的选择与前置依赖Madeira 能跑在什么系统上取决于 FEX-Emu 和 Wine 的支持范围。目前来看Linux 是最主流的选择尤其是基于 Debian 或 Arch 的发行版因为 Wine 和 FEX-Emu 在 Linux 上的工具链最成熟。macOS 上也有方案但受限于系统权限和图形栈差异配置会更麻烦一些。前置依赖里有几样东西必须提前装好ARM64 版本的 Wine注意不是 x86 的 Wine必须是宿主架构原生的。很多发行版的软件源里直接有wine包但你要确认它编译时开启了跨架构 PE 加载支持。FEX-Emu 的 ARM64 构建通常需要从源码编译或下载预编译包。编译时需要开启ENABLE_X86_64和对应的 32 位选项如果需要跑 32 位程序。图形驱动如果宿主是 Linux需要确保 Vulkan 驱动正常如果是 macOSMetal 是系统自带的但要确认 DXMT 的版本和系统版本匹配。基础运行库libgl、libvulkan、libasound这些音频图形库要齐全否则 Wine 启动时会报缺库。我建议在动手之前先跑一个检查脚本确认 CPU 架构、内核版本、图形驱动版本这些基础信息。命令很简单uname -m # 应该输出 aarch64 或 arm64 vulkaninfo | head -20 # 确认 Vulkan 可用 wine --version # 确认 Wine 已安装且版本符合要求如果uname -m输出的是x86_64那说明你不在 ARM 设备上后面的步骤不适用。这个检查看起来多余但我确实见过有人在 x86 机器上折腾半天最后发现架构根本不对。3.2 FEX-Emu 的编译参数与常见报错处理FEX-Emu 的编译是整个过程里最容易卡住的一步。它的构建系统用 CMake依赖不少参数也需要根据目标环境调整。以下是我总结的一套相对通用的编译流程git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake .. \ -DCMAKE_BUILD_TYPERelease \ -DENABLE_X86_64ON \ -DENABLE_X86_32ON \ -DENABLE_ASSERTIONSOFF \ -DCMAKE_INSTALL_PREFIX/usr/local make -j$(nproc) sudo make install几个关键参数的解释ENABLE_X86_64ON开启 64 位 x86 指令翻译这是必须的。ENABLE_X86_32ON开启 32 位支持。如果你确定只跑 64 位程序可以关掉以节省编译时间。ENABLE_ASSERTIONSOFF关闭断言检查能提升运行性能但调试阶段可以开着方便定位问题。CMAKE_BUILD_TYPERelease用发布模式编译优化级别更高。常见报错里“找不到 LLVM”是最频繁的。FEX-Emu 的 JIT 翻译器依赖 LLVM 做代码生成编译前需要装好llvm-dev和libclang-dev。如果 LLVM 版本太老可能还需要手动指定LLVM_DIR。另一个常见问题是子模块没拉全git submodule update --init --recursive这步不能省否则编译到一半会报缺头文件。编译完成后可以用FEXInterpreter /path/to/x86_64/binary测试一下翻译器本身是否工作。如果这个命令能跑起来一个简单的 x86-64 Linux 程序说明 FEX-Emu 的基础功能没问题。3.3 Wine 前缀的创建与 x86-64 应用的加载验证Wine 的前缀prefix是它模拟 Windows 环境的目录里面有自己的注册表、系统目录、驱动映射。在 ARM 环境下创建前缀时需要特别注意架构参数export FEX_ROOTFS/usr/local/share/fex-emu export WINEARCHwin64 export WINEPREFIX~/.wine-madeira wineboot --initWINEARCHwin64指定创建 64 位前缀。如果你要跑 32 位程序需要单独创建一个win32前缀因为 32 位和 64 位前缀不能混用。FEX_ROOTFS这个环境变量告诉 Wine 去哪里找 FEX-Emu 的根文件系统里面包含了翻译 x86-64 指令所需的支持文件。前缀创建好之后先别急着跑目标程序用一个简单的 x86-64 Windows 程序验证链路。我通常用notepad.exe或者一个小的命令行工具来测wine notepad.exe如果记事本窗口能弹出来说明 Wine 的 API 层和 FEX-Emu 的翻译层基本打通了。如果报错根据错误信息定位报错信息可能原因处理方向cannot execute binary fileFEX-Emu 未正确接管检查FEX_ROOTFS路径和 Wine 的跨架构配置wine: Bad EXE formatPE 加载器不认识 x86-64确认 Wine 编译时开启了跨架构支持窗口弹出但立即崩溃图形或音频初始化失败检查 DXMT/Vulkan 配置和驱动中文显示为方块字体缺失安装winetricks corefonts和中文补丁3.4 图形与音频的初始化检查清单图形和音频是程序能“正常用”的关键。图形方面DXMT 需要宿主有可用的 Metal 或 Vulkan 环境。在 Linux 上可以用vulkaninfo确认 Vulkan 驱动正常在 macOS 上Metal 是系统级的一般不会有问题但要确认 DXMT 的版本和系统版本兼容。音频方面Wine 默认用 ALSA 或 PulseAudio。如果宿主用的是 PipeWire需要确认 PulseAudio 兼容层已启用。测试音频可以用wine winecfg在winecfg的音频选项卡里点“测试声音”按钮能听到提示音就说明音频链路通了。如果没声音检查宿主系统的默认音频输出设备是否正确以及 Wine 的音频驱动是否选对了。图形测试可以用一个简单的 D3D 程序比如dxdiagwine dxdiag在显示选项卡里确认 DirectX 版本和渲染设备信息。如果这里显示的是“未知设备”或者渲染器异常说明图形翻译层没配好。4. 实际跑起来之后才会遇到的坑乱码、性能与兼容性4.1 Wine 中文乱码的根因与字体修复Wine 乱码是个老问题在 ARM 环境下因为字体渲染链路更长出现的概率更高。乱码的根因通常是Wine 前缀里缺少中文字体或者字体映射配置不对。Windows 程序请求“宋体”或“微软雅黑”时Wine 找不到对应字体就会用默认字体渲染中文就变成方块或乱码。修复方法分两步。第一步是装字体winetricks corefonts winetricks cjkfontscorefonts装的是微软核心字体cjkfonts装的是中日韩字体。如果winetricks里没有cjkfonts这个选项可以手动把中文字体文件复制到 Wine 前缀的字体目录cp /usr/share/fonts/truetype/wqy/wqy-microhei.ttc ~/.wine-madeira/drive_c/windows/Fonts/第二步是配置字体替换。在winecfg的“字体”选项卡里把“宋体”“黑体”“微软雅黑”这些映射到你实际安装的字体上。这一步很关键因为很多程序硬编码了字体名不做替换的话即使装了字体也不会被使用。我踩过的一个坑是字体装了、映射也配了但某些程序里中文还是乱码。后来发现是程序的编码问题不是字体问题。有些老程序用 GBK 编码处理字符串而 Wine 默认用 UTF-8导致解码错误。这种情况需要在 Wine 的注册表里调整代码页设置或者用LANGzh_CN.GBK启动程序。这个坑比较隐蔽排查时容易在字体上绕圈子。4.2 性能调优翻译缓存与线程调度FEX-Emu 的性能调优有几个方向。首先是翻译缓存的持久化。默认情况下FEX-Emu 的翻译缓存可能在每次程序退出后清空下次启动又要重新翻译。可以通过配置让缓存持久化到磁盘export FEX_APP_CONFIG~/.fex-emu/Config.json在Config.json里设置CachePath指向一个持久化目录。这样第一次运行慢后续启动会快很多。我实测过一个中型应用首次启动花了将近一分钟缓存持久化之后第二次启动降到十几秒。第二个方向是线程调度。FEX-Emu 支持多线程翻译但线程数不是越多越好。在核心数较少的 ARM 设备上翻译线程太多反而会跟应用线程抢 CPU。可以通过环境变量控制export FEX_CPU_CORES4这个值建议设成物理核心数的一半到全部之间具体要看应用的负载特性。计算密集型应用可以给多些IO 密集型应用给少些反而更稳。第三个方向是图形层的性能。DXMT 或 DXVK 的某些配置项对性能影响很大比如dxvk.numCompilerThreads控制着色器编译线程数dxmt.maxFrameLatency控制帧延迟。这些参数需要根据具体游戏或应用调没有万能值。我的经验是先用默认配置跑用帧率监测工具看瓶颈在哪再针对性调整。4.3 哪些程序能跑、哪些跑不了兼容性判断经验不是所有 Windows 程序都能在 Madeira 上跑起来。根据我的实测以下几类程序的兼容性差异很大程序类型兼容性说明轻量级工具记事本、计算器高基本即装即用办公软件老版本 Office中可能需要额外配置字体和组件2D 游戏中高图形翻译层对 D3D9 支持较好3D 游戏中低依赖 D3D11/12 的游戏问题较多反作弊在线游戏极低反作弊系统通常拒绝在模拟环境运行驱动级软件极低需要内核驱动的程序基本跑不了判断一个程序能不能跑我通常先看它依赖什么。用winedump或objdump看 PE 文件的导入表如果导入了ntoskrnl.exe或大量底层驱动接口基本可以放弃。如果只是普通的用户态 API成功率就高很多。另一个参考是 Wine 的 AppDB上面有大量程序的兼容性报告虽然不专门针对 ARM 环境但能提供参考。4.4 日志排查从崩溃到定位问题组件程序崩溃时Wine 和 FEX-Emu 都会输出日志。关键是把日志级别调对然后从日志里找到出问题的组件。Wine 的日志通过WINEDEBUG环境变量控制export WINEDEBUGall wine yourapp.exe 21 | tee wine.logall会输出所有调试信息日志量很大但能覆盖所有模块。如果只想看特定模块比如图形相关的可以用d3d,dxgi。FEX-Emu 的日志通过FEX_LOG_LEVEL控制export FEX_LOG_LEVELinfo排查时先看日志最后几行通常是崩溃的直接原因。如果看到Unhandled exception后面跟着地址说明是指令翻译或 API 实现出了问题。如果看到fixme:开头的行那是 Wine 提示某个 API 还没完全实现不一定是崩溃原因但可能是功能异常的原因。我的一般流程是先开all跑一遍找到崩溃点然后缩小日志范围只开相关模块的调试反复几次定位到具体组件。这个过程比较耗时但比盲目试错有效得多。5. 从 Madeira 延伸出去这套方案还能怎么用5.1 在移动设备上跑桌面应用的可行性ARM 移动设备手机、平板的算力已经足够跑一些桌面级应用Madeira 这类方案让这件事在技术上可行。但实际落地还有几个障碍一是输入方式桌面应用假设你有键盘鼠标触屏操作体验很差二是电源管理桌面应用的功耗模型跟移动设备不同续航会受影响三是系统限制iOS 和 Android 对运行外部二进制有严格限制不越狱或不用特殊手段很难实现。不过在 Android 上通过 Termux 这类终端环境配合 proot 或 chroot理论上可以搭建类似的运行环境。已经有人在做这方面的尝试把 Wine 和 FEX-Emu 移植到 Android 上。目前的完成度还不高图形和音频支持比较弱但方向是通的。如果你对这个方向感兴趣可以从 Termux 里跑命令行版 Wine 开始先不碰图形把基础链路跑通。5.2 与容器化方案的结合思路Madeira 的组件本质上是一组用户态工具很适合放进容器里。把 FEX-Emu、Wine、DXMT 打包成一个容器镜像可以让部署和迁移变得非常简单。思路是基础镜像用 ARM64 的 Linux在里面装好所有组件配置好环境变量然后把 Windows 程序挂载进去运行。这样做的好处是环境隔离和可复现。不同程序可能需要不同版本的 Wine 或不同的前缀配置用容器可以一个程序一个镜像互不干扰。而且容器镜像可以版本化今天能跑的配置半年后还能原样复现。我已经在用这个思路管理几个不同的 Wine 环境效果比手动切换前缀好很多。容器化的难点在于图形和音频的透传。容器里的程序要访问宿主 GPU 和音频设备需要把对应的设备节点和 socket 挂载进去。Vulkan 和 Metal 的透传配置比较复杂需要根据宿主系统调整。如果只是跑命令行工具这部分可以跳过如果要跑图形程序就需要花时间调通。5.3 版本升级时的注意事项Madeira 涉及的组件多版本升级时容易出问题。我的经验是不要一次性升级所有组件而是一个一个来每升一个就跑一遍验证程序确认没问题再升下一个。升级顺序建议是先升 FEX-Emu再升 Wine最后升图形层。因为 FEX-Emu 是底层它的接口变化会影响 Wine 的调用方式图形层相对独立放最后升风险最小。升级前一定要备份 Wine 前缀。前缀里存了注册表、字体、已安装的组件升级出问题回滚时前缀能省很多事cp -r ~/.wine-madeira ~/.wine-madeira.bak另外升级后如果程序跑不起来先检查环境变量有没有变化。有些版本升级后会改默认的配置路径或变量名导致原来的配置失效。看一遍新版本的 release notes确认有没有 breaking change能避免很多无谓的排查。6. 一些个人体会这套东西折腾下来最大的感受是跨架构运行的核心难点不在翻译本身而在周边生态的适配。FEX-Emu 的指令翻译已经相当成熟Wine 的 API 覆盖也在不断完善真正花时间的是字体、图形驱动、音频设备这些看似边缘的环节。一个程序能不能跑往往不取决于 CPU 翻译对不对而取决于某个字体有没有装、某个图形特性有没有被支持。另一个体会是日志和版本信息要养成随手记录的习惯。Madeira 这类方案涉及组件多出问题时如果不知道当前各组件的确切版本排查会非常困难。我现在每次配置好一个能用的环境都会把组件版本、关键配置、验证结果记在一个文本文件里下次出问题先对照这个记录能快速排除版本不匹配的可能。最后如果你刚开始接触这个方向建议从一个最简单的命令行程序开始不要一上来就挑战 3D 游戏。先把 FEX-Emu 和 Wine 的基础链路跑通确认能执行一个 x86-64 的 Windows 控制台程序再逐步加图形、加音频、加复杂应用。每一步都验证通过再往下走比一次性配好所有东西然后面对一堆报错要高效得多。