0%

Apple Silicon Mac虚拟机中使用AMD64架构应用

前情提要

我现在工作有两台电脑,一台M1 Pro处理器的Macbook Pro,一台12700K CPU的台式机,macbook连到两个显示器,作为我的显示前端,台式机以无头模式运行,作为高性能后端。平常要用Windows的时候就用远程桌面的方式连接。

台式机安装的系统是Proxmox VE 8.0.4,在这个系统上通过KVM虚拟化运行着一个Ubuntu Server和一个Windows 11,Ubuntu系统主要用于平常AOSP系统的编译,虽然公司也有提供一个24核 32G内存的堡垒机作为编译服务器,但是感觉台式机的单核性能和固态硬盘速度更快,实际编译时比堡垒机更快,所以平常堡垒机基本闲置。同时又因为有给手机刷机的需求,要用高通提供的qfil软件(高通好像也有提供linux版本的qfil,但是我的ubuntu没有装桌面环境,也懒得去记刷机命令了),为了能够刷机,台式机的USB控制器直通到了Windows系统里,因此只有Windows系统能使用USB连接

最近工作中经常要跑谷歌要求的测试,主要是为了GMS认证相关的,谷歌提供的工具只能在Linux上跑,并且有些测试必须在USB ADB模式才能跑,在尝试本文的技术之前,我每次跑测试,都需要先将Windows和Ubuntu关机,然后将直通的PCIE USB控制器切换到Ubuntu里,然后开Ubuntu跑测试,跑完之后关闭Ubuntu,再将USB切换到Windows,再将两个系统开机,非常繁琐。

其他可行方案

台式机只安装Ubuntu,macbook再跑一个ARM64结构的Windows虚拟机

这种方案实现起来确实比较简单,但高通的工具写的跟个💩一样,在ARM64虚拟机里跑再转译x86/64,估计很难顶

使用USBIP的方式将Windows里的USB设备给Ubuntu使用

这种方案以前玩WSL的时候有使用过,确实比较方便,但是不知道什么原因,在我现在的架构下没跑起来,并且谷歌测试的时候会将手机重启,这种情况USBIP有识别不到设备的可能

台式机只安装Windows,通过WSL运行Ubuntu

理论上USB的问题解决了,但是平常台式机主要用来编译系统,嵌套WSL来编译担心性能损失比较大

最主要的是,方案1和方案3,台式机都是使用bare metal的安装方式,工作时如果不小心配置错了导致系统无法正常运行的话,不在公司都不太好处理,而用KVM虚拟化的方案,我远程也可以控制虚拟系统的开关机,甚至来个远程重装系统也不是大问题。

在mac上通过UTM qemu模拟x86_64架构的系统

虽然说不是不能用吧,但是因为是软件纯模拟,性能太差了……
[又不是不能用”表情包进化史,越来越抽象了

抽象的解决方案

敲代码、飞书、上网、go fishing:macOS
刷机、看高通日志:台式机的Windows虚拟机
高负载编译:台式机的Ubuntu虚拟机
跑VTS测试:macOS上Ubuntu虚拟机

解决方案如上,这样总体日常我继续使用我习惯的macOS,台式机的主要任务还是在虚拟的Ubuntu上跑编译,时不时远程桌面到Windows看日志和刷机。VTS测试的周期并不频繁,只是会在项目的中后期经常使用,用的时候再在macOS上启动Ubuntu虚拟机并直通USB设备即可。

但是现在mac使用apple silicon架构的cpu,基于arm64,而谷歌的vts测试工具是按amd64架构发布的,macOS上虚拟化出来的Ubuntu肯定是不能直接运行的。

好在macOS上有一个叫Rosetta的软件,这个软件是作为一个转译器伴随apple silicon设备发布的,它可以让曾经为Intel架构的mac软件,在不需要开发者重新编译打包的情况下,直接在新架构的mac上运行。

在macOS 13版本之后,如果虚拟机使用’Apple Hypervisor’作为虚拟机后端框架,就可以在Linux虚拟机上,使用Rosetta来运行x86_64架构的可执行程序,官方的链接:https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta

本文主要参考:https://mybyways.com/blog/using-rosetta-in-a-utm-linux-vm-with-docker-on-apple-silicon

安装Apple Hypervisor后端的虚拟机

如果从来没有使用过Rosetta的话,可能需要先进行安装
运行命令:softwareupdate --install-rosetta

本文使用UTM来创建虚拟机,本来是想直接将现有的QEMU后端的虚拟机改为Apple Hypervisor框架的,但似乎没有找到相关的选项,于是就重新装一个
首先创建的时候要选择虚拟化

然后选择Linux系统

然后选择使用Use Apple Hypervisor,勾选后会出现附加选项的弹框,勾选Enable Rosetta

然后正常选择需要启动的ISO镜像完成安装即可

配置Rosetta与APT源

  1. 创建Rosetta挂载的文件 sudo mkdir /media/rosetta
  2. 挂载Rosetta sudo mount -t virtiofs rosetta /media/rosetta
  3. 安装binfmt sudo apt install binfmt-support 这个工具可以帮助Linux识别二进制的格式
  4. 配置binfmt,对于x86_64架构的应用使用Rosetta运行
    1
    2
    3
    4
    sudo /usr/sbin/update-binfmts --install rosetta /media/rosetta/rosetta \
    --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 yes
  5. 配置Rosetta自动挂载 sudo vi /etc/fstab 在行尾添加内容rosetta /media/rosetta virtiofs ro,nofail 0 0 ,然后输入:wq 保存退出

现在重启系统后,使用df -h 应该可以看到Rosetta的挂载信息

1
2
3
4
5
magicdian@mdrosettaserver:~$ df -h
Filesystem Size Used Avail Use% Mounted on
...省略
rosetta 1.9T 1.3T 552G 71% /media/rosetta
...省略

但是此时系统还没有x86_64的软件库,因此我们要更新apt源,编辑/etc/apt/source.list添加以下信息

1
2
3
4
5
6
7
8
9
deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy main restricted
deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted
deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy universe
deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates universe
deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy multiverse
deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security main restricted
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security universe
deb [arch=amd64] http://security.ubuntu.com/ubuntu jammy-security multiverse

这样apt就知道要从这些地方获取amd64架构的软件包了

ADB咋跑不起来?

兴高采烈地配置完第二步,到谷歌下载了个最新版本的platform-tools,adb启动,结果报错

1
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2

大概意思就是找不到ld-linux-x86-64.so.2这个库,谷歌了一圈感觉没有讲怎么解决,但我猜测应该是系统没有带这个库导致的,安装即可,于是去搜了下发现这个库是包含在libc6这个软件包里的,那很明显安装amd64架构的libc6就好了

1
2
3
4
magicdian@mdrosettaserver:~$ apt list libc6
Listing... Done
libc6/jammy-updates,jammy-security,now 2.35-0ubuntu3.5 amd64 [installed]
libc6/jammy-updates,jammy-security,now 2.35-0ubuntu3.5 arm64 [installed,automatic]

通过apt list命令查找libc6软件包,可以看到有两种架构,我们使用sudo apt-get install libc6:amd64 来安装就可以了

然后adb就可以正常启动了

Geekbench启动

搞定了Rosetta肯定还是要跑个分的,结果不算特别好,但也不差,GB6单核心1023分,多核心5544分,相比原生macOS下的跑分,差不多打了5折…
不过还是比QEMU纯软件模拟的方式要高的,纯软模拟的x86_64直接跑不了GB6,超时结束了。

后记

文章写完了,为什么还要后记呢
因为抽象的苹果虽然提供了Hypervisor框架,但是这个框架似乎并没有考虑到虚拟机直通USB设备的需求,所以我折腾了大半天,x86的adb是跑起来了,但是并没有usb设备给我连接,实际上我除了以x86_64的架构跑了个分,并没有产生实际的应用,拜了个拜

Apple Hypervisor框架不支持USB直通?

一开始使用UTM工具来创建虚拟机,发现没有配置USB设备的选项,后来又尝试了Parallels Desktop这款付费虚拟机软件,发现虚拟机创建后,可以随时打开或关闭Rosetta的支持,并且安装Parallels tools后,会自动挂载Rosetta工具,只需要自己配置一下AMD64架构的源,安装一下libc6,基本就能用了。

到底是Apple Hypervisor本身就支持USB直通,还是Parallels Desktop这款付费软件额外做了一些开发,我不得而知,不过在github上能找到一个破解rosetta的库,从而可以在任何arm64的linux上运行AMD64的软件,https://github.com/CathyKMeow/rosetta-linux-asahi ,所以理论上来说,树莓派以及rockchip等芯片的开发板,也可以利用Rosetta。

Parallels Desktop下的Rosetta使用情况

也同样进行了跑分,跑分比UTM略弱一点点,考虑是误差吧,想尝试跑谷歌的VTS测试工具,但是报错,暂时没想到怎么解决,不过考虑到重新读VTS测试工具的说明书后,发现VTS测试工具可以直接跑在macOS上,我想我应该不会继续钻牛角尖用Rosetta来跑了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Android Vendor Test Suite 14_r3 (11237927)
Use "help" or "help all" to get more information on running commands.
This command is not for general use and should only be run as the result of a call to
ProcessBuilder.start() or Runtime.exec() in a java application
This command is not for general use and should only be run as the result of a call to
ProcessBuilder.start() or Runtime.exec() in a java application
01-09 11:37:52 E/adb: Cannot run program "adb": error=0, Failed to exec spawn helper: pid: 8497, exit value: 1
Console received an unexpected exception (shown below); shutting down TF.
java.lang.IllegalArgumentException: java.io.IOException: Cannot run program "adb": error=0, Failed to exec spawn helper: pid: 8497, exit value: 1
at com.android.ddmlib.AndroidDebugBridge.<init>(AndroidDebugBridge.java:875)
at com.android.ddmlib.AndroidDebugBridge.createBridge(AndroidDebugBridge.java:614)
at com.android.ddmlib.AndroidDebugBridge.createBridge(AndroidDebugBridge.java:566)
at com.android.tradefed.device.AndroidDebugBridgeWrapper.init(AndroidDebugBridgeWrapper.java:83)
at com.android.tradefed.device.DeviceManager.startAdbBridgeAndDependentServices(DeviceManager.java:324)
at com.android.tradefed.device.DeviceManager.init(DeviceManager.java:291)
at com.android.tradefed.device.DeviceManager.init(DeviceManager.java:220)
at com.android.tradefed.device.DeviceManager.init(DeviceManager.java:210)
at com.android.tradefed.command.CommandScheduler.initDeviceManager(CommandScheduler.java:1145)
at com.android.tradefed.command.CommandScheduler.start(CommandScheduler.java:1123)
at com.android.tradefed.command.Console.run(Console.java:1134)
at com.android.compatibility.common.tradefed.command.CompatibilityConsole.run(CompatibilityConsole.java:104)
Caused by: java.io.IOException: Cannot run program "adb": error=0, Failed to exec spawn helper: pid: 8497, exit value: 1
at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1143)
at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
at com.android.ddmlib.AndroidDebugBridge.lambda$runAdb$0(AndroidDebugBridge.java:954)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.IOException: error=0, Failed to exec spawn helper: pid: 8497, exit value: 1
at java.base/java.lang.ProcessImpl.forkAndExec(Native Method)
at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:314)
at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:244)
at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1110)
... 3 more
Exception in thread "TfConsole" java.lang.IllegalStateException: start() must be called before this method
at com.android.tradefed.command.CommandScheduler.assertStarted(CommandScheduler.java:2553)
at com.android.tradefed.command.CommandScheduler.doShutdown(CommandScheduler.java:2140)
at com.android.tradefed.command.CommandScheduler.shutdown(CommandScheduler.java:2129)
at com.android.tradefed.command.ICommandScheduler.shutdown(ICommandScheduler.java:171)
at com.android.tradefed.command.Console.run(Console.java:1201)
at com.android.compatibility.common.tradefed.command.CompatibilityConsole.run(CompatibilityConsole.java:104)

Rosetta on Linux,还有什么玩法?

目前看到比较多的是用来运行AMD64架构的Docker镜像,不过我感觉常用的工具软件现在基本也会有arm架构的版本,在DockerHub上大概搜了一下

  • WordPress
  • Jenkins
  • Gitea
  • Emby
  • Plex
  • Vaultwarden

这些我比较常用的镜像都是有arm64的版本的,所以感觉即便Linux上有Rosetta,好像实际用处也不大,可能主要还是针对一些,停更了比较长时间,但是仍然非常好用的软件工具,让他们可以在新架构下直接部署。搜索的过程中确实也看到一些没有arm64的镜像,所以有rosetta的话,arm linux也许也能用的更顺手吧

哦,还想试试微信

然后我打开了微信官网,发现根本就没有Linux版本的安装包

在DockerHub的images列表里帧的找了喝酒,才找到——confluence的镜像是没支持arm64的,最终尝试使用8.5-ubuntu-jdk17这个版本成功跑起来。本来以为能很轻松找到不支持arm64的镜像,没想到找了半天……这样一来,确实也想不到什么更合理的场景来使用转译了…