Android-X86集成houdini所做的修改

Android-X86其实是基于Android的源码编译出来的一个X86版本,可以运行在X86系列处理器上。对于应用程序来说,如果全是使用Java语言编写的话,不会有什么问题。就算用到了JNI函数,如果编译出来了一个X86的动态库,并且包含在apk文件中,也不会有问题。要命就要命在有许多应用程序内部,只有ARM指令集的动态库,就不能在Android-X86平台下运行了,并且这类应用还不在少数。为了解决这类兼容性问题,Intel提供了一个叫做Houdini的库,可以在运行时动态转指令集,将ARM指令集翻译成X86指令集再运行,并且这全是在程序在运行的过程中动态翻译的,对应用程序来说完全透明。而且Google自己从5.0开始,也在其AOSP代码中加入了NativeBridge的中间层,帮助类似Houdini之类的应用更好的集成进Android系统中,代码改动量非常的小(具体可以参考《用于Android ART虚拟机JNI调用的NativeBridge介绍》这篇博客)。

但是,通过阅读android-x86-5.1_rc1的源码,以及查看打开NativeBridge下载下来的二进制文件(关于如何打开Android-X86的NativeBridge,可以参考《如何打开Android X86对houdini的支持》这篇博客),还是可以发现和普通的AOSP代码有一部分不一样,本文将对其做一个简单的罗列。

1)ro.dalvik.vm.native.bridge系统属性

这是一个在default.prop文件中定义的系统配置项,如果没有设置这个配置项,或者这个配置项被设置成了0,则Android系统将禁用NativeBridge,也就是禁用了Houdini。但是,如果将其设置成一个可以找到的动态库的名字,且这个动态库满足NativeBridge的规范,则Android系统将会使用这个动态库,初始化NativeBridge。这个完全是Android自己的行为,所有代码已经在AOSP中了。

对于Android-X86来说,这个配置项被设置成了libnb.so。

2)persist.sys.nativebridge系统属性

在Android-X86的“Settings->Apps compatibility”中,打开“Enable native bridge”选项,其实就会将系统属性persist.sys.nativebridge设置为1。

为什么要这样呢?我们可以简单看一下libnb.so里面的行为:

Android-X86集成houdini所做的修改_第1张图片

通过反汇编的代码,可以大致看出来,libnb.so中会读取persist.sys.nativebridge这个系统属性。如果为0的话,则输出内容是“Native bridge is disabled”的日志,然后直接退出;而如果是1的话,才会继续跳转到正常的路径去执行。而且,可以看出这是Houdini自己的行为,和AOSP无关。

3)ro.product.cpu.abilist系统属性(包括ro.product.cpu.abilist32系统属性)

这两个配置项定义在Android-X86系统中的build.prop配置文件中,它们都被设置成了下面的值:

ro.product.cpu.abilist=x86,armeabi-v7a,armeabi
ro.product.cpu.abilist32=x86,armeabi-v7a,armeabi

这个属性到底有什么用呢?其实它就对应的是Framework层的android.os.Build类内部的SUPPORTED_ABIS和SUPPORTED_32_BIT_ABIS(可以猜到属性ro.product.cpu.abilist64对应的是Build内的SUPPORTED_64_BIT_ABIS,但是Android-X86目前只是32位的,所以不需要改这项)。

也就是说这个属性值是一组列表,表示了当前系统支持哪些平台。而且,属性值的顺序也是有讲究的,第一个一定是x86,因为这个列表的第一个值表示本系统最兼容的那个平台,如果满足那第二个也可以,如果还是不行,第三个也凑合,如果全都不满足,则会报错。

系统中有许多地方用到了这个属性。最简单的,对于应用程序安装来说,如果其只包含了一个ARM指令集的JNI动态库,那么如果安装在一个只支持X86指令集的平台上是没有任何意义的,因为肯定运行不起来,这时候PackageManagerService会阻止你安装这个应用。但如果安装了Houdini,情况就不同了,可以通过动态转指令集的方法运行,这时就要通过修改这个属性值来告诉系统,我其实还是可以支持ARM的。

4)ro.dalvik.vm.isa.arm系统属性

这个配置项定义在Android-X86系统中的build.prop配置文件中,它被设置成了下面的值:

ro.dalvik.vm.isa.arm=x86

这个系统配置项只在PackageManagerService中(代码位于frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java文件中):

private static String getDexCodeInstructionSet(String sharedLibraryIsa) {
    String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa);
    return (dexCodeIsa.isEmpty() ? sharedLibraryIsa : dexCodeIsa);
}

private static String[] getDexCodeInstructionSets(String[] instructionSets) {
    ArraySet dexCodeInstructionSets = new ArraySet(instructionSets.length);
    for (String instructionSet : instructionSets) {
        dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet));
    }
    return dexCodeInstructionSets.toArray(new String[dexCodeInstructionSets.size()]);
}

public static String[] getAllDexCodeInstructionSets() {
    String[] supportedInstructionSets = new String[Build.SUPPORTED_ABIS.length];
    for (int i = 0; i < supportedInstructionSets.length; i++) {
        String abi = Build.SUPPORTED_ABIS[i];
        supportedInstructionSets[i] = VMRuntime.getInstructionSet(abi);
    }
    return getDexCodeInstructionSets(supportedInstructionSets);
}

这其实也是对支持指令集的一个修正。对于Android 5.0以上的系统来说,在安装的时候需要通过dex2oat试图将apk文件中的dex文件编译成包含本地指令集执行代码的oat文件,从而可以加快程序的执行。由于要编译成本地指令集,那当然是平台和程序本身支持几个就要编译几个。对于普通情况来说是没有问题的,但是对于像Houdini这种情况就不同了。Houdini只是用来对X86指令集的JNI动态库转成ARM指令集的,而dex还是应该用X86指令集来编译。因此,通过阅读getDexCodeInstructionCode的代码可以知道,对于arm指令集来说,参数传入的是“arm”字符串,因此要读取系统属性ro.dalvik.vm.isa.arm的值,而它被设置成了x86,因此这个函数会返回X86。所以这个选项的目的是告诉PackageManagerService,如果碰到ARM指令集,就将其修正成X86指令集,即:

ro.dalvik.vm.isa.=

这样的话,对于支持ARM指令集的平台来说,任然会用X86指令集编译dex文件,但会用Houdini执行ARM指令集的JNI动态库。

5)NativeBridge

这部分基本算是对原有AOSP代码最大的改动了,Houdini实现的NativeBridge的版本号升级成了2,且提供出来的函数多了两个。由于AOSP原生加入了NativeBridge的中间层,很好的把这部分逻辑抽象了出来,所以基本只需要更改NativeBridge部分的代码就好了,对ART虚拟机本身做的更改非常小。这个可以通过NativeBridgeCallbacks的定义看出来(代码位于system\core\include\nativebridge\native_bridge.h文件中):

struct NativeBridgeCallbacks {
  // Version number of the interface.
  uint32_t version;
  bool (*initialize)(const NativeBridgeRuntimeCallbacks* runtime_cbs, const char* private_dir,
                     const char* instruction_set);
  void* (*loadLibrary)(const char* libpath, int flag);
  void* (*getTrampoline)(void* handle, const char* name, const char* shorty, uint32_t len);
  bool (*isSupported)(const char* libpath);
  const struct NativeBridgeRuntimeValues* (*getAppEnv)(const char* instruction_set);
  bool (*isCompatibleWith)(uint32_t bridge_version);
  NativeBridgeSignalHandlerFn (*getSignalHandler)(int signal);
};

可以看出,libnb.so提供的回调函数变成了7个,而不是在AOSP中定义的5个,多出了两个。

再来看看libnb.so反编译后的结果:

Android-X86集成houdini所做的修改_第2张图片

可以看出,其版本变成了2。多出来的两个函数,一个叫做isCompatibleWith,一个叫做getSignalHandler。

我们先来看看isCompatibleWith有什么用:

static constexpr uint32_t kLibNativeBridgeVersion = 2;
......
static bool VersionCheck(const NativeBridgeCallbacks* cb) {
  if (cb == nullptr || cb->version == 0) {
    return false;
  }

  if (cb->version >= 2) {
    if (!callbacks->isCompatibleWith(kLibNativeBridgeVersion)) {
      return false;
    }
  }

  return true;
}

很简单,其实就是判断兼容性的一个函数,kLibNativeBridgeVersion表示的是Anroid系统NativeBridge的版本号,而cb->version表示的是提供转码功能的那个Houdini动态库的版本号。它们各自有各自的版本,因此需要isCompatibleWith函数来判断一下是否兼容。

对于getSignalHandler函数来说,用在这里(代码位于system\core\libnativebridge\native_bridge.cc文件中):

NativeBridgeSignalHandlerFn NativeBridgeGetSignalHandler(int signal) {
  if (NativeBridgeInitialized() && callbacks->version >= 2) {
    return callbacks->getSignalHandler(signal);
  }
  return nullptr;
}

只对版本号大于2才有效,试图从libnb.so中获得一个信号处理函数。调用这个函数的地方在这里(代码位于art\runtime\native_bridge_art_interface.cc文件中):

void InitializeNativeBridge(JNIEnv* env, const char* instruction_set) {
  if (android::InitializeNativeBridge(env, instruction_set)) {
    if (android::NativeBridgeGetVersion() >= 2U) {
#ifdef _NSIG
      for (int signal = 0; signal < _NSIG; ++signal) {
        android::NativeBridgeSignalHandlerFn fn = android::NativeBridgeGetSignalHandler(signal);
        if (fn != nullptr) {
          SetSpecialSignalHandlerFn(signal, fn);
        }
      }
#endif
    }
  }
}

在初始化NativeBridge之后会调用这个函数,它会从0开始到_NSIG(65),一个个从libnb.so中获得对应该信号的处理函数,并将其设置到进程中去。

你可能感兴趣的:(Android)