为了提高问题的质量,我做了一些研究。
参考资料:
1 - https://dougallj.wordpress.com/2022/11/09/why-is-rosetta-2-fast/
2 - https://en.wikipedia.org/wiki/Binary_translation#Dynamic_binary_translation
3 - https://github.com/laniku/sys71src
Rosetta 1/2 的技术,主要是动态二进制转译( Dynamic Binary Translation )。资料 1 中提到,系统在转译 x86-64 的二进制文件的时候,首次会把所有内容都转译成 ARM 的代码,所以首次启动比较慢。我在 ycombinator 也看到有人说 Total store ordering (TSO)是 Rosetta 2 高速最重要的原因,不过说实话没看懂是为什么。
我觉得 x86-64 -> ARM 对我来说太复杂了,而且也没有源代码看,所以想找点稍微简单点的例子。资料 2 中提到苹果在每一次更换 CPU 架构的时候都会做类似的事情。第一次是在 Power PC 架构的苹果电脑 rom 里放置了一个 M68K emulator 。但是我找来找去也没找到源代码,甚至在资料 3 ( System 7.1 ,应该是首个有 M68K emulator 的操作系统)中也没有,可能 rom 的源代码是分开的。
我想请教一下做类似工作的大佬(我猜想龙芯或者华为可能有做这个的朋友?),如何上手研究这项技术?有没有什么比较简单易懂的项目可以先做起来?我琢磨着,如果转译比如说 6502 到 z80 ,是不是会容易一些?这两个架构都是相对简单一些的,而且可能差别不大,但是寄存器数量不一样( 5 vs 16 ),感觉还是会有些头疼。
多谢!
1
Avn 48 天前 via iPhone 1
|
2
wangyucn 48 天前 1
wine 在 macos 上运行 底层还是靠 Rosetta 2 转译 并不是自己实现了类似技术
|
3
ysc3839 48 天前 2
建议研究开源的游戏模拟器
|
4
Avn 48 天前 via iPhone 2
@wangyucn
Wine 把 Windows API 转换成 macOS API Rosetta 把 x86 指令转换成 ARM 指令 它两个是不同级别的转译,一个是操作系统级别、一个是硬件级别,不是包含关系。我理解是: 1. 在 x86 芯片的 macOS 上运行 x86 的 Windows 应用:只需要 Wine 2. 在 ARM 芯片的 macOS 上运行 x86 的 macOS 应用:只需要 Rosetta 3. 在 ARM 芯片的 macOS 上运行 x86 的 Windows 应用:需要 Wine 和 Rosetta |
5
zk8802 48 天前 2
看一下 Qemu 的源码吧。
|
6
passive 48 天前 via Android 1
不考虑动态链接、内存 layout 这些,理想情况是不是能先逆编译到 llvm ,再新编译到目标代码的时候重新分配寄存器、优化指令。
|
7
passive 48 天前 via Android 1
我在想那些跨 endian 的是怎么转译的。像 cuda 那样同时支持大小 endian 太幸福了。如果在程序里已经写死了每个 byte 处理的顺序,转译的时候并不会知道那是在处理一个 word 。
|
8
wangyucn 48 天前 2
@Avn
>Wine 把 Windows API 转换成 macOS API >Rosetta 把 x86 指令转换成 ARM 指令 >它两个是不同级别的转译,一个是操作系统级别、一个是硬件级别,不是包含关系 这个说的没错 但是人家楼主明确说,要学的是 x86-64 -> ARM 指令转译。 你建议看 wine ,能学到 x86-64 -> ARM 转译吗? |
9
levelworm OP @ysc3839 多谢,我又找了点资料,原来 m68k emulator 第一个版本就是读入一个 m68k 指令,然后转译成 PPC 的对应指令。我以前写过简单的 emulator ,但是直接是软件执行,而不是转成指令在本地执行,我研究一下。
|
10
xfn 48 天前 1
Rosetta 2 的高效转义主要得益于 Apple Silicon 系芯片的加持,芯片本身在转义过程中也做了一些工作,可以简单理解为 Apple Silicon 实现了一些 x86 芯片才有的特性( https://x.com/ErrataRob/status/1331736203402547201 )。Rosetta 2 并非 qemu 这样完全的直接指令转换。
|
11
DianQK 48 天前 2
https://github.com/ptitSeb/box64 应该是一个合适的参考项目?
|
12
nullyouraise 48 天前 1
我之前看到的一篇逆向 Rosetta 2 的文章 https://github.com/FFRI/ProjectChampollion
|
13
michaelzxp 48 天前
Rosetta 2 相比 Rosetta 1 效率提升让人惊艳,接近无缝过度
|
14
iamqk 48 天前 1
@Avn 3. 在 ARM 芯片的 macOS 上运行 x86 的 Windows 应用:需要 Wine 和 Rosetta
这个不成立吧? arm mac 下面无法运行 x86 的 windows 程序吧? 否则能够运行 window x86 的虚拟机早就开发出来了 现在只有 windows arm 的虚拟机 |
15
iOCZS 48 天前 1
操作系统为可执行文件提供了 API 和 loader 。
|
18
mmdsun 48 天前 1
还有微软 Prism ,也是相同的原理也可以研究一下,必要时候逆向
https://learn.microsoft.com/zh-cn/windows/arm/apps-on-arm-x86-emulation |
19
levelworm OP |
21
secondwtq 48 天前 1
dougallj.wordpress.com/2022/11/09/why-is-rosetta-2-fast Why is Rosetta 2 fast? | dougallj
|
23
DIMOJANG 47 天前 1
@Avn #17 巧了,我现在确实在这么用,其实性能还 OK 。拿来写嵌入式的代码是没啥问题的,就是烧录和编译的速度比较慢( M2 Air 8+256 ,windows 7 x64 )。( Parallels Desktop 模拟出来的 Arm windows 11 不支持 x86 的驱动,太差劲了)
|
24
LanhuaMa 47 天前 2
@Avn #1 Wine 是通过包装 API ,Rosetta 是通过转译 CPU 指令。你这话有点像,想学习 TCP/IP 通讯吗?没问题,你把 HTTP 搞懂了就可以了,这里面应该有类似的技术
|
25
wangyzj 47 天前 1
谢谢 OP 替我问出了我一直想问的问题
|
28
secondwtq 3 天前 1
@levelworm 抱歉,没仔细看贴。
> 第一次是在 Power PC 架构的苹果电脑 rom 里放置了一个 M68K emulator 。但是我找来找去也没找到源代码 这是 Binary Translation (以下简称 BT )这个领域的特点——对于外界技术社区来说可见性很低,可以说是超级冷门。技术手段上二进制翻译和编译器较为接近,然而在技术生态上这两个却是两个极端:任何一个合格的 C/Ç++ 程序员都对 UB 及编译器对其进行的优化有所了解,任何一个 Rust 拥趸都会吹捧 LLVM 的优化如何让 Rust 的性能追平甚至超越 C++,甚至路边抓一个 JavaScript 程序员都能说两句太伟大了 TurboFan 。 以上这些项目都是开源的,如果你对其中某一个部分有疑问,你可以去查源码,追断点,翻 git log 和 issue tracker 。但是 BT 完全不是这样,尤其是楼主所特指的,从一个特定 ISA 向另一个特定 ISA 的翻译,这种项目一般都是由某种确定的商业需求产生,并且会一直以商业闭源软件的形式存在。因为如果抛去自由软件运动所强调的“学习,修改软件的自由”,只谈现在主流的“商业开源”的话,实在没有什么开源这类项目的理由,毕竟开源社区对这类项目的需求相对来说很小——对于开源用户来说,想在不同的 ISA 上使用开源项目基本只要重新编译即可;而商业逻辑上,类似 Rosetta 这种项目(以下称为“类 Rosetta 项目”)本身就构成了厂商护城河的一部分,开源出去会直接损害厂商自身的优势(比如微软改一改就可以直接翻译 x86 Windows 应用,以低成本打通 WoA )。所以这是个典型的没啥开源价值,但是却有很大实际商业价值的东西。 不过事情总有例外,在“BT + 开源”这个交叉点,有至少两个例外: * 使用 BT 技术辅助开发。比如 QEMU 模拟运行其他 ISA 的程序,以及 Valgrind 和 DynamoRIO 这类分析程序执行过程的项目。这里面其实有更加微妙的问题,比如 Valgrind 和 DynamoRIO ,这种项目的架构大概是,有一个做 BT 的底层框架,这个框架会提供一个比较高层的 API ,然后基于这个框架可以写不同的 tool ,这个 tool 会调用框架的 API 来分析源程序代码并在合适的地方插入一些代码,比如 cachegrind 可能会在每个 load 指令旁边插一个模拟 cache 的函数调用,框架会帮你生成好新的代码并运行。这种项目的源程序和目标程序平台相同,只是行为不同,更准确地叫 DBI (Dynamic Binary Instrumentation),不过技术上和 DBT 很类似。QEMU 其实有类似的功能,不过做得比较简单并且大家一般不会用 QEMU 做这些事情,QEMU 有其他方面的问题,后面再说。还可以提一下的是 Linux kernel 的 uprobe 功能,因为是 kernel 实现的,所以在 BT 部分反而可以做得很简单,更像 debugger 。 * 开源游戏主机模拟器。这是真实存在,虽依然小众并且处于灰色地带但完全无法用其他方式解决的需求。对于爱好者来说,理论上这是一个不错的起点(特别是很多项目应该比较缺人)。不过我个人对它们的具体实现的了解比较有限,所以也无法评估这些项目与类 Rosetta 项目的具体区别。不过有几点是可以从表面的信息简单推导出来的,游戏主机很多,一个特定的机型很可能会因为以下某些特点导致其模拟器项目与类 Rosetta 项目有不同的侧重点:1) 硬件十分古老,就算是模拟开销很高对现代硬件也没有压力,所以不需要太多性能优化,而类 Rosetta 项目的源硬件和目标硬件平台之间绝对性能差距一般不超过五倍。比如 Citra 在最开始两年是没有 CPU 的 JIT 的,那时候 SKL 才刚出来。2) 需要精确到 cycle 的模拟,对其他指标构成了限制,这是由于很多主机程序是为非常确定的硬件开发,以及这些硬件本身的设计特点导致的问题,类 Rosetta 项目的源程序一般本身就能在同平台中不同性能特点的不同硬件上运行,不会有这方面的硬要求。3) 需要对 GPU shader 进行二进制翻译,这个同样是因为主机程序 target 到确定的硬件,shader 一般是直接编译到 GPU 指令,而类 Rosetta 项目的源程序由于没有固定的目标 GPU ,shader 一般是以源代码或某种目标无关 IR 表示,如果目标平台也有对应的 API 的话很有可能除了不同驱动的兼容 bug 外,不怎么需要处理 GPU 方面的问题。4) 平台架构设计思路上与现代主流平台差异过大(注意这里不是指简单的 ISA 差异),典型是 PS3 。类 Rosetta 项目一般都是现代主流平台之间的翻译。 如果你想找直接的开源代码,那么大概要从这两个地方入手。虽然由于上面的这么多因素,和你想要的类 Rosetta 项目并不一致,但是技术方向其实类似。我认为在 BT 方面,多看看处理类似问题的不同思路是有好处的。 回到商业闭源项目来,比较有名的项目除了苹果的 68k emulator ,Rosetta 和 Rosetta 2 以外,华为还有一个 ExaGear ,龙芯则有 LATX 和 LATA ,微软的 Prism 。 Rosetta 其实是来自外部的技术,原来叫 QuickTransit 。Mac 转 x86 之前,微软有个叫 Virtual PC 的东西是在 PPC Mac 上跑 x86 Windows ,也是买的外部的一个叫 Connectix 的公司。华为的 ExaGear 貌似一开始也是外面买来的。 游戏主机有时会提供向后兼容,当两代主机硬件架构有较大区别时,这个兼容有时是通过直接带上老主机的硬件实现的(如 PS3 对 PS2 的兼容),有时是通过软件模拟实现的,相当于官方模拟器(如 PS3 对 PS1 的兼容),可能有一些是通过混合手段实现的。另外有趣的是,有时候厂商也会占开源模拟器的便宜,比如一些游戏的移植版本就是用原版套了一层开源模拟器,索尼自己出了个 PS1 的炒冷饭版叫 PS Classic ,就是拿现在的硬件配上社区开发的 PCSX 模拟器的 ARM 版。此外也不能忽视独立的商业模拟器,PS1 时有一个商业模拟器叫 Bleem!,被索尼告了,官司拖垮了这个公司,但是贡献了主机模拟器技术本身不违法的判例。 还有一种路线很有意思,以前有个公司叫 Transmeta ,他们做的 CPU 不使用任何主流的 ISA ,而是自己开发了一个 ISA ,专为 BT 设计,然后在上面加一个叫 Code Morphing Software 的 BT 软件来跑 x86 软件。这样其实 BT 成了硬件不可或缺的一部分(或者说相当于把现代 OoO 架构中一部分挪到软件中),没了 BT 这个硬件基本等于 waste of sand 。这样可以做得很灵活,比如可以加入更多的 BT 支持不同的 ISA ,或者大改底层 ISA 但依然无缝兼容原来的应用,再或者通过更新软件来支持新版本的 ISA 或提升性能。Linus Torvalds 曾经在 Transmeta 上过工。后来 NVIDIA 自己的 ARM 微架构 Denver 采用了类似的思路。 最后,如果广义地来讲,由于类似 JVM 字节码也是二进制形式的,JVM 这类东西某种程度也可以被视为一种 BT 。但是 JVM 的语义要高级得多,更像一种编译器 IR ,所以 JVM 处理的问题和一般的 BT 是很不一样的,这方面现在更准确地说属于“高级语言虚拟机”这个领域。然而事情不是非黑即白的,比如 CUDA 代码一般会编译到 PTX ,PTX, SPIR-V 和 D3D Shader 这种 IR 就处于一种比 JVM 字节码更低级,但比汇编更高级的位置(特别地,前两者的寄存器是无限的虚拟寄存器)。CUDA 程序在运行前,会从 PTX 翻译为具体 GPU 的汇编。 相对于具体的代码,如果楼主想找一些技术资料,可能论文会更多。当然需要注意很多论文只是研究,不一定应用到产品中(倒不如说做产品的公司很少会直接发论文,但高校里有一些研究这个的组经常会发)。专利也可以挖一挖,不过专利会更难懂。 主机模拟器有自己的圈子,你要是能混进去可能能挖到一点东西。很多模拟器有公开的开发资料比如 blog 之类,有些模拟器会开 IRC 或 Discord ,然后开发者可能会在里面交流一些开发相关的内容( Yuzu 就是 Discord 里被抓的 ...)。至于商业项目,相关公司的从业者会互跳,应该有职业的圈子。但说实话,这些人在本站很难找。 www.jos.org.cn/jos/article/abstract/7099 二进制翻译技术综述 这篇论文粗略瞄了一下讲得还行,并且因为是综述,所以确实讲了很多商业项目的状况。从中可以发现像 DEC Alpha ,HP PA-RISC ,Intel IA-64 等已经淘汰的架构都曾经有过官方的 BT 方案。很多东西文章里面已经说得比较详细,我没必要重复。但是有几点需要解释: > 我在 ycombinator 也看到有人说 Total store ordering (TSO)是 Rosetta 2 高速最重要的原因,不过说实话没看懂是为什么。 虽然现代主流平台的基本原则都大同小异,但是一点小的细节差异就可能对 BT 有严重影响。当软件实在没有办法时,就只能在硬件上做兼容。 比如龙芯的论文 https://crad.ict.ac.cn/cn/article/pdf/preview/10.7544/issn1000-1239.202220196.pdf 龙芯指令系统架构技术 中举出了 x86 的 EFLAGS 特性翻译到 LoongArch 时的问题:如果使用 LoongArch ,x86 里一个简单的 ALU+Jcc 模式就需要使用一大段指令来模拟,加入了专门模拟 x86 EFLAGS 语义的指令后就只需要两条额外指令(不过我没太明白他们为什么还做了 x87 ,x87 在现代 x86 程序上是极少用到的,可能他们要跑一堆三叠纪屎山程序)。一开始的那个 dougallj 的文章也涉及了 Rosetta 2 在这里的工作。 TSO 也是类似的问题,ARM 默认的内存访问指令在同步语义上比 x86 更弱。如果一个内存访问可能会涉及线程同步,在 ARM 汇编里一般不会使用简单的 ldr/str 指令来表示,而 x86 汇编中就是一个普通的 mov ,如果直接把所有的 mov 翻译为 ldr/str ,那按照 ARM 默认的同步语义,可能就不会做线程同步了,翻译出来程序就会有正确性问题。而因为原代码中所有的内存访问都是 mov ,BT 是不知道哪个需要线程同步,哪个不需要的。 类似的问题还有 x86 所有的 mov 都是支持访问不对齐地址的,同样很麻烦,不过 ARM 似乎也支持,并且如果能够假设实际程序中不对齐访问较少的话,在 kernel 里模拟应该问题也不大。 这些都是很细节的地方,能在软件上解决的问题,往往是用一些 ad-hoc 的 trick (这就是为什么前面说“多看看处理类似问题的不同思路是有好处的”)。而硬件要做哪些一般是在产品规划时就要考虑好的,因为硬件上做改动成本和周期比软件要高的多。其实 BT 的性质决定了这个技术虽然一直存在,但是商业项目很多都是和硬件绑定的,临时性的,做到一定程度后就很难继续优化了,甚至不需要继续存在,比如 Rosetta 后来就被苹果拿掉了。这大概也是开源社区搞不起来的原因之一。 #2 > 看一下 Qemu 的源码吧。 QEMU 可以看,但不一定是最合适的项目。注意到类 Rosetta 项目一般是针对非常固定的平台的(比如上面刚刚提到一些问题需要非标准的硬件扩展来兼容),并且作为要用到生产上的商业项目,一般都是有明确的性能目标。前者会大大影响项目的设计思路,因为开源社区讲究跨平台,像 QEMU 这种项目需要从任意 ISA 翻译到任意的另一个 ISA ,实现时就会先翻译成 ISA 无关的 IR ,再生成目标代码,但是这样很难做精细的优化,牺牲了从特定 ISA 翻译到另一个特定 ISA 的性能,并且更难以利用硬件扩展。后者意味着商业项目本来就会在性能优化上花更多的功夫。像 Rosetta 2 这种尽量做到一对一的翻译,在 QEMU 的架构上很难实现。 另外 QEMU 是个很广的项目,还包括同架构虚拟化、系统虚拟化、设备虚拟化等,其中做 BT 的模块叫 TCG (T for Tiny),只是其中很小的一部分。 #6 > 不考虑动态链接、内存 layout 这些,理想情况是不是能先逆编译到 llvm ,再新编译到目标代码的时候重新分配寄存器、优化指令。 说的对,但“理想情况”是由理论自主研发的一款全新开放世界 ... 这个思路存在一些不可忽视的问题,上面那个论文里面提到了。最典型的是二进制文件丢失了太多的源文件信息,很难知道文件中哪部分是一个函数,这个函数接收几个参数,参数和返回值是什么类型之类的。另外上面说的 QEMU 的类似问题在这里也存在。还有 LLVM 本身不是特别快,会增大 BT 运行开销。 论文中给出了一些最近利用 LLVM 做 BT 的研究,但是目前大多数商业类 Rosetta 项目应该依然采取的是逐条翻译汇编指令的思路。 这里可以 cue 一下另一个冷门领域——反编译器,典型是 Hex-Rays ,GHIDRA ,Hopper Disassembler 和 Binary Ninja 等。一般反编译器的技术原理其实和它的名字差不多(不考虑用 LLM 反编译之类的混沌造物 ...),就是编译的过程反过来:先把汇编代码转换成一种平台无关的 IR ,然后会对 IR 进行优化和变换,然后变成更高级的 IR ,然后把高级 IR 变成高级语言伪代码。一般编译器把高级 IR 变成低级 IR 叫"lower",而反编译器里低级 IR 变成高级 IR 叫"lift"。这其实就是类似“用 LLVM 做 BT”的思路,但区别是反编译器不要求结果正确性(甚至大多数反编译出来的代码都不正确),对运行时间要求也低,而因为结果是平台无关的,天然要求引入平台无关 IR 。这方面参考: www.slideshare.net/slideshow/ilfak-guilfanov-decompiler-internals-microcode/90442266 Ilfak Guilfanov - Decompiler internals: Microcode [rooted2018] | PPT www.elastic.co/cn/security-labs/introduction-to-hexrays-decompilation-internals Hex-Rays 反编译内部原理简介 — Elastic 安全实验室 至于同时满足保证正确性和使用高级 IR 的也有,比如 SPIR-V 和 LLVM IR 是可以互转的,Mesa 的 IR 也是高级 IR ,所以你可以把 SPIR-V 转成 LLVM 或者 Mesa 的 IR ,然后在 GPU 上跑。不过这个前提也是 SPIR-V 自己是较高级的 IR ,保留了必要的信息。 > 问出了大海。。。说实话太难了,我还是看看早期类似的实现比较现实。Rosetta 2 估计大部分是汇编写的? 无论是编译器、BT 、DBI 、反编译器,概念上解决的问题很简单,即喂一种格式的代码输入,给出另一种格式的代码输出,也就是 f(x) = y 。 实现这个需要对汇编的了解,但并不需要直接使用汇编。当然你可以用汇编实现,一些早期的产品应该是这么做的,不过那只是因为早期几乎所有软件本来就要用汇编写。 不过这里需要注意的是和传统编译器与反编译器不同,DBT 和 DBI 等项目除了做上述的代码变换外,还需要一个较为复杂的获取原代码的过程,需要处理 SMC ,为了节省翻译开销,翻译完后的代码要做缓存等等,会涉及到较多的外围代码。这个其实跟高级语言虚拟机类似,除了做编译和优化外,还要做 GC 、模块的查找和载入等。而像是主机模拟器这种东西,很多精力可能要花在逆向主机环境、GPU 和其他设备支持、调试游戏模拟 bug 等工作上,其中 CPU ISA 的翻译也只是一部分。 |
29
levelworm OP @secondwtq #28
非常感谢,大佬的回答给的太详尽了。 惭愧,看了一圈资料之后,尤其是看了几个模拟器的代码之后,觉得的确是远远超过了自己的水平。我也就是一个写过一点 C ,知道一点底层资料,但是从来没怎么写过底层代码的 1/10 瓶水。 我也觉得模拟器可能比较容易切入一点。我写了个 LC-3 模拟器,这是最简单的 ISA 了。打算从这里入手,先把一些调试用的工具比如说内存窗口和代码转换窗口用 imgui 写出来,然后再去琢磨动态代码翻译。动态和静态相比有个缺点,就是无法实时确定跳转偏移,所以往往在真正跳转发生之后,要重新翻译,或者把之前翻译好的切成两段。 总而言之,现在觉得自己要是 20 岁就好了,这段时间最大的感受,就是看代码的能力很差,比如 dear imgui 这种无文档纯靠读代码的小型项目,我基本上是看不懂,只能说是 demo 的代码,看个大概,然后模仿和试错。40 岁挑这种项目来玩,的确是活受罪。好在是自己的东西,慢慢来吧。 |