摘要
安卓基于linux内核,其终端也能够运行一些ELF,然而与各大发行版linux不同,Android使用的是谷歌安卓自己的bionic。网上有一些使用Termux环境安装qemu-user运行linux elf的案例,但termux环境终究不是纯粹的android shell。我在之前的一篇文章浅浅地涉及了Android Native Bridge API,而目前基于此API的成熟项目都会配备binfmt-misc,提供像qemu-user那样的功能。如果没有特殊需求的话,那些产品的binfmt-misc功能更完善成熟,效率更高,因此更值得首选;而如果需要切换到开源实现的话,从qemu-user入手是个不错的选择。
回到此篇的话题,经过测试,qemu-user-static不经修改基本可以直接在Android终端运行ndk编译的elf程序,但在某些方面上仍存在问题。下面介绍踩坑。
1. 获取qemu-user-static
在这里我为了方便直接使用现成的,你也可以自行编译以更好地适应Android的环境,编译必须要让编译产物是静态、无外部链接依赖,应该还要让其PIE。
在此页面中定位到qemu-user-static一处,可以看到其deb预构建包的历史版本和与构建版本:https://packages.debian.org/search?keywords=qemu
选择一个版本,比如unstable,接着就可以选择能在我们自己系统上运行的架构,我主要在基于x86_64的安卓上运行,因此选择amd64,此外我还会用x86的32位、以及在手机上测试运行,分别对应的是i386和arm64。
在出来的页面下有很多ftp站点链接,建议挑选亚洲的,右键另存为进行下载。
下载deb包直接用7zip打开,定位到usr/bin,这里就有很多模拟目标架构的qemu-user-static程序可供选择了,在x86_64安卓上我们主要运行arm64和arm的程序,按实际需求选择即可,需要留意里边部分文件是软链接。
2. 初步运行
到这里其实你直接adb连上安卓,adb shell终端运行就完事了。首先adb的环境,连接,这里就不讲了,直接从adb操作开始。
既然是用户态翻译运行程序,首先我们得有一个异构程序,这里我使用xda上有人编译的没有外部库依赖的静态arm64版本adb和fastboot。链接:https://androidfilehost.com/?a=show&w=files&flid=50704
用adb将它们push进去,赋予权限,然后直接像图示那样,将运行adb64的命令作为qemu-user-static的参数,运行qemu-aarch64-static,可以看到程序正常运行了。
再看看fastboot,我还打印了linux内核信息。
很好,就这样结束了?
3. 配置依赖库
当然不是,如果要运行接下来的p7zip程序7zr的话,你就会发现它报错。
如果我们在linux上的话p7zip大概会链接到自身架构指定的那个链接器路径,而网上也介绍这种情况,需要下载程序对应架构所需要的环境,就像box86运行x86程序前前要补齐i386系统库。
那么这里的p7zip是一个ndk构建的专门在安卓上运行的elf程序,而它指明了linker的路径为/system/bin/linker64,显然,这个x86安卓里这个路径是一个x86_64版本的linker,而不是arm64的版本。我们查看一下qemu-aarch64-static的帮助:
其中选项:
-L path QEMU_LD_PREFIX set the elf interpreter prefix to 'path'
正好是我们需要的,我们需要在这个选项指定的目录,补充程序的系统库依赖。
好了,去哪找依赖呢,我的程序是arm64程序,当然在arm64安卓里了。
这里我们来偷个懒,之前我的文章就介绍了houdini64拥有相应arm64文件夹,里边的库等等正好是我们所需的arm64二进制文件。我的安卓是genymotion里的安卓12模拟器,那就直接去mumu12模拟器提取我们所需要的库文件和linker64(补充:linker64不能偷懒,此linker64是改过linker寻库路径的,这可能导致我们部分程序不能运行)。之后在我这个x86安卓的一个目录下,新建system→lib64,在里边存放库;新建system→bin,在里边存放linker64。我就直接把这个目录创建在/data/local/tmp/下取名tmp_arm64,这样方便改权限。(注,此处偷懒仅限houdini12及以下版本)
我们补全依赖,指定QEMU_LD_PREFIX后,7zr运行成功。
那,我的arm64手机上运行x86_64程序的依赖又该怎么办?
如果是复杂的程序,那很可能要从x86_64安卓模拟器的系统里照猫画虎,收集x86_64的linker和库。收集时几点需要注意的:
- ①从安卓10开始许多库转移到了apex中;
- ②建议对要运行的程序使用patchelf来查看其依赖,再查看依赖库的依赖库,来确保补全依赖;
- ③程序可能有自己的依赖库,参考网上方法使用-E设置LD_PRELOAD变量;
4. 补充说明
4.1 为何上述偷懒仅限houdini12及以下版本?
在houdini13之前,houdini可执行文件与libhoudini是分开的,houdini可执行文件就像一个独立的qemu-user-static,没有任何外部依赖,因此arm文件夹下先是包含houdini所需的arm库环境,然后里边的nb文件夹才是libhoudini的环境,nb文件夹中的库并不是原arm库,而是类似wrapper/proxy/thunking的魔改库。
这部分可以参考Berberis的介绍文档:https://cs.android.com/android/platform/superproject/main/+/main:frameworks/libs/binary_translation/docs/berberis_modules.pdf
在houdini13+和ndk_translation中,可执行文件变成需要依赖其对接Android Api的库了,因此猜测其arm库环境便混合在一起,不需要分成两套了。
4.2 可否在32位上模拟运行64位的架构?
在后边的环节中,我均无法实现,qemu-user-static的tcg会有以下报错:
../accel/tcg/user-exec.c:492: page_set_flags: Assertion `start < end' failed.
补充:可以,但存在部分程序有上述报错,可能与qemu有关;在某些环节,比如7z压缩,速度会异常慢,详见附录。
4.3 能否直接像注册houdini/ndk_translation那样注册binfmt_misc到安卓?
尽管我未测试,但我认为默认预构建的版本不能,在我实践过程中,默认QEMU_LD_PREFIX就不对了,且在后边出现了arm64平台上qemu-i386/arm-static无法直接运行,提示:
Unable to find a guest_base to satisfy all guest address mapping requirements 00000000-ffffffff
我需要修改GUEST_BASE才可让其运行。因此,qemu-user-static编译前仍需一些修改和配置编译参数,才可直接应用于安卓的binfmt_misc功能,尤其是指定其配套环境的位置,设置模拟的cpuinfo等。
4.4 box86/box64可以这么直接用吗?
截至当前box86/box64还未完全支持静态构建,因此termux环境对这俩是必要的;待它们能构建静态版本后我再测试。
5. p7zip跑分效率结果
声明:跑分仅作参考,影响因素与衡量因素是有很多的,此处计算效率取:目标测试分数 / 翻译器运行于何种架构上的原生分数。分数取最终结果里的Tot一栏的Rating MIPS值。
x86_64翻译运行arm64 (i5-1137g7 in vbox64, 4c4t)
翻译器/数据 | Native原生x86_64 | Houdini64 12.0.0 | QEMU x86_64 8.2.2 |
分数(取Tot) | 12371 | 7991 | 3317 |
效率比 | 100% | 64.6% | 26.8% |
x86_64/x86 翻译运行arm
翻译器/数据 | Native原生x86_64 | QEMU x86_64 8.2.2 |
分数(取Tot) | 12371 | 3262 |
效率比 | 100% | 26.4% |
翻译器/数据 | Native原生x86 | Houdini 12.0.0 | QEMU x86 8.2.2 |
分数(取Tot) | 11020 | 9066 | 2961 |
效率比 | 100% | 82.3% | 26.9% |
x86_64 翻译运行x86
翻译器/数据 | Native原生x86_64 | QEMU x86_64 8.2.2 |
分数(取Tot) | 12371 | 3860 |
效率比 | 100% | 31.2% |
arm64翻译运行x86_64/x86/arm(sdm865,8c8t) QEMU arm64 8.2.2
翻译器/数据 | Native原生arm64 | QEMU Guest: x86_64 | QEMU Guest: x86 | QEMU Guest: arm |
分数(取Tot) | 13667 | 2035 | 1494 | 2952 |
效率比 | 100% | 14.9% | 10.9% | 21.6% |
附录
Native原生运行 x86_64/ x86
转译参考:houdini12.0.0,需要其依赖的lib和linker,直接在mumu12模拟器环境就行了,64位翻译arm64,32位翻译arm
X86_64版qemu-user-static,翻译arm64,arm
X86版qemu-user-static,翻译arm
特殊:x86_64版翻译x86
Native原生运行 arm64/arm
arm64版qemu-user-static,翻译x86_64、x86、arm
补充:32位可以模拟64位架构,但7z压缩效率堪忧,解压效率倒是可以,很奇怪。
32位x86安卓10,7zr解压缩3个文件。使用qemu-user-static 8.2.2测试tcg翻译性能,time命令计时:
x86原生:压缩6.5s,解压:0.25s
arm翻译:压缩18.5s,解压:0.78s
x86_64翻译:压缩154.5s,解压:1.05s
arm64翻译:压缩442.8s,解压:1.17s
x86安卓10环境,模拟arm,x86_64,arm64
原生
arm
x86_64
arm64不测了,压缩分数估计只有个位数。
测试32位arm环境,安卓11,7zr压缩4个文件,使用qemu-user-static 8.2.5测试tcg翻译性能,time命令计时:
压缩:
arm原生:0.84s
x86翻译:773.56s
arm64翻译:遥遥无期
x86_64翻译:qemu报错,qemu9.0有signal11错误