天天看點

Android on Linux(在Linux主機上運作Android可執行程式)概述使用靜态編譯的方式運作Android x86程式使用clone和chroot運作帶動态庫的Android X86程式使用libhoudini運作Android ARM程式

概述

本文主要介紹如何在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,開始編譯并運作:

[email protected]:jni$ ndk-build
[x86_64] Compile        : main <= main.c
[x86_64] Executable     : main
[x86_64] Install        : main => libs/x86_64/main
[email protected]:jni$ ../libs/x86_64/main
bash: ../libs/x86_64/main: 沒有那個檔案或目錄
           

此時運作報錯,我們來檢視一下原因:

[email protected]: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
[email protected]:jni$
[email protected]:jni$ gcc main.c
[email protected]: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

檔案的依賴:

[email protected]: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
[email protected]: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)
           

重新編譯,檢查依賴,然後運作。

[email protected]:jni$ ndk-build
[x86_64] Install        : main => libs/x86_64/main
[email protected]:jni$ readelf -a ../libs/x86_64/main | grep NEED
[email protected]: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

檔案夾。

[email protected]: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):

[email protected]:android_on_linux$ gcc android_on_linux.c
[email protected]: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

[email protected]:android_on_linux$ ndk-build
[arm64-v8a] Compile        : main <= main.c
[arm64-v8a] Executable     : main
[arm64-v8a] Install        : main => libs/arm64-v8a/main
[email protected]: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

執行:

[email protected]:android_on_linux$ gcc android_on_linux.c
[email protected]: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程式。

繼續閱讀