1. 从“Madeira”这个名字说起一个跨平台兼容层的野心第一次看到“Madeira”这个项目名很多人会以为是某个旅游地或者葡萄酒品牌。但在跨平台兼容这个圈子里它指向的是一类非常硬核的技术方向——让原本为某一套系统编译的二进制程序能够在另一套完全不同的系统上直接跑起来。结合热搜词里高频出现的 FEX-Emu、Wine、DXMT、x86-64 这些关键词基本可以判断Madeira 是一个围绕指令集翻译与系统调用转译构建的兼容运行环境目标是在 ARM 设备上运行 x86-64 的 Windows 应用并且尽可能保留图形加速能力。这件事为什么值得单独拿出来讲因为过去几年里大家做跨平台兼容的常规思路是“虚拟机 完整系统镜像”资源占用高、启动慢、图形性能差。而 Madeira 这类方案走的是另一条路不做完整虚拟化而是把指令翻译、系统调用映射、图形 API 转换这三层拆开各自用最合适的组件去处理。FEX-Emu 负责把 x86-64 指令翻译成 ARM64 指令Wine 负责把 Windows 的 PE 加载和 Win32 API 调用翻译成 POSIX 调用DXMT 则负责把 Direct3D 调用翻译成 Metal 调用。三者叠在一起才构成一个能跑 3D 游戏的完整链路。这套组合的价值在于它让 ARM 设备比如各类轻薄本、掌机、开发板在不装双系统、不买云主机的前提下直接运行大量 Windows 独占软件和游戏。对于做 iOS 开发、自动化测试、或者单纯想在 ARM 设备上跑 Windows 工具链的人来说这是一个非常实用的方向。本文会从架构拆解、环境搭建、图形链路调优、常见故障排查四个角度把 Madeira 这类方案讲透适合有一定 Linux 基础、想动手折腾兼容层的读者。2. Madeira 的三层架构指令翻译、系统调用、图形转译2.1 FEX-Emu 在链路里的位置它不是模拟器是翻译器很多人把 FEX-Emu 和 QEMU 混为一谈其实两者定位差别很大。QEMU 做的是完整系统级模拟连 CPU 的寄存器状态、中断控制器、外设都要模拟一遍所以慢。FEX-Emu 做的是用户态指令翻译它只接管目标程序的指令流把 x86-64 指令块动态翻译成 ARM64 指令块翻译结果会缓存起来下次执行同一段代码直接走缓存。这意味着热点代码的翻译开销会被摊薄实际运行效率比全系统模拟高一个数量级。FEX-Emu 的核心机制是Block Translation 指令缓存。程序第一次执行到某个基本块时FEX 把这段 x86-64 指令翻译成 ARM64存进内存里的翻译缓存之后再次执行到同一地址直接跳转到缓存里的 ARM64 代码。对于循环密集的代码这个缓存命中率非常高性能损失可以压到 20% 到 40% 之间。但要注意FEX 对自修改代码和JIT 生成的代码处理起来比较吃力因为翻译缓存需要失效重建这也是为什么某些加壳程序或者自带 JIT 的运行时会出问题。在 Madeira 的链路里FEX-Emu 是最底层的一环它不关心你跑的是 Windows 程序还是 Linux 程序只负责指令集转换。所以它必须和上层的 Wine 配合Wine 负责加载 PE 文件、解析导入表、提供 Win32 API 实现FEX 负责把这些代码翻译成 ARM64 执行。两者通过一个叫thunk的机制通信Wine 调用系统库时走原生 ARM64 路径调用 Windows 程序内部代码时走 FEX 翻译路径。2.2 Wine 的角色不只是“兼容层”更是 PE 加载器和 API 翻译器Wine 经常被误解成“模拟 Windows”其实它一行 Windows 代码都不模拟。它的本质是实现一套与 Win32 API 同名的函数库当 Windows 程序调用CreateWindowEx时Wine 用自己的实现去响应底层再调用 X11 或 Wayland 的对应接口。所以 Wine 不是模拟器是API 翻译层。在 Madeira 场景下Wine 还要额外处理一件事PE 文件的加载和重定位。Windows 的 exe 和 dll 是 PE 格式Linux 是 ELF 格式Wine 需要把 PE 文件映射到内存、解析导入表、处理重定位信息然后才能把控制权交给 FEX 去翻译执行。这个过程里最容易出问题的是DLL 依赖很多 Windows 程序依赖msvcp140.dll、vcruntime140.dll这类运行库Wine 自带了一部分实现但版本对不上就会报错。热搜词里出现的“wine 乱码”“wine 栏是乱码”就是典型问题。Wine 默认的字体配置和 Windows 不一样中文程序调用CreateFont时如果指定的字体在系统里找不到Wine 会回退到一个不含中文字形的字体结果就是方框或者乱码。解决办法通常是安装winetricks里的corefonts和cjkfonts或者直接把 Windows 的字体目录挂载到 Wine 的字体路径下。2.3 DXMT 为什么关键Direct3D 到 Metal 的最后一公里如果只跑 2D 程序FEX Wine 就够了。但要跑 3D 游戏就必须解决图形 API 转换问题。Windows 游戏调用的是 Direct3D而 ARM 设备上的原生图形 API 是 Metal苹果平台或 VulkanLinux/安卓平台。DXMT 的作用就是把 D3D 调用翻译成 Metal 调用让游戏在不改代码的情况下用上硬件加速。DXMT 的工作方式和 DXVK 类似但目标 API 不同。DXVK 把 D3D 翻译成 VulkanDXMT 把 D3D 翻译成 Metal。翻译层要处理的不只是函数调用映射还有资源生命周期管理D3D 的纹理、缓冲区、着色器对象都要在 Metal 里找到对应的资源类型并且保证同步正确。如果同步做不好就会出现画面撕裂、纹理错位、甚至崩溃。在 Madeira 的链路里DXMT 是性能瓶颈最明显的一环。因为指令翻译FEX和 API 翻译Wine的开销相对固定而图形翻译的开销跟游戏复杂度直接相关。一个简单的 2D 游戏可能只损失 10% 帧率但一个重度 3D 游戏可能损失 50% 以上。所以调优的重点通常放在 DXMT 的配置上比如调整着色器缓存大小、开启异步编译、关闭不必要的调试层。3. 在 ARM 设备上把 Madeira 跑起来环境准备与依赖安装3.1 系统选择与内核要求Madeira 这类方案对系统内核有明确要求需要支持 4K 页大小或者 16K 页大小的 ARM64 内核并且要开启binfmt_misc模块否则 FEX 无法注册为 x86-64 二进制的处理器。大多数主流 Linux 发行版的 ARM64 版本都满足这个条件但要注意有些定制系统裁剪了binfmt_misc需要自己重新编译内核或者加载模块。我实测下来比较稳的组合是Ubuntu 22.04 ARM64 或 Fedora 38 ARM64内核版本 5.15 以上。这两个发行版的软件源里都有 FEX-Emu 和 Wine 的较新版本省去大量编译时间。如果用的是苹果 Silicon 设备还需要注意系统完整性保护SIP的限制某些内核模块无法加载这时候只能走用户态方案性能会打折扣。安装顺序很重要先装 FEX-Emu再装 Wine最后装 DXMT。因为 Wine 在编译时会检测 FEX 的存在如果先装 Wine 后装 FEXWine 可能没有启用 FEX 支持导致 x86-64 程序无法执行。具体命令如下# 添加 FEX-Emu 官方源以 Ubuntu 为例 sudo add-apt-repository ppa:fex-emu/fex sudo apt update sudo apt install fex-emu # 注册 binfmt_misc sudo systemctl restart systemd-binfmt # 验证 FEX 是否生效 echo x86-64 binary format registered装完 FEX 后可以用一个简单的 x86-64 静态编译程序测试比如file命令查看一个 x86-64 的 ELF 文件然后直接执行看是否能跑起来。如果能跑说明 FEX 的 binfmt 注册成功。3.2 Wine 的编译选项与 FEX 集成Wine 的安装有两种方式发行版包管理器直接装或者从源码编译。如果只是跑普通程序包管理器版本够用但如果要跑 3D 游戏或者需要 FEX 深度集成建议从源码编译并且开启以下选项./configure --enable-archsi386,x86_64 \ --with-fex \ --without-x \ --with-wayland--with-fex是关键它让 Wine 在加载 PE 文件时主动调用 FEX 的翻译接口而不是依赖 binfmt_misc 的自动触发。这样做的好处是Wine 可以控制翻译的粒度比如对某些频繁调用的 DLL 做预翻译减少运行时开销。编译完成后需要设置环境变量让 Wine 知道 FEX 的位置export FEX_ROOT/usr/lib/fex-emu export WINEPREFIX$HOME/.wine-madeira wineboot --initWINEPREFIX建议单独设置不要和系统默认的~/.wine混用因为 Madeira 场景下的 Wine 配置和普通 Wine 差别很大混用容易出问题。3.3 DXMT 的安装与 Metal 后端启用DXMT 目前主要通过源码编译安装依赖Metal 头文件和 Metal 编译器。在 Linux 上Metal 支持是通过mesa的metal驱动或者苹果的moltenvk间接提供的。如果是在苹果 Silicon 设备上跑 Linux 虚拟机Metal 可以直接透传如果是纯 Linux ARM 设备则需要通过mesa的软件渲染或者 Vulkan 转译层。安装步骤大致如下git clone https://github.com/3Shain/dxmt.git cd dxmt meson setup build --prefix/usr ninja -C build sudo ninja -C build install装完后需要把 DXMT 的d3d11.dll、dxgi.dll等文件复制到 Wine 的system32目录下覆盖 Wine 自带的实现。然后设置环境变量export DXMT_ENABLE_METAL1 export DXMT_SHADER_CACHE1 export DXMT_ASYNC_COMPILE1DXMT_ASYNC_COMPILE开启后着色器编译会在后台线程进行避免主线程卡顿。这个选项对帧率稳定性提升很明显但代价是第一次遇到新着色器时可能出现短暂画面异常。4. 图形链路调优从帧率波动到着色器缓存4.1 为什么第一次运行游戏总是卡着色器编译的锅很多人第一次用 Madeira 跑游戏时会发现刚进游戏时帧率很低走几步就卡一下但玩了几分钟后越来越流畅。这不是错觉而是着色器编译在作祟。D3D 游戏在运行时会把 HLSL 着色器编译成 GPU 能执行的机器码这个编译过程在原生 Windows 上由显卡驱动完成速度很快但在 DXMT 链路里编译要经过HLSL → DXBC → Metal Shader Language → Metal 二进制多道转换耗时可能是原生的几十倍。解决办法是开启着色器缓存。DXMT 支持把编译好的 Metal 着色器缓存到磁盘下次运行同一游戏时直接加载缓存跳过编译步骤。缓存文件默认放在~/.cache/dxmt下可以手动备份换机器时直接拷贝过去。# 查看缓存大小 du -sh ~/.cache/dxmt # 备份缓存 tar czf dxmt-cache-backup.tar.gz ~/.cache/dxmt但要注意缓存和驱动版本、DXMT 版本绑定升级 DXMT 后旧缓存可能失效需要删除重建。我一般会在升级后先删掉缓存目录让游戏重新编译一遍避免缓存不一致导致的画面错误。4.2 帧率上不去的三个常见原因调优过程中帧率上不去通常不是单一原因而是多个瓶颈叠加。我整理了一个排查表按优先级从高到低排列现象可能原因排查方法解决方向帧率稳定但偏低GPU 瓶颈或翻译开销大用MANGOHUD1查看 GPU 占用降低分辨率或关闭抗锯齿帧率波动大着色器编译或内存不足观察卡顿是否集中在特定场景开启异步编译和缓存帧率突然掉到个位数同步问题或死锁查看日志是否有timeout或fence错误调整 DXMT 同步模式CPU 占用高但 GPU 空闲指令翻译瓶颈用perf top看 FEX 占用开启 FEX 的块缓存优化其中同步问题最隐蔽。DXMT 默认使用 Metal 的MTLEvent做同步但如果游戏用了多线程渲染事件等待可能变成死锁。这时候可以尝试切换到MTLSharedEvent模式或者降低同步频率。具体配置在 DXMT 的dxmt.conf里[Sync] Mode SharedEvent Timeout 5000Timeout设太小会导致误判设太大会让卡顿更明显5000 微秒是我实测比较平衡的值。4.3 内存与显存的分配策略ARM 设备通常是统一内存架构CPU 和 GPU 共享同一块物理内存。这本来是优势但在 Madeira 场景下反而容易出问题Wine 和 FEX 会占用大量内存做翻译缓存DXMT 又要分配显存做纹理和缓冲区两边抢内存就会导致频繁换页帧率暴跌。我的经验是给 FEX 的翻译缓存设上限给 DXMT 的显存池设下限。FEX 的缓存默认是动态增长的可以手动限制export FEX_TCACHE_SIZE512M export FEX_TCACHE_MAX1GDXMT 这边则要保证有足够的显存池export DXMT_VRAM_SIZE2048这两个值要根据设备实际内存调整。8GB 内存的设备FEX 缓存给 512MB 到 1GBDXMT 显存给 2GB剩下的留给系统和 Wine基本能跑大多数游戏。16GB 设备可以适当放宽。5. 踩坑实录从乱码到崩溃的完整排查链路5.1 Wine 中文乱码不只是字体问题“wine 乱码”是热搜里出现频率最高的问题之一。很多人以为装个中文字体就解决了其实乱码分三种情况处理方式完全不同第一种是字体缺失。Wine 默认只带Liberation系列字体不含中文字形。程序调用CreateFont时如果指定的字体名在系统里找不到Wine 会回退到默认字体中文就变成方框。解决办法是安装winetricks的cjkfontswinetricks cjkfonts第二种是字符集不匹配。有些老程序用 GBK 编码Wine 默认按 UTF-8 解析结果就是乱码。这时候需要设置LANG和LC_ALLexport LANGzh_CN.GBK export LC_ALLzh_CN.GBK第三种是注册表里的字体替换没配好。Wine 的注册表里有一张字体替换表如果SimSun被映射到了一个不含中文的字体也会乱码。可以用wine regedit手动修改[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes] SimSunNoto Sans CJK SC Microsoft YaHeiNoto Sans CJK SC我踩过的坑是只装了字体但没改注册表结果程序还是乱码。后来发现是程序内部硬编码了SimSun必须通过注册表替换才能生效。5.2 FEX 翻译失败自修改代码与 JIT 的冲突有一次跑一个自带 JIT 的脚本引擎程序启动后直接崩溃日志里全是FEX: invalid instruction。排查了半天才发现这个引擎在运行时动态生成 x86-64 代码并执行而 FEX 的翻译缓存不知道这段代码被修改了仍然执行旧的翻译结果导致指令错乱。解决办法是开启 FEX 的自修改代码检测export FEX_SMC1开启后FEX 会在每次执行翻译块前检查原始代码是否被修改如果被修改就重新翻译。代价是性能下降因为每次都要做内存比较。但对于自带 JIT 的程序这是唯一能跑通的办法。另一个相关问题是JIT 生成的代码不在 FEX 的监控范围内。有些程序用mmap分配可执行内存然后写入机器码直接跳过去执行。FEX 默认不拦截这种跳转结果就是执行了未翻译的 x86-64 代码直接非法指令。这时候需要开启FEX_JIT模式让 FEX 监控所有可执行内存的分配export FEX_JIT1这个选项对性能影响较大只在必要时开启。5.3 DXMT 崩溃纹理格式不支持的连锁反应跑某个 3D 游戏时进入特定场景就崩溃日志显示MTLTexture creation failed。查了半天发现是游戏用了一个DXMT 不支持的纹理格式比如DXGI_FORMAT_R11G11B10_FLOATDXMT 尝试创建 Metal 纹理时失败但没有正确处理错误导致空指针崩溃。这类问题的排查思路是先看日志里的格式名再去 DXMT 源码里查是否支持。如果不支持可以尝试用DXMT_FORMAT_FALLBACK环境变量强制回退到兼容格式export DXMT_FORMAT_FALLBACK1回退后画面可能略有差异但至少能跑起来。如果回退也不行就只能等 DXMT 更新支持或者换一个游戏版本。6. 从 Madeira 延伸出去这套方案还能怎么用6.1 在 iOS 开发流程里借用兼容层思路热搜词里出现了大量 iOS 开发相关的内容比如xcode从证书配置到上架全流程、ios自动化、ios设备模拟。虽然 Madeira 本身不直接做 iOS 开发但它的兼容层思路可以借鉴比如在 ARM 服务器上跑 x86-64 的 CI 工具链用 FEX 做指令翻译用 Wine 跑 Windows 版的签名工具这样就不需要维护两套构建环境。具体做法是在 ARM64 的 CI runner 上装 FEX Wine然后把 Windows 版的signtool或者自定义的签名脚本跑起来。这样 iOS 的 IPA 签名流程可以在 ARM 机器上完成不需要额外的 x86 机器。实测下来签名这种 IO 密集型任务FEX 的翻译开销几乎可以忽略。6.2 麒麟、统信等系统上的 Wine 兼容组件热搜里还有麒麟wine助手、统信wine windows兼容组件下载这类词。这些国产系统上的 Wine 兼容组件底层原理和 Madeira 类似都是 Wine 指令翻译 图形转译的组合。区别在于它们通常做了更多本地化适配比如预置了常用 Windows 软件的配置模板、集成了中文字体、优化了国产 CPU 的指令翻译路径。如果你在这些系统上跑 Madeira 类似的方案要注意系统自带的 Wine 可能和 FEX 冲突。因为系统 Wine 可能已经注册了 binfmt_miscFEX 再注册会覆盖掉导致系统 Wine 无法启动 Windows 程序。解决办法是给 FEX 单独设置一个 binfmt 名称或者用update-binfmts手动管理优先级。6.3 无感漏洞与自动化兼容层的安全边界热搜词里出现了ios 无感、ios 无感漏洞这类词虽然和 Madeira 不直接相关但提醒我们兼容层本身也是一个攻击面。FEX 翻译 x86-64 指令时如果对某些指令的语义理解有偏差可能导致程序行为异常甚至被利用来做权限提升。DXMT 翻译图形 API 时如果对资源边界的检查不严也可能导致内存越界。所以我在实际使用中会遵循几个原则不在兼容层里跑来源不明的程序、定期更新 FEX 和 DXMT 到最新版本、开启日志监控异常指令。FEX 支持把未识别的指令记录到日志export FEX_LOG_LEVELwarn export FEX_LOG_FILE/tmp/fex.log定期检查日志里有没有unknown instruction或者invalid memory access能提前发现潜在问题。7. 一些实测下来的配置模板与经验值折腾 Madeira 这类方案最耗时的不是安装而是调参。我把经过多次验证的配置整理成模板可以直接抄作业。这套配置在 8GB 内存的 ARM64 设备上跑中等复杂度的 3D 游戏能稳定在 30 帧以上。首先是环境变量建议写进~/.bashrc或者单独的启动脚本# FEX 配置 export FEX_TCACHE_SIZE512M export FEX_TCACHE_MAX1G export FEX_SMC1 export FEX_LOG_LEVELwarn export FEX_LOG_FILE/tmp/fex.log # Wine 配置 export WINEPREFIX$HOME/.wine-madeira export WINEARCHwin64 export LANGzh_CN.UTF-8 # DXMT 配置 export DXMT_ENABLE_METAL1 export DXMT_SHADER_CACHE1 export DXMT_ASYNC_COMPILE1 export DXMT_VRAM_SIZE2048 export DXMT_FORMAT_FALLBACK1然后是 DXMT 的配置文件~/.config/dxmt/dxmt.conf[Sync] Mode SharedEvent Timeout 5000 [Cache] Path ~/.cache/dxmt MaxSize 4G [Render] MaxFrameLatency 2 VSync 0MaxFrameLatency设成 2 是我实测下来延迟和流畅度比较平衡的值设成 1 延迟更低但容易掉帧设成 3 更流畅但操作延迟明显。VSync关掉可以避免垂直同步带来的额外延迟但画面可能撕裂看个人取舍。最后是 Wine 的注册表优化主要是字体替换和 D3D 设置wine reg add HKEY_CURRENT_USER\Software\Wine\Direct3D /v MaxVersionGL /t REG_DWORD /d 4 /f wine reg add HKEY_CURRENT_USER\Software\Wine\Direct3D /v VideoMemorySize /t REG_SZ /d 2048 /fMaxVersionGL设成 4 是为了让 Wine 使用 OpenGL 4.x 的特性配合 DXMT 的 Metal 后端能减少一层转换。VideoMemorySize要和 DXMT 的VRAM_SIZE保持一致否则可能出现显存分配失败。这套配置不是万能的不同游戏可能需要微调。但作为起点它能帮你快速跑通大部分场景然后再根据具体问题逐个优化。我自己的经验是先保证能跑再追求跑得好。一开始就追求满帧往往会在细节上卡住不如先让程序跑起来再根据日志和性能数据逐步调整。
阅读完成 · 觉得有帮助?