动态二进制翻译技术原理:ExaGear 怎么把 x86 指令变成 ARM
为什么 x86 程序不能直接跑在 ARM 上
x86 和 ARM 是两套完全不同的机器指令集。x86 是 CISC(复杂指令集),一条指令可能同时做加法、内存访问和条件判断;ARM 是 RISC(精简指令集),每条指令只做一件事。更麻烦的是字节序不同、寄存器数量不同、调用约定也不同。Windows 程序编译后生成的是 x86 的机器码,ARM CPU 拿到这些二进制根本不知道怎么执行。
转译器的三段流水线
动态二进制翻译器在运行时对二进制代码做三件事:
- Basic Block 切割:把 x86 程序切成一个个「基本块」——一个连续指令序列,唯一出口是跳转或返回。块是翻译的最小单位。
- 逐条转译 + 优化:对块内每条 x86 指令生成等价的 ARM 指令序列,同时做窥孔优化(比如合并相邻的寄存器操作、消除冗余内存访问)。
- 缓存与链接:翻译好的 ARM 代码放进代码缓存。下次程序走到同一块,直接跳缓存里的 ARM 版本,不再重新翻译。块之间的跳转也被改写,让控制流在缓存的 ARM 代码之间直接跳转。
热代码 vs 冷代码
程序 90% 的时间在执行 10% 的代码——这些是「热代码」,必须缓存。冷代码(初始化逻辑、极少触发的错误处理)可以每次重新翻。ExaGear 内部维护一个计数器,某个基本块被执行多次后自动升级为「预翻译」状态(pre-translator 特性),下次启动时直接跳过首次翻译,冷启动速度明显提升。
与静态翻译的对比
| 方式 | 转译时机 | 启动速度 | 运行性能 | 灵活性 |
|---|---|---|---|---|
| 动态翻译(ExaGear 采用) | 运行时即时 | 中等(首次要翻) | 高(热代码缓存后接近原生) | 高(无需预处理二进制) |
| 静态翻译 | 运行前一次性 | 快 | 理论最高 | 低(需要完整反编译) |
| 解释执行 | 运行时逐条 | 慢 | 最低 | 最高 |
鲲鹏服务器上的 ExaGear
值得一提的是,华为鲲鹏服务器用的 ExaGear 版本比 Android 版功能更全:支持 AArch64 平台上的 x86 Linux 程序,支持 pre-translator 预翻译以加速冷启动,还支持 Docker-ExaGear 方案让容器里直接跑 x86 镜像。Android 版是技术下放的产物,内核相同。
性能开销来自哪里
转译不是零成本。每条 x86 指令展开成的 ARM 指令数通常是 2~4 倍;加上块边界的跳转修正、寄存器映射开销、内存布局调整——翻译层本身要吃掉 10%~30% 的性能。Wine 的 Windows API 模拟又是另一层开销。所以手机上运行,骁龙 8 系或同等性能的处理器 + 4GB RAM 是比较舒适的配置。