概述
本文主要介紹如何在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/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),而在linux主機上是使用的是/lib64/ld-linux-x86-64.so.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主機上運行了,但是實際專案依賴的庫較多,并不能全部都能靜態編譯,而且我們會更青睞使用動態庫的方式,接下來試試如何運行包含動態庫的可執行程式,
使用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的介紹,也可以參考:https://www.redhat.com/zh/topics/containers/whats-a-linux-container
在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執行:
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,將其直接解壓到上述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.uj5u.com/qianduan/170667.html
標籤:其他
上一篇:Android 引導頁
下一篇:下載指定版本的NDK
