当前位置:首页 > CN2资讯 > 正文内容

android linux 工具 android on linux

1天前CN2资讯


概述

本文主要介绍如何在Linux(x86)主机上简单高效地运行一个Android可执行程序,主要使用类似LXC的技术,将Android可执行程序在容器中运行。首先会介绍如何运行Android x86程序,然后会介绍如何使用libhoudini运行Android ARM程序。文章中使用的程序可到https://gitee.com/cqupt/android_on_linux查看。

直接运行Android x86程序

运行静态编译的程序

我们先写一个简单的Android可执行程序,使用NDK编译,可以参考ndk-build的使用介绍。

// main.c #include <stdio.h> int main(int argc, char const *argv[]) { printf("hello world!\n"); return 0; }// Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= main.c LOCAL_MODULE := main include $(BUILD_EXECUTABLE)// 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

此时因为我们想直接在Linux x86上执行此程序,所以使用的ABI是x86_64,开始编译并运行:

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: 没有那个文件或目录

此时运行报错,我们来查看一下原因:

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/.2, for GNU/Linux 3.2.0, BuildID[sha1]=e53d68617f04135f77102f0e87226709ef80cb50, not stripped

我们使用file对比了ndk-build和主机gcc分别编译的文件发现,Android是使用/system/bin/linker64作为链接器(关于它的介绍:Android Linker),而在linux主机上是使用的是/lib64/.2(关于它的介绍:ld-linux.so)。它们的作用都是来加载动态库的。

查看../libs/x86_64/main文件的依赖:

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文件,使其静态编译。

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) // 新增 LOCAL_LDFLAGS := -static LOCAL_SRC_FILES:= main.c LOCAL_MODULE := main include $(BUILD_EXECUTABLE)

重新编译,检查依赖,然后运行。

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的位置:

ifeq ($(APP_ABI),x86_64) LOCAL_LDFLAGS += -Wl,--dynamic-linker=linker64 -Wl,-rpath=./ endif

重新编译后,准备好main所依赖的x86_64的动态库和linker64:

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 ,它是一款让android应用运行在PC上的服务平台(一个“Android模拟器”,之所以是加引号的模拟器,因为它使用不是模拟器,而是使用的LXC容器技术,从而能获得更加的性能)。之后又发现与另一个“Android模拟器”–Anbox,同样也是使用了LXC,我们有理由相信,它将是一个突破口。

什么是LXC?

LXC是Linux内核包含功能的用户空间接口。

当前的LXC使用以下内核功能来包含进程:

  • 内核名称空间(ipc,uts,mount,pid,网络和用户)
  • Apparmor和SELinux配置文件
  • Seccomp政策
  • chroots(使用pivot_root)
  • 内核功能
  • CGroups(对照组)
  • LXC容器通常被视为chroot和成熟的虚拟机之间的中间对象。LXC的目标是创建一个尽可能接近标准Linux安装环境的环境,而不需要单独的内核。


在LXC Chroot Cgroup Namespace文章中总结到:

LXC, LinuX Containers,它是一个加强版的Chroot。简单的说,LXC就是将不同的应用隔离开来,这有点类似于chroot,chroot是将应用隔离到一个虚拟的私有root下,而LXC在这之上更进了一步。LXC内部依赖Linux内核的3种隔离机制(isolation infrastructure):

  • Chroot
  • Cgroups
  • Namespaces

在DOCKER基础技术:LINUX NAMESPACE(上)文章中详细说明了Linux Namespace的使用。接下来跟着前人的步伐实践一下吧!

实践一下

参考DOCKER基础技术:LINUX NAMESPACE(上),我们需要准备好Android需要的rootfs文件夹。

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下载。

下面就开始写代码:

// android_on_linux.c #define _GNU_SOURCE #include <sched.h> #include <signal.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> #include <sys/mount.h> /* 定义一个给 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的方法免去sudo):

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()函数的介绍:

类似于fork()和vfork(),Linux特有的系统调用clone()也能创建一个新线程。与前两者不同的是,后者在进程创建期间对步骤的控制更为准确。

2、利用PID Namespace,使用了CLONE_NEWPID标志,进行PID隔离,还可以使用Mount namespaces、Network namespaces等,更多信息请参考:DOCKER基础技术:LINUX NAMESPACE(下)。

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平台兼容性的问题,github下载仓库libhoudini。

在如何打开Android X86对houdini的支持和Anbox手动安装ARM兼容库文章中都写了如何开启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运行

## 通过文件开始位置的特殊的字节来判断是否是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可执行程序。

// Application.mk APP_ABI := arm64-v8a ## APP_ABI := x86_64 APP_PLATFORM := android-19

重新编译,并拷贝文件到rootfs/system/bin/main_arm64中

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。

-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执行:

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程序。


    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/32262.html

    分享给朋友:

    “android linux 工具 android on linux” 的相关文章

    VPS主机如何选择?灵活性与性价比并存的最佳方案

    在当今信息化高速发展的时代,VPS主机成为了许多企业和个人用户的热门选择。那么,什么是VPS主机呢?它是通过虚拟化技术在一台物理服务器上创建的多个独立服务器。每个虚拟专用服务器(VPS)都具备自己的操作系统、CPU、内存和存储空间,用户可以像管理独立服务器一样灵活配置和控制自己的VPS,真是个便利的...

    如何开启BBR查询并提升TCP网络性能

    BBR(Bottleneck Bandwidth and Round-trip propagation time)是一种由Google开发的TCP拥塞控制算法,我对它的了解让我感到非常兴奋。BBR旨在通过精确的网络条件监测,以提高传输速度和稳定性。传统的拥塞控制算法往往依赖于丢包率的变化来调整传输速...

    高性能HKT VPS服务评测与应用指南

    HKT VPS概述 什么是HKT VPS HKT VPS其实就是基于香港HKT网络架构的虚拟专用服务器,提供了强大的性能和灵活的可配置性。我从多个服务商的不同产品中了解到,HKT VPS非常适合对网络速度和稳定性要求较高的用户。无论是游戏玩家还是企业用户,都能通过它享受到快速的上传和下载速度。 HK...

    怎么看VPS的路由好不好:评估与优化路由性能的方法

    如何评估VPS路由性能 VPS的路由性能对网站的加载速度和用户体验至关重要。评估VPS的路由性能,我通常会关注几个关键指标,包括延迟、丢包率和带宽。了解这些内容能够帮助我判断服务器能否在高流量时段保持稳定运行。 在这过程中,我特别重视使用一些专业的测试工具。这些工具可以帮助我全面了解VPS的网络性能...

    AMD EPYC 7002处理器:高性能与高能效的完美结合

    我一直对AMD EPYC 7002系列处理器充满兴趣。这款处理器是AMD公司最新推出的服务器处理器,确实让人感到兴奋。基于现代的Zen 2架构,这款处理器融合了先进的7nm制程工艺,投放市场后便以其高性能和高能效著称。随着数据中心和云计算需求的不断增加,EPYC 7002系列成了一个热议的话题,来看...

    阿里云国际:企业数字化转型的理想云服务解决方案

    阿里云国际概述 阿里云国际成立于2015年,致力于为全球用户提供高效、可靠、安全的云计算服务。作为阿里巴巴集团的一部分,阿里云国际不仅承载着国内市场的技术精华,也积极拓展国际市场。随着全球互联网的快速发展,企业对云服务的需求不断增加,阿里云国际顺应这一趋势,凭借创新的技术和丰富的经验,迅速在全球范围...