AnyPS5 --to-intel 完整指南:SSE4a/SHA-NI 指令如何被降级运行在 Intel CPU 上
【免费下载链接】AnyPS5Tool for automatic PS5 executables porting to Linux and Windows项目地址: https://gitcode.com/gh_mirrors/an/AnyPS5
AnyPS5是一款将 PS5 可执行文件自动移植到 Linux 和 Windows 的工具,无需模拟器或独立运行时进程。它的 relinker 负责把可执行文件转换为目标系统的原生格式,并提供 系统 PRX 库 的动态链接实现。对于 Intel 宿主,传入--to-intel参数即可把可执行文件及配套 PRX 中的 AMD 专属指令自动降级,让你在 Intel CPU 上跑原本"只认 AMD"的游戏代码。
为什么需要 --to-intel?先搞懂 AMD64 专属指令
PS5 主机用的是 AMD Zen 架构 CPU,编译器可以理所当然地生成一些AMD 专属指令。但你在自己的电脑上运行移植后的游戏时,CPU 很可能是 Intel 的——Intel CPU 并不认识这些指令,直接运行会触发非法指令错误。
常见的问题指令有两大家族:
| 指令族 | 指令 | 作用 |
|---|---|---|
| 🏷️SSE4a(AMD) | EXTRQ/INSERTQ | 在 128 位 SSE 寄存器中按位域提取/插入数据 |
| 🔐SHA 指令(AMD SHA-NI) | SHA256RNDS2/SHA256MSG1/SHA256MSG2 | 硬件加速 SHA-256 哈希运算 |
此外,替换表 中还登记了MONITORX、MWAITX、CLZERO、RDPRU、MCOMMIT、MOVNTSS等 AMD 指令的识别条目,供扫描器匹配。
💡 简单说:
EXTRQ/INSERTQ是"寄存器里的位搬运工",SHA 指令则是"CPU 内置的哈希加速器"。AnyPS5 的任务就是把这些"AMD 方言"翻译成 Intel 也听得懂的"普通话"(x86-64 基线指令)。
一键转换步骤:--to-intel 怎么用
relinker 只依赖 C++20 标准库,任何符合标准的编译器都能构建。在 Intel 宿主机上,只需在命令中加上--to-intel:
relinker --to-intel input.elf output.elf完整的命令行选项可以在 CliArgs.cpp 的用法说明中找到,例如还可以配合--windows(输出 Windows PE 格式)、--rpath <path>(自定义库搜索路径)、--autorun(转换完直接运行)等。
--to-intel参数本身在 CliArgs.cpp 中解析,随后在 main.cpp 中触发整个 Intel 转换流程,并打印每一处替换的报告。
揭秘降级机制:原地替换与跳板桩两大模式
转换的核心实现是 Amd64OnlyConverter.cpp。它的工作流可以分为三步:
1️⃣ 逐段扫描,找出所有 AMD 专属指令
转换器读取 ELF 的所有代码段,用指令扫描器逐条解码,再用指令匹配器判断哪些是 AMD 专属指令(Amd64OnlyInstructionMatcher.cpp)。
2️⃣ 选择降级方式
根据指令形态,每条 AMD 专属指令会被分配两种降级方式之一(见 Amd64OnlyConverter.cpp 的switch):
InPlace(原地替换):当 AMD 指令能翻译成等长的 Intel 指令序列时,直接在原位置覆写。例如某些特殊场景下的EXTRQ可以简化为SHRD/PSRLDQ甚至删除,再用NOP指令填充剩余空间(Sse4aLowering.cpp 的LowerInPlace实现了这种优化,填充用的 1~7 字节 NOP 模板定义在 替换表 中)。Trampoline(跳板桩/Stub):当翻译结果比原指令更长时(比如 SHA 指令展开成几十条 SSE 指令),就地放不下。此时把原指令位置改写为一条 5 字节的jmp,跳到一段新分配的"桩代码"区域执行完整逻辑,执行完再跳回原处(kJmpRel32定义于 替换表)。桩体的生成由 StubBodyBuilder.cpp 负责。
3️⃣ 安全校验,绝不"猜"
这里有两个严谨的防错设计,非常值得新手学习:
- 分支落点检查:跳板会改写一段连续空间,如果其他分支指令恰好跳进这段空间中间就会出错。转换器会先收集所有分支目标地址,发现"分支落入 AMD 指令内部"的情况直接报错(_collectBranchTargets)。
- 不支持就报错:遇到没有降级方案的 AMD 指令,或跳转超出 x86-64 相对分支范围的情况,程序会抛出明确的
CodegenException,错误信息里附带文件偏移(见 CodegenException.hpp 与 main.cpp 的异常处理),而不是生成一个"能跑但结果错误"的坏文件。
SSE4a 降级详解:一条 EXTRQ 如何变成一串 SSE
EXTRQ/INSERTQ的翻译是纯位运算组合拳,实现在 Sse4aLowering.cpp。按指令形态分三条路径:
- 字节对齐 + 长度 8 的倍数:直接用一条
PSHUFB字节洗牌指令搞定(L114-L119),这是最快的路径; - 非对齐的 EXTRQ:用
PSRLDQ/PSLLDQ移位 +PSRLDQ掩码截取位域(L120-L127); - 非对齐的 INSERTQ:先用
PXOR+ 掩码在目标寄存器挖出"坑",再把左移后的源数据POR进去(L138-L154); - 寄存器形式的 INSERTQ:需要拆分源寄存器的高/低 64 位再重组,使用 3 个临时寄存器完成(_emitInsertqRegisterForm)。
操作数解析(区分内存形式/寄存器形式、提取 index 和 length)由 Sse4aOperands.cpp 完成。
SHA-NI 降级详解:把硬件哈希展开为纯 SSE 逻辑
SHA 指令的降级更复杂——SHA256RNDS2一条指令对应 SHA-256 压缩函数里两轮完整的 64 位运算,展开后需要 8 个临时寄存器。实现在 Sha256Lowering.cpp:
- Σ(sigma)函数:如
BigSigma0(x) = ROTR2(x) ⊕ ROTR13(x) ⊕ ROTR22(x),用PSRLDQ/PSLLDQ循环移位加PXOR异或组合实现(_sigma); - Ch/Maj 布尔函数:
Ch(e,f,g) = (e ∧ f) ⊕ (¬e ∧ g)这类逻辑被拆成PAND/PXOR序列(_choose、_majority); - 两个轮次 + 状态写回:_emitRounds 完成 T1/T2 计算并更新 ABCD 状态;
SHA256MSG1/MSG2的消息扩展逻辑在 _emitMessage1 和 _emitMessage2 中展开。
操作数的指令类型识别(RNDS2/MSG1/MSG2)在 Sha256Operands.cpp 中处理。
🎯 由于 SHA 指令展开后远长于原指令,它们必然走 Trampoline 跳板路径——这也解释了为什么桩代码区需要预留空间并做 16 字节对齐(
kStubAlignment,见 替换表)。
转换报告怎么看:读懂你的转换日志
运行--to-intel后,控制台会输出类似这样的报告(打印逻辑在 main.cpp):
Intel substitution: SHA256RNDS2 at 0x1a2b3 (2 bytes) -> stub 196 bytes Intel substitution: EXTRQ at 0x2c4d1 (3 bytes) -> in place 3 bytes Intel conversion: 1 in place, 3 stubsin place:原地替换,字节数保持相等,零跳板开销;stub:生成跳板桩,原位置变成短跳,桩体长度即报告中的字节数;- 若遇到无法处理的指令,输出会是
FAIL: AMD-only instruction without Intel lowering: XXX (offset 0x...)——这是"严格报错"设计,宁可失败也不给你留下隐患。
这些跳板桩最终随 LinuxElfPatcher 一起写进输出文件(main.cpp 中的 Patch 调用)。
常见问题 FAQ
❓ 我的游戏在 AMD 电脑上能跑,Intel 电脑上不行,该怎么办?
先重新用--to-intel转换一次,确认转换报告中没有FAIL。如果转换成功但运行崩溃,检查崩溃地址是否落在跳板桩区域附近,并把报告反馈给开发者。
❓ 为什么不支持的指令不是自动跳过,而是直接报错?
因为静默跳过会产生"看似能运行、实则数据错误"的坏产物(尤其是哈希相关的代码),对游戏存档、在线校验这类场景危害极大。项目的整体风格就是"不支持的状态严格抛异常"(见 README),--to-intel也遵循同样的原则。
❓ 转换会影响性能吗?
原地替换路径性能损失很小(几条基线 SSE 指令替代一条专属指令);SHA 指令走跳板时,每次调用多一次间接跳转,且展开的 SSE 序列比硬件指令慢——但换来了在 Intel CPU 上运行的能力,属于典型的空间换可用。
❓ 只移植到 Windows 还需要 --to-intel 吗?
需要与否取决于目标机 CPU,而不是操作系统。只要最终运行在 Intel CPU 上(无论 Windows 还是 Linux),都建议加上--to-intel。
想深入源码?从这里入手
如果你想动手研究这套降级系统,推荐按以下顺序阅读:
- IAmd64OnlyConverter.hpp —— 转换器接口定义,30 行看懂全貌
- Amd64OnlyConverter.cpp —— 扫描、降级决策、跳板生成的主流程
- Sse4aLowering.cpp + Sha256Lowering.cpp —— 两个指令族的完整降级实现
- Amd64OnlyConverterTests.cpp —— 测试用例,展示了各种指令形态的期望转换结果
配合项目的开发约定文档和技术债务清单,你就能理解整个 relinker 模块的设计思路。
【免费下载链接】AnyPS5Tool for automatic PS5 executables porting to Linux and Windows项目地址: https://gitcode.com/gh_mirrors/an/AnyPS5
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考