ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FEX-Emu + Wine + DXMT:ARM 设备跨平台运行 Windows 应用实战

FEX-Emu + Wine + DXMT:ARM 设备跨平台运行 Windows 应用实战 1. 从“Madeira”说起一个跨平台兼容项目的整体设计思路“Madeira”这个名字乍一看像是个地名但在跨平台兼容和系统仿真这个圈子里它代表的是一个相当有意思的技术方向——把 x86-64 架构的 Windows 应用通过 FEX-Emu、Wine、DXMT 这一整套工具链搬到非 x86 的平台上跑起来尤其是 ARM 架构的设备。我第一次接触这个组合的时候脑子里冒出来的第一个问题就是为什么不直接用虚拟机答案其实很直接——虚拟机跑 x86 应用在 ARM 设备上性能损耗大到让人怀疑人生而 FEX-Emu 这种用户态指令翻译层配合 Wine 的 Windows API 实现再加上 DXMT 做图形指令的转译整套链路虽然复杂但效率比全系统模拟高出一个数量级。这个项目的核心目标用户其实很明确一是在 ARM 设备上想跑 Windows 游戏或生产力工具的人二是需要在 Linux 环境下兼容 Windows 应用的开发者三是那些对系统仿真和指令翻译技术感兴趣、想自己动手折腾的技术爱好者。如果你只是想在 Windows 上装个软件那这个项目跟你关系不大但如果你手里有一台 ARM 笔记本、一台跑 Linux 的迷你主机或者想在一台设备上同时搞定多平台应用那这套方案值得你花时间研究。我之所以对这个组合特别感兴趣是因为它把几个原本独立的技术栈串成了一条完整的链路。FEX-Emu 负责把 x86-64 指令翻译成 ARM64 指令Wine 负责把 Windows API 调用翻译成 POSIX 调用DXMT 负责把 DirectX 调用翻译成 Metal 或 Vulkan 调用。每一层都有自己的坑但组合起来之后能跑的应用范围比单独用任何一个都大得多。下面我就把这套东西拆开一层一层讲清楚。1.1 为什么是 FEX-Emu 而不是 QEMU很多人一提到跨架构运行应用第一反应就是 QEMU。QEMU 确实强大能模拟整个系统包括 CPU、内存、外设但它的代价是性能。QEMU 的全系统模拟模式下每一条 x86 指令都要经过取指、译码、执行、写回这一整套流程而且还要模拟内存管理单元和中断控制器开销非常大。我实测过在 ARM 设备上用 QEMU 跑一个简单的 x86 Windows 程序启动时间比原生慢了将近二十倍交互操作更是卡到没法用。FEX-Emu 的思路完全不同。它工作在用户态只翻译应用层的指令不模拟整个系统。具体来说它通过一个叫“thunking”的机制把 x86-64 的二进制代码动态翻译成 ARM64 的二进制代码然后直接交给宿主 CPU 执行。系统调用这一层它通过一个叫“syscall translation”的模块把 x86 的 syscall 映射到 ARM64 的 syscall 上。这样做的好处是大部分计算密集型的代码经过一次翻译之后后续执行都是原生速度只有遇到新的代码路径时才需要重新翻译。当然FEX-Emu 也不是没有缺点。它对自修改代码的支持比较有限有些加壳的程序或者运行时生成代码的引擎可能会出问题。另外它的翻译缓存机制在内存受限的设备上可能会成为瓶颈。但总体来说在 ARM 设备上跑 x86 应用这个场景下FEX-Emu 是目前性价比最高的选择。1.2 Wine 在中间扮演了什么角色FEX-Emu 解决了指令集的问题但 Windows 应用不光是 x86 指令它还依赖大量的 Windows API。这就是 Wine 上场的地方。Wine 的全称是“Wine Is Not an Emulator”它不是一个模拟器而是一个兼容层把 Windows 的 API 调用翻译成宿主系统的 POSIX 调用。比如 Windows 的CreateFile会被翻译成 Linux 的openReadFile会被翻译成read等等。Wine 的复杂度在于Windows API 的语义和 POSIX 的语义并不是一一对应的。比如 Windows 的注册表在 Linux 上就没有直接对应的东西Wine 只能用一个文件来模拟。再比如 Windows 的窗口管理和 Linux 的 X11 或 Wayland 差异很大Wine 需要做大量的适配工作。这也是为什么 Wine 跑某些程序会出各种奇怪的问题——不是 Wine 不够努力而是两个系统的设计哲学本来就不一样。在“Madeira”这个组合里Wine 的版本选择很关键。我建议用较新的 Wine 版本比如 8.x 或 9.x因为新版本对 DXMT 的支持更好而且修复了很多在 ARM 平台上的兼容性问题。另外Wine 的配置也很有讲究比如winecfg里的 Windows 版本要选对winetricks里要装的组件也要根据具体应用来定。1.3 DXMT 解决了图形 API 的翻译问题Windows 应用尤其是游戏离不开 DirectX。在 Linux 上传统的做法是用 DXVK 把 DirectX 翻译成 Vulkan但在 ARM 设备上Vulkan 驱动的支持情况参差不齐而且 DXVK 本身也是 x86 的库需要经过 FEX-Emu 翻译性能损失不小。DXMT 的思路不一样它直接把 DirectX 翻译成 Metal这在 Apple Silicon 设备上特别有用因为 Metal 是苹果的原生图形 API性能开销比 Vulkan 小得多。DXMT 目前主要支持 DirectX 11 和部分 DirectX 12 功能对老游戏的兼容性不错但新游戏可能会遇到问题。我在实际使用中发现DXMT 对纹理格式的转换处理得比较好但在处理复杂的着色器时偶尔会出问题。如果你主要跑的是独立游戏或者老游戏DXMT 完全够用如果是最新的 3A 大作可能还需要等 DXMT 进一步成熟。1.4 这套组合适合谁说了这么多技术细节回到最实际的问题这套东西到底适合谁我的判断是如果你满足以下任意一条就值得尝试第一你有一台 ARM 设备比如 Apple Silicon 的 Mac、树莓派 5、或者高通骁龙的 Windows 笔记本想跑 Windows 应用第二你在 Linux 环境下工作但偶尔需要用 Windows 专属软件第三你对指令翻译和系统兼容层技术感兴趣想自己动手搭建一套环境。但如果你追求的是“开箱即用”的体验那这套方案可能会让你失望。它的配置过程需要一定的命令行基础遇到问题时也需要自己排查。不过一旦配置好了日常使用的体验还是相当不错的。我自己的主力设备上就跑了这套组合平时写代码、跑一些 Windows 小工具都没问题。2. 核心细节解析从指令翻译到图形渲染的完整链路2.1 FEX-Emu 的安装与配置要点FEX-Emu 的安装方式取决于你的宿主系统。在 Ubuntu 或 Debian 上可以通过 PPA 安装在 Arch 上AUR 里有现成的包在 Apple Silicon 的 Mac 上可以通过 Homebrew 或者直接编译源码。我推荐直接编译源码因为这样可以针对你的具体硬件做优化比如开启特定的 CPU 特性支持。编译 FEX-Emu 之前需要先装好依赖CMake、Ninja、Clang、以及一些开发库。具体的命令如下git clone https://github.com/FEX-Emu/FEX.git cd FEX git submodule update --init --recursive mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease -DCMAKE_C_COMPILERclang -DCMAKE_CXX_COMPILERclang .. make -j$(nproc) sudo make install编译完成后需要配置 FEX 的根文件系统。FEX 需要一个包含 x86-64 库和可执行文件的根文件系统通常可以从一个 x86-64 的 Linux 发行版里提取。我一般用 Ubuntu 的 rootfs因为它的库比较全。配置命令如下FEXRootFSFetcher这个命令会引导你下载并配置根文件系统。下载完成后可以通过FEXBash进入一个 x86-64 的 shell 环境测试一下基本功能。注意FEX-Emu 的根文件系统比较大建议预留至少 10GB 的磁盘空间。另外如果你的宿主系统是 ARM64 的 Linux需要确保内核开启了binfmt_misc支持否则 FEX 无法自动接管 x86-64 可执行文件。2.2 Wine 的版本选择与 winetricks 配置Wine 的版本选择直接影响兼容性。我建议用 Wine 9.x 的稳定版因为它在 ARM 平台上的表现比 8.x 好很多。安装方式有两种一种是用系统包管理器安装比如sudo apt install wine另一种是用 Wine 官方的仓库安装这样可以拿到最新的版本。我推荐后者因为系统仓库里的 Wine 版本往往比较旧。安装完 Wine 之后第一件事是运行winecfg配置 Windows 版本。对于大多数应用选 Windows 10 就行如果是老游戏可以选 Windows 7 或 XP。然后在winetricks里安装一些必要的组件比如corefonts解决字体乱码问题、vcrun2019Visual C 运行库、dotnet48.NET Framework 4.8等。winetricks corefonts vcrun2019 dotnet48这里有个坑要注意dotnet48的安装过程比较慢而且有时候会卡住。如果卡住了可以尝试用winetricks -q dotnet48静默安装或者手动下载安装包来装。另外corefonts装完之后Wine 的字体渲染会好很多但如果你遇到中文乱码还需要额外安装中文字体比如把 Windows 的simsun.ttc复制到 Wine 的字体目录里。2.3 DXMT 的编译与集成DXMT 的编译需要 Metal 的开发环境所以只能在 macOS 上编译。如果你用的是 Linux那 DXMT 暂时用不了只能用 DXVK。编译 DXMT 的步骤如下git clone https://github.com/3Shain/dxmt.git cd dxmt mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease .. make -j$(nproc)编译完成后会生成d3d11.dll、dxgi.dll等文件。把这些文件复制到 Wine 的system32目录里然后在winecfg的“函数库”标签页里把d3d11和dxgi设置为“原生”优先。这样 Wine 就会加载 DXMT 而不是自带的 WineD3D。提示DXMT 目前对 DirectX 12 的支持还不完整如果你跑的游戏是 DX12 的可能需要回退到 DXVK 或者等 DXMT 更新。另外DXMT 的日志输出比较详细遇到问题时可以通过WINEDEBUGdxmt来查看详细的调试信息。2.4 整体链路的性能调优这套组合的性能瓶颈通常出现在两个地方一是 FEX-Emu 的指令翻译开销二是图形 API 的翻译开销。对于前者可以通过调整 FEX 的配置来优化比如开启FEX_TSOEnabledTotal Store Ordering来提升内存访问的效率或者调整翻译缓存的尺寸来减少重复翻译。对于后者可以通过调整 DXMT 的配置来优化比如开启异步着色器编译来减少卡顿。另外宿主系统的电源管理策略也会影响性能。在 Linux 上可以把 CPU 调频器设置为performance模式在 macOS 上可以关闭“低电量模式”。这些细节看起来不起眼但实际使用中能感觉到明显的差异。3. 实操过程从零搭建一套可用的跨平台兼容环境3.1 环境准备与依赖安装在开始之前先确认你的宿主系统。我以 Ubuntu 22.04 ARM64 为例其他系统的步骤类似。首先更新系统并安装基础依赖sudo apt update sudo apt upgrade -y sudo apt install -y build-essential cmake ninja-build clang lld git python3 python3-pip然后安装 FEX-Emu 的依赖sudo apt install -y libssl-dev libglib2.0-dev libfuse-dev libsdl2-dev接下来安装 Wine 的依赖sudo apt install -y libgnutls28-dev libkrb5-dev libx11-dev libxext-dev libxrender-dev这些依赖装完之后就可以开始编译 FEX-Emu 和 Wine 了。如果你不想自己编译也可以用现成的二进制包但自己编译的好处是可以针对你的硬件做优化。3.2 FEX-Emu 的编译与根文件系统配置编译 FEX-Emu 的过程前面已经提过这里补充一些细节。编译时可以通过-DCMAKE_C_FLAGS和-DCMAKE_CXX_FLAGS来传递优化参数比如-marchnative可以让编译器针对你的 CPU 生成最优化的代码。不过要注意FEX-Emu 本身是运行在 ARM64 上的所以-marchnative是针对 ARM64 的不是 x86-64。编译完成后运行FEXRootFSFetcher来下载根文件系统。这个工具会从网上下载一个 x86-64 的 Ubuntu rootfs然后解压到~/.fex-emu/RootFS目录下。下载完成后可以用FEXBash进入这个环境测试一下uname -m是否返回x86_64。FEXBash uname -m # 应该输出 x86_64如果输出的是aarch64说明 FEX 没有正确接管需要检查binfmt_misc的配置。3.3 Wine 的安装与初始配置Wine 的安装可以用包管理器也可以从源码编译。我推荐用 Wine 官方的仓库因为版本比较新。添加仓库的命令如下sudo mkdir -pm755 /etc/apt/keyrings sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/jammy/winehq-jammy.sources sudo apt update sudo apt install --install-recommends winehq-stable安装完成后运行winecfg来初始化 Wine 的配置目录。第一次运行时会提示安装wine-mono和wine-gecko这两个组件是 .NET 和 HTML 渲染的基础建议都装上。如果下载速度慢可以手动下载对应的.msi文件然后放到~/.wine/drive_c/windows/temp目录下Wine 会自动识别。注意在 ARM 设备上wine-gecko和wine-mono需要是 ARM64 版本否则无法运行。Wine 官方提供的版本通常是 x86 的需要通过 FEX-Emu 来运行。如果遇到问题可以尝试用winetricks来安装这些组件。3.4 DXMT 的集成与测试DXMT 的集成相对简单把编译好的 DLL 文件复制到 Wine 的system32目录然后在winecfg里设置加载顺序即可。测试的时候可以用一个简单的 DirectX 11 程序来验证比如dxdiag或者一些老游戏。cp build/bin/d3d11.dll ~/.wine/drive_c/windows/system32/ cp build/bin/dxgi.dll ~/.wine/drive_c/windows/system32/ winecfg # 在“函数库”标签页里添加 d3d11 和 dxgi设置为“原生”测试时可以用WINEDEBUGdxmt来查看 DXMT 的日志确认它是否被正确加载。如果日志里出现DXMT initialized之类的信息说明集成成功。3.5 实际应用场景测试我测试了几个典型的应用场景一是跑一个 Windows 下的老游戏比如《植物大战僵尸》二是跑一个 Windows 下的生产力工具比如 Notepad三是跑一个基于 .NET 的小工具。结果如下应用类型运行情况备注植物大战僵尸游戏流畅DXMT 渲染正常帧率稳定Notepad工具流畅字体显示正常中文无乱码.NET 小工具工具可用需要安装 dotnet48启动稍慢从测试结果来看这套组合对老游戏和轻量级工具的兼容性很好但对大型游戏和复杂 .NET 应用的兼容性还有待提升。4. 常见问题与排查技巧实录4.1 Wine 乱码问题的排查与解决Wine 乱码是最常见的问题之一表现是界面上的文字显示成方块或者问号。根本原因是 Wine 找不到合适的字体。解决方法分三步第一步安装corefonts第二步安装中文字体第三步配置 Wine 的字体替换。winetricks corefonts # 把 Windows 的 simsun.ttc 复制到 Wine 的字体目录 cp /path/to/simsun.ttc ~/.wine/drive_c/windows/Fonts/ # 在 winecfg 的“字体”标签页里把所有字体替换为 simsun如果还是乱码可以检查一下~/.wine/system.reg里的字体配置确保FontSubstitutes里的映射是正确的。4.2 FEX-Emu 启动失败的常见原因FEX-Emu 启动失败通常有几个原因一是binfmt_misc没有配置好二是根文件系统不完整三是权限问题。排查的时候可以先运行FEXBash看看能不能进入 x86-64 环境如果不能再检查binfmt_misc的注册情况。ls /proc/sys/fs/binfmt_misc/ # 应该能看到 FEX-Emu 的注册项如果没有可以手动注册echo :FEX-x86_64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/FEXInterpreter:OCF | sudo tee /proc/sys/fs/binfmt_misc/register4.3 DXMT 渲染异常的排查思路DXMT 渲染异常的表现包括黑屏、花屏、闪退等。排查的时候首先看日志用WINEDEBUGdxmt启动应用观察日志里有没有报错。常见的错误包括着色器编译失败、纹理格式不支持、以及 Metal 设备创建失败。如果日志里出现Shader compilation failed可以尝试关闭 DXMT 的异步着色器编译或者更新 Metal 驱动。如果是纹理格式的问题可以尝试用DXVK替代 DXMT看看问题是否依然存在。4.4 性能调优的实操经验性能调优方面我总结了几个有效的技巧一是开启 FEX 的 TSO 模式可以提升内存密集型应用的性能二是调整 Wine 的CSMT设置开启多线程命令流三是关闭不必要的后台服务释放 CPU 和内存资源。export FEX_TSOEnabled1 export WINEDEBUG-all另外如果你的设备支持可以开启硬件加速的视频解码这样在跑视频播放类的应用时CPU 占用会低很多。4.5 常见问题速查表问题可能原因解决方法Wine 界面乱码缺少字体安装 corefonts 和中文字体FEX 启动失败binfmt_misc 未配置手动注册 binfmt_miscDXMT 黑屏着色器编译失败关闭异步编译更新驱动应用启动慢翻译缓存未命中预热缓存调整缓存大小游戏卡顿图形翻译开销大降低画质开启异步编译5. 跨平台兼容方案的扩展与个人体会这套组合的扩展性其实比想象中强。比如你可以把 FEX-Emu 和 Wine 打包成一个容器镜像这样部署起来更方便也可以把 DXMT 的配置做成模板针对不同的游戏快速切换。另外如果你对性能有极致追求可以尝试把 FEX-Emu 的翻译缓存持久化这样第二次启动同一个应用时速度会快很多。我个人在实际操作中的体会是这套方案最大的价值不在于它能跑多少应用而在于它提供了一种思路通过分层翻译把不同架构、不同系统的生态打通。这种思路不仅适用于 x86 到 ARM 的迁移也适用于其他场景比如把老旧的 Windows 应用迁移到现代 Linux 系统上。最后再分享一个小技巧如果你在配置过程中遇到问题可以先在一个干净的虚拟机里复现这样可以排除宿主系统的干扰。另外多看看 FEX-Emu 和 Wine 的官方文档虽然有些地方写得比较简略但关键信息都在里面。社区论坛和 GitHub Issues 也是很好的资源很多问题别人已经踩过坑了直接搜一下就能找到答案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进