Skip to content

Commit 35329a7

Browse files
committed
Initial commit
0 parents  commit 35329a7

File tree

7 files changed

+300
-0
lines changed

7 files changed

+300
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__/
2+
venv*/
3+
/rosetta

README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# rosetta-multipass
2+
3+
Would you also like to run `amd64` binaries in your [Multipass](https://multipass.run/) VMs on an M1 Mac? This is a guide/tool to enable [Rosetta](https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta) without official support from Multipass.
4+
5+
## Rosetta Installation (macOS host)
6+
7+
First you need to [install Rosetta](https://osxdaily.com/2020/12/04/how-install-rosetta-2-apple-silicon-mac/) on your host system:
8+
9+
```sh
10+
softwareupdate --install-rosetta --agree-to-license
11+
```
12+
13+
You should now have the `rosetta` translator binary:
14+
15+
```sh
16+
% tree /Library/Apple/usr/libexec/oah
17+
/Library/Apple/usr/libexec/oah
18+
├── RosettaLinux
19+
│   └── rosetta # <- this one
20+
├── debugserver -> /usr/libexec/rosetta/debugserver
21+
├── libRosettaRuntime
22+
├── runtime -> /usr/libexec/rosetta/runtime
23+
└── translate_tool -> /usr/libexec/rosetta/translate_tool
24+
```
25+
26+
**Note**: If you do not get the `RosettaLinux/rosetta` binary, try following the [UTM Rosetta Guide](https://docs.getutm.app/advanced/rosetta/) first, which should install Rosetta for you.
27+
28+
## Rosetta Installation (Ubuntu guest)
29+
30+
Copy the `RosettaLinux/rosetta` binary to your guest VM. In this example the binary has been copied to `/bin/rosetta`, but any path should work. Make sure that you can run the binary from inside the VM:
31+
32+
```
33+
$ /bin/rosetta
34+
rosetta error: Rosetta is only intended to run on Apple Silicon with a macOS host using Virtualization.framework with Rosetta mode enabled
35+
Trace/breakpoint trap (core dumped)
36+
```
37+
38+
The error is expected, you can read more about it in a [Quick look at Rosetta on Linux](https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/).
39+
40+
### Using `mount-rosetta.py`
41+
42+
To fix the error you can create a FUSE mount using `mount-rosetta.py`, which implements the `ioctl` required for the Rosetta handshake:
43+
44+
```sh
45+
sudo apt install libfuse2
46+
sudo python -m pip install fusepy
47+
```
48+
49+
**Note**: To mount without root, edit `/etc/fuse.conf` and uncomment `user_allow_other`. This shouldn't be necessary unless you are developing the script.
50+
51+
Now you can run the script:
52+
53+
```sh
54+
sudo python mount-rosetta.py /bin/rosetta
55+
```
56+
57+
This will shadow the `/bin/rosetta` binary and handle the necessary `ioctl` calls. To confirm you you can run:
58+
59+
```sh
60+
$ /bin/rosetta
61+
Usage: rosetta <x86_64 ELF to run>
62+
63+
Optional environment variables:
64+
ROSETTA_DEBUGSERVER_PORT wait for a debugger connection on given port
65+
66+
version: Rosetta-289.7
67+
```
68+
69+
**Note**: You will have to run `mount-rosetta.py` in the background.
70+
71+
### Installing the binfmt handler (guest)
72+
73+
To register `rosetta` for `amd64` binaries:
74+
75+
```
76+
sudo apt install binfmt-support
77+
sudo update-binfmts --install rosetta /bin/rosetta \
78+
--magic "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00" \
79+
--mask "\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff" \
80+
--credentials yes --preserve no --fix-binary no
81+
```
82+
83+
**Important**: If you want to use `--fix-binary yes` you will have to run `mount-rosetta.py` before the `update-binfmts` command.
84+
85+
### Running an `amd64` executable
86+
87+
You should now be able to execute a statically linked executable:
88+
89+
```sh
90+
$ ./tests/main-static
91+
Hello from Rosetta!
92+
```
93+
94+
### Creating a service
95+
96+
You can create `/etc/systemd/system/rosetta.service`:
97+
98+
```
99+
[Unit]
100+
Description=Rosetta Mount Service
101+
102+
[Service]
103+
ExecStart=/usr/bin/python3 /home/ubuntu/.local/bin/mount-rosetta.py /bin/rosetta
104+
Environment=PYTHONUNBUFFERED=1
105+
Restart=on-failure
106+
107+
[Install]
108+
WantedBy=default.target
109+
```
110+
111+
See [here](https://github.com/torfsen/python-systemd-tutorial) for more information.
112+
113+
```
114+
sudo systemctl list-unit-files | grep rosetta
115+
sudo systemctl enable rosetta.service
116+
sudo systemctl start rosetta.service
117+
```
118+
119+
## Installing shared libraries (experimental)
120+
121+
**Warning**: This section is super experimental and unlikely to yield great results. That being said, it is possible to run clang compiled for `amd64` and build something.
122+
123+
Running a dynamically linked binary will not work properly at this point:
124+
125+
```
126+
$ ./tests/main-dynamic
127+
./tests/main-dynamic: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
128+
```
129+
130+
To fix this (only tested on Ubuntu 22.04) you can run the following commands:
131+
132+
```
133+
sudo apt install g++-multilib-x86-64-linux-gnu gcc-multilib-x86-64-linux-gnu
134+
sudo ln -s /usr/x86_64-linux-gnu/lib64 /lib64
135+
export LD_LIBRARY_PATH=/usr/x86_64-linux-gnu/lib
136+
```
137+
138+
At this point things are working:
139+
140+
```
141+
./tests/main-dynamic
142+
Hello from Rosetta!
143+
```
144+
145+
**Note**: For other distributions you can search for `multilib` or `multiarch` packages or look for guides on getting qemu usermode emulation to work.
146+
147+
### Fixing some dynamic linker errors
148+
149+
Since `/usr/x86_64-linux-gnu` is meant for compiling things and not running you might have to replace some `.so` files that are actually linker scripts with the binaries:
150+
151+
```
152+
$ sudo -i
153+
$ cd /usr/x86_64-linux-gnu/lib
154+
$ rg GROUP # lists fake files
155+
libc.so
156+
5:GROUP ( /usr/x86_64-linux-gnu/lib/libc.so.6 /usr/x86_64-linux-gnu/lib/libc_nonshared.a AS_NEEDED ( /usr/x86_64-linux-gnu/lib64/ld-linux-x86-64.so.2 ) )
157+
libm.so
158+
4:GROUP ( /usr/x86_64-linux-gnu/lib/libm.so.6 AS_NEEDED ( /usr/x86_64-linux-gnu/lib/libmvec.so.1 ) )
159+
160+
$ mv libc.so libc.so.script
161+
$ cp libc.so.6 libc.so
162+
$ mv libm.so libm.so.script
163+
$ cp libm.so.6 libm.so
164+
```
165+
166+
### Cross-compiling libraries
167+
168+
If you need to install something like [zlib](https://zlib.net) for `amd64` you can use [Zig](https://zig.news/kristoff/cross-compile-a-c-c-project-with-zig-3599) for easy cross compiling:
169+
170+
```
171+
sudo snap install zig --classic --edge
172+
git clone https://github.com/madler/zlib
173+
cd zlib
174+
CC="zig cc -target x86_64-linux-musl" CXX="zig c++ -target x86_64-linux-musl" AR="zig ar" RANLIB="zig ranlib" uname_S="Linux" uname_M="x86_64" C11_ATOMIC=yes USE_JEMALLOC=no USE_SYSTEMD=no ./configure --prefix /usr/x86_64-linux-gnu
175+
make
176+
sudo make install
177+
```
178+
179+
For CMake project you can use [zig-cross](https://github.com/mrexodia/zig-cross) as a base.
180+
181+
### Multithreading
182+
183+
According to [Quick look at Rosetta on Linux](https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/) you might run into issues if the VM is not running in Total Store Ordering (TSO) mode. The solution should be to wrap `rosetta` in a `taskset` command during `update-binfmts`. This was not explored further.

mount-rosetta.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import sys
5+
import errno
6+
import ctypes
7+
import argparse
8+
import logging
9+
import stat
10+
11+
from fuse import FUSE, FuseOSError, Operations
12+
13+
# References:
14+
# - https://github.com/libfuse/libfuse/blob/master/example/hello.c
15+
# - https://github.com/fusepy/fusepy/blob/master/examples/memory.py
16+
# - https://github.com/fusepy/fusepy/blob/master/examples/ioctl.py
17+
# - https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/
18+
19+
class RosettaFS(Operations):
20+
log = logging.getLogger('RosettaFS')
21+
22+
def __init__(self, rosetta_binary: str):
23+
# Virtualization check constants (https://threedots.ovh/blog/2022/06/quick-look-at-rosetta-on-linux/)
24+
self.ioctl_cmd = 0x80456122
25+
self.handshake = b"Our hard work\nby these words guarded\nplease don\'t steal\n\xc2\xa9 Apple Inc\0"
26+
27+
# Load the contents of the original binary
28+
with open(rosetta_binary, "rb") as f:
29+
self.data = f.read()
30+
if self.handshake not in self.data:
31+
self.log.warning(f"Could not find handshake in binary. Either you are mounting the wrong binary or the virtualization check changed")
32+
33+
# Proxy file attributes
34+
st = os.lstat(rosetta_binary)
35+
self.attr = dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
36+
'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
37+
self.attr["st_mode"] = stat.S_IFREG | 0o755
38+
self.attr["st_nlink"] = 1
39+
40+
self.log.info(f"Created mount, to test you can run: {rosetta_binary}. To register the binfmt handler:")
41+
self.log.info(f"sudo update-binfmts --install rosetta {rosetta_binary} --magic \"\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x3e\\x00\" --mask \"\\xff\\xff\\xff\\xff\\xff\\xfe\\xfe\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff\" --credentials yes --preserve no --fix-binary no")
42+
43+
def __call__(self, op, path, *args):
44+
def truncate(s):
45+
if len(s) > 256:
46+
return s[:256] + "..."
47+
return s
48+
49+
self.log.debug('-> %s %s %s', op, path, truncate(repr(args)))
50+
51+
ret = '[Unhandled Exception]'
52+
try:
53+
ret = getattr(self, op)(path, *args)
54+
return ret
55+
except OSError as e:
56+
ret = str(e)
57+
raise
58+
finally:
59+
self.log.debug('<- %s %s', op, truncate(repr(ret)))
60+
61+
def readdir(self, path, fh):
62+
raise FuseOSError(errno.ENOTDIR)
63+
64+
def getattr(self, path, fh=None):
65+
if path == "/":
66+
return self.attr
67+
raise FuseOSError(errno.ENOENT)
68+
69+
def open(self, path, flags):
70+
if path == "/":
71+
return 1
72+
raise FuseOSError(errno.ENOENT)
73+
74+
def read(self, path, size, offset, fh):
75+
if path == "/":
76+
return self.data[offset:offset + size]
77+
raise FuseOSError(errno.EIO)
78+
79+
def ioctl(self, path, cmd, arg, fip, flags, data):
80+
if path == "/":
81+
if cmd == self.ioctl_cmd:
82+
self.log.debug(f"Handling rosetta handshake")
83+
ctypes.memmove(data, self.handshake, len(self.handshake))
84+
return 0
85+
else:
86+
self.log.error(f"Unsupported ioctl({hex(cmd)}) -> Probably the virtualization check changed")
87+
88+
raise FuseOSError(errno.ENOTTY)
89+
90+
def main():
91+
parser = argparse.ArgumentParser("mount-rosetta")
92+
parser.add_argument("rosetta_binary", help="Path to the rosetta binary to mount")
93+
parser.add_argument('--debug', help="Enable debug logging", action=argparse.BooleanOptionalAction)
94+
args = parser.parse_args()
95+
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
96+
binary = args.rosetta_binary
97+
if not os.path.isfile(binary):
98+
raise FileNotFoundError(f"Rosetta binary not found: {binary}")
99+
FUSE(RosettaFS(binary), binary, nothreads=True, foreground=True, allow_other=True, nonempty=True)
100+
101+
if __name__ == "__main__":
102+
main()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fusepy

tests/main-dynamic

5.12 KB
Binary file not shown.

tests/main-static

12.8 KB
Binary file not shown.

tests/main.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <stdio.h>
2+
3+
/* Compilation:
4+
zig cc -target x86_64-linux-musl -static main.c -o main-static
5+
zig cc -target x86_64-linux-gnu main.c -o main-dynamic
6+
*/
7+
8+
int main()
9+
{
10+
puts("Hello from Rosetta!");
11+
}

0 commit comments

Comments
 (0)