HACKER Q&A
📣 lisper

How does Rosetta 2 actually work?


Apple's docs say that Rosetta 2 works by translating x86 code into ARM64 code when an application starts up and then running the resulting ARM code natively. But that can't actually be true because if I use dlopen to load a dynamic library at runtime, the architecture of the library has to match the architecture of the executable that called dlopen or it results in an error (Error opening shared library foo.dylib : dlopen(foo.dylib, 0x000A): tried: 'foo.dylib' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')

So at a minimum, dlopen can invoke Rosetta again in this case. But why thrown an error? Why not allow an x86 application to open an ARM library if it is actually ARM code at run-time?

The situation is actually even weirder than that because there is an application (Clozure Common Lisp) that produces x86 code at run time but does not go through dlopen, it just writes x86 opcodes directly into memory (AFAIK). And yet, it works when run under Rosetta. How is that possible?

All of the evidence indicates to me that Rosetta is actually an emulator and not a translator. But why would Apple misrepresent this? It makes no sense.


  👤 saagarjha Accepted Answer ✓
Rosetta is an emulator, that is correct. It consists of both an ahead-of-time translator that does fairly straightforward conversion of x86 code into ARM, albeit with a 1-to-1 mapping between registers rather than following AAPCS or any standard calling convention. At runtime, if new code is generated (e.g. a JIT compiler) it’s run through what’s essentially a runtime version of this process. Self-modifying code is detected on write (code page is marked as read-only if it looks like it’s code, and the write faults and indicates the page should be marked with read-write permissions) or indirect branch (code has been written out and needs to be executed, so page is translated and flipped back to read-only). Calling into arm64 code directly is not possible without thunks due to the mismatched ABI. The system libraries are actually provided as a translated shared cache, rather than being mapped in as “normal” code.

👤 EdSchouten
> Why not allow an x86 application to open an ARM library if it is actually ARM code at run-time?

Because the Application Binary Interface (ABI) of ARM and x86-64 is different. Function calling conventions don't map 1:1, structure layouts may differ, exceptions can't be propagated properly, etc. etc. etc..

That's why Rosetta only allows you to mix code of a single architecture in a given address space, so that it can be emulated as a whole. That way all code that's being executed has the same ABI.


👤 matthewmacleod
The ABI used by a Rosetta-translated application is not the same as the native arm64 one - that’s why you can’t load an arm64 library. Then keeping track of memory which is executable allows trapping and translation on-the-fly.

Lots of useful information in this reverse-engineering article: https://ffri.github.io/ProjectChampollion/part1/


👤 58028641
Rosetta mainly does translation, but it does emulation for code produced at runtime.