# android_on_linux **Repository Path**: cqupt/android_on_linux ## Basic Information - **Project Name**: android_on_linux - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2020-10-11 - **Last Updated**: 2022-11-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 概述 本文主要介绍如何在Linux(x86)主机上简单高效地运行一个Android可执行程序,主要使用类似[LXC](https://linuxcontainers.org/)的技术,将Android可执行程序在容器中运行。首先会介绍如何运行Android x86程序,然后会介绍如何使用[libhoudini](https://github.com/Rprop/libhoudini)运行Android ARM程序。文章中使用的程序可到查看。 ## 直接运行Android x86程序 ### 运行静态编译的程序 我们先写一个简单的Android可执行程序,使用NDK编译,可以参考[ndk-build](https://developer.android.com/ndk/guides/ndk-build?hl=zh-cn)的使用介绍。 ```c // main.c #include int main(int argc, char const *argv[]) { printf("hello world!\n"); return 0; } ``` ```shell // Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= main.c LOCAL_MODULE := main include $(BUILD_EXECUTABLE) ``` ```shell // Application.mk ## APP_ABI := arm64-v8a APP_ABI := x86_64 APP_PLATFORM := android-19 ``` 关于Android ABI的介绍:[https://developer.android.com/ndk/guides/abis?hl=zh-cn](https://developer.android.com/ndk/guides/abis?hl=zh-cn) 此时因为我们想直接在Linux x86上执行此程序,所以使用的ABI是x86_64,开始编译并运行: ```bash chenls@chenls-PC:jni$ ndk-build [x86_64] Compile : main <= main.c [x86_64] Executable : main [x86_64] Install : main => libs/x86_64/main chenls@chenls-PC:jni$ ../libs/x86_64/main bash: ../libs/x86_64/main: 没有那个文件或目录 ``` 此时运行报错,我们来查看一下原因: ```shell chenls@chenls-PC:jni$ file ../libs/x86_64/main ../libs/x86_64/main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, BuildID[sha1]=c253c19cb84ea278fa41e8360fdf4f13a60d9d63, stripped chenls@chenls-PC:jni$ chenls@chenls-PC:jni$ gcc main.c chenls@chenls-PC:jni$ file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e53d68617f04135f77102f0e87226709ef80cb50, not stripped ``` 我们使用`file`对比了`ndk-build`和主机`gcc`分别编译的文件发现,Android是使用`/system/bin/linker64`作为链接器(关于它的介绍:[Android Linker](https://blog.csdn.net/dinuliang/article/details/5509009)),而在linux主机上是使用的是`/lib64/ld-linux-x86-64.so.2`(关于它的介绍:[ld-linux.so](https://www.cnblogs.com/kelamoyujuzhen/p/9823272.html))。它们的作用都是来加载动态库的。 查看`../libs/x86_64/main`文件的依赖: ```shell chenls@chenls-PC:jni$ readelf -a ../libs/x86_64/main | grep NEED [ 9] .gnu.version_r VERNEED 000000000000040c 0000040c 0x0000000000000001 (NEEDED) 共享库:[libc.so] 0x0000000000000001 (NEEDED) 共享库:[libm.so] 0x0000000000000001 (NEEDED) 共享库:[libstdc++.so] 0x0000000000000001 (NEEDED) 共享库:[libdl.so] 0x000000006ffffffe (VERNEED) 0x40c 0x000000006fffffff (VERNEEDNUM) 1 chenls@chenls-PC:jni$ ``` `../libs/x86_64/main`依赖了`libc.so`等其它动态库。因为Android与Linux使用了不能的链接器,导致Android程序在Linux无法正常加载动态库。此时我们可以尝试将此程序静态编译。 修改`Android.mk`文件,使其静态编译。 ```shell LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) // 新增 LOCAL_LDFLAGS := -static LOCAL_SRC_FILES:= main.c LOCAL_MODULE := main include $(BUILD_EXECUTABLE) ``` 重新编译,检查依赖,然后运行。 ```shell chenls@chenls-PC:jni$ ndk-build [x86_64] Install : main => libs/x86_64/main chenls@chenls-PC:jni$ readelf -a ../libs/x86_64/main | grep NEED chenls@chenls-PC:jni$ ../libs/x86_64/main hello world! ``` 此时一个简单的Android可执行程序能直接在Linux主机上运行了,但是实际项目依赖的库较多,并不能全部都能静态编译,而且我们会更青睐使用动态库的方式。接下来试试如何运行包含动态库的可执行程序。 ### 运行带动态库的程序 可以在Android.mk中增加链接参数,指定链接器`linker`的位置: ```bash ifeq ($(APP_ABI),x86_64) LOCAL_LDFLAGS += -Wl,--dynamic-linker=linker64 -Wl,-rpath=./ endif ``` 重新编译后,准备好`main`所依赖的**x86_64**的动态库和`linker64`: ```shell chenls@chenls-PC:android_on_linux$ tree linker_path/ tree linker_path/ ├── libc.so ├── libdl.so ├── libm.so ├── libstdc++.so ├── linker64 └── main 0 directories, 6 files ``` 这样就可以直接运行x86_64的包含动态库的可执行程序了,测试脚本可查看。 ## 使用clone和chroot运行Android X86程序 ### 技术要点 接下来这一步走得比较艰难,刚开始一直没有找到头绪,最后发现了[xDroid](https://www.linzhuotech.com/index.php/home/index/xdroid.html) ,它是一款让android应用运行在PC上的服务平台(一个“Android模拟器”,之所以是加引号的模拟器,因为它使用不是模拟器,而是使用的[LXC](https://linuxcontainers.org/)容器技术,从而能获得更加的性能)。之后又发现与另一个“Android模拟器”--[Anbox](https://anbox.io/),同样也是使用了LXC,我们有理由相信,它将是一个突破口。 什么是LXC? >LXC是Linux内核包含功能的用户空间接口。 当前的LXC使用以下内核功能来包含进程: >- 内核名称空间(ipc,uts,mount,pid,网络和用户) >- Apparmor和SELinux配置文件 >- Seccomp政策 >- chroots(使用pivot_root) >- 内核功能 >- CGroups(对照组) >- LXC容器通常被视为chroot和成熟的虚拟机之间的中间对象。LXC的目标是创建一个尽可能接近标准Linux安装环境的环境,而不需要单独的内核。 更多关于LXC的介绍,也可以参考:[https://www.redhat.com/zh/topics/containers/whats-a-linux-container](https://www.redhat.com/zh/topics/containers/whats-a-linux-container) 在[LXC Chroot Cgroup Namespace](https://www.dazhuanlan.com/2020/01/07/5e142e0549fc5/)文章中总结到: >LXC, LinuX Containers,它是一个加强版的Chroot。简单的说,LXC就是将不同的应用隔离开来,这有点类似于chroot,chroot是将应用隔离到一个虚拟的私有root下,而LXC在这之上更进了一步。LXC内部依赖Linux内核的3种隔离机制(isolation infrastructure): > >- Chroot >- Cgroups >- Namespaces 在[DOCKER基础技术:LINUX NAMESPACE(上)](https://coolshell.cn/articles/17010.html)文章中详细说明了`Linux Namespace`的使用。接下来跟着前人的步伐实践一下吧! ### 实践一下 参考[DOCKER基础技术:LINUX NAMESPACE(上)](https://coolshell.cn/articles/17010.html),我们需要准备好Android需要的`rootfs`文件夹。 ```shell chenls@chenls-PC:android_on_linux$ tree rootfs/ rootfs/ ├── proc └── system ├── bin │ ├── linker64 │ └── main └── lib64 ├── libc.so ├── libdl.so ├── libm.so └── libstdc++.so 4 directories, 8 files ``` 上述文件就是`main`程序(前面的示例代码,使用ndk-build非静态编译)必须所依赖的动态库和`linker64`,如果实际项目中依赖其它的库,需要再手动添加它们。另外这些库必须是Android X86平台中的,可以到[android-x86](https://www.android-x86.org/)下载。 下面就开始写代码: ```c // android_on_linux.c #define _GNU_SOURCE #include #include #include #include #include #include #include #include /* 定义一个给 clone 用的栈,栈大小10M */ #define STACK_SIZE (10 * 1024 * 1024) static char container_stack[STACK_SIZE]; char *container_args[] = {"/system/bin/main", NULL}; int container_main(void *arg) { printf("Container [%5d] - inside the container!\n", getpid()); if (mount("proc", "rootfs/proc", "proc", 0, NULL) != 0) { perror("proc"); } if (chdir("./rootfs") != 0 || chroot("./") != 0) { perror("chdir/chroot"); } printf("execv %s\n", container_args[0]); execv(container_args[0], container_args); perror("exec"); printf("Something's wrong! %s\n", container_args[0]); return 1; } int main(int argc, char const *argv[]) { printf("Parent [%5d] - start a container!\n", getpid()); int container_pid = clone(container_main, container_stack + STACK_SIZE, CLONE_NEWPID | SIGCHLD, NULL); waitpid(container_pid, NULL, 0); printf("Parent - container stopped!\n"); umount("rootfs/proc"); return 0; } ``` 编译`android_on_linux.c`,并使用`sudo ./a.out`执行,(这里需要sudo执行,可以参考[一种在Linux上运行时免root的方法](https://blog.csdn.net/u011057800/article/details/109403993)免去sudo): ```shell chenls@chenls-PC:android_on_linux$ gcc android_on_linux.c chenls@chenls-PC:android_on_linux$ sudo ./a.out 请输入密码 [sudo] chenls 的密码: 验证成功 Parent [ 6571] - start a container! Container [ 1] - inside the container! execv /system/bin/main hello world! Parent - container stopped! ``` 可以看到一切OK,上述代码中主要做了以下几件事: 1、使用了clone()函数开启新的进程,[系统调用clone()函数](https://blog.csdn.net/ren18281713749/article/details/94769023)的介绍: >类似于fork()和vfork(),Linux特有的系统调用clone()也能创建一个新线程。与前两者不同的是,后者在进程创建期间对步骤的控制更为准确。 2、利用PID Namespace,使用了`CLONE_NEWPID`标志,进行PID隔离,还可以使用Mount namespaces、Network namespaces等,更多信息请参考:[DOCKER基础技术:LINUX NAMESPACE(下)](https://coolshell.cn/articles/17029.html)。 3、`mount`主机的`proc`文件系统到`rootfs`的`proc`下。 4、使用了`chroot()`函数把`rootfs`目录作为根目录。 5、调用`/system/bin/main`开始执行。 至此我们主要使用了`clone`和`chroot`函数,运行了带动态库的Android x86程序,接下我们再探索一下如何运行Android ARM程序。 ## 使用libhoudini运行Android ARM程序 ### 技术要点 houdini的介绍: >houdini技术 是intel 研发的ARM binary translator,用于解决当前android部分native应用库兼容跑在x86架构上的技术,它的原理在于把ARM的二进制代码转译为X86指令集,使得可以在X86的CPU上执行。 更多信息请查看[关于houdini技术和android x86平台兼容性的问题](https://blog.csdn.net/rendong_yang/article/details/27701411),github下载仓库[libhoudini](https://github.com/Rprop/libhoudini)。 在[如何打开Android X86对houdini的支持](https://blog.csdn.net/Roland_Sun/article/details/49735601)和[Anbox手动安装ARM兼容库](https://leux.cn/doc/Anbox%E5%AE%89%E8%A3%85ARM%E5%85%BC%E5%AE%B9%E5%BA%93.html)文章中都写了如何开启houdini的支持。 在此总结成以下两点: 1、下载`libhoudini`兼容库并挂载到`/system/lib/arm(arm64)`目录下。 2、通过`binfmt_misc`设置将ARM的程序通过`houdini`来运行。 ### 实践一下 1、我们这里使用Android 7 64bit的兼容库,下载地址:[http://dl.android-x86.org/houdini/7_z/houdini.sfs](http://dl.android-x86.org/houdini/7_z/houdini.sfs),将其直接解压到上述`rootfs`文件夹的`/system/lib64/arm64`中。 2、可以通过`binfmt_misc`在其中设置使用`houdini`运行 ```shell ## 通过文件开始位置的特殊的字节来判断是否是ARM程序,是的话将其使用houdini来运行 sudo echo ':arm64_exe:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7::/system/lib64/arm64/houdini64:P' | tee -a /proc/sys/fs/binfmt_misc/register sudo echo ':arm64_dyn:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\xb7::/system/lib64/arm64/houdini64:P' | tee -a /proc/sys/fs/binfmt_misc/register ``` 但此处我们可以直接使用类似`/system/lib64/arm64/houdini64 /system/bin/main_arm64`命令来执行,因此不需要改动`binfmt_misc`。 修改`Application.mk`文件,编译arm64可执行程序。 ```shell // Application.mk APP_ABI := arm64-v8a ## APP_ABI := x86_64 APP_PLATFORM := android-19 ``` 重新编译,并拷贝文件到`rootfs/system/bin/main_arm64`中 ```shell chenls@chenls-PC:android_on_linux$ ndk-build [arm64-v8a] Compile : main <= main.c [arm64-v8a] Executable : main [arm64-v8a] Install : main => libs/arm64-v8a/main chenls@chenls-PC:android_on_linux$ cp libs/arm64-v8a/main rootfs/system/bin/main_arm64 ``` 修改`android_on_linux.c`文件,使用`houdini64`执行`main_arm64`。 ```c -char *container_args[] = {"/system/bin/main", NULL}; +char *container_args[] = {"/system/lib64/arm64/houdini64", "/system/bin/main_arm64"}; ``` 编译`android_on_linux.c`,并使用`sudo ./a.out`执行: ```shell chenls@chenls-PC:android_on_linux$ gcc android_on_linux.c chenls@chenls-PC:android_on_linux$ sudo ./a.out Parent [23725] - start a container! Container [ 1] - inside the container! execv /system/lib64/arm64/houdini64 hello world! Parent - container stopped! ``` 至此我们使用了`clone`和`chroot`函数加上`houdini64`相关库,运行了带动态库的Android ARM程序。测试脚本可查看