用于Android ART虚拟机JNI调用的NativeBridge介绍

有一个项目叫做Android-X86,它是一个可以在X86平台上运行的Android系统。目前许多市面上销售的Intel Atom处理器的平板电脑,就是使用的这个系统。对于普通用Java代码编写的程序来说,理论上在Android-X86平台上运行是没有任何问题的。但是,如果你的程序包含JNI函数,并且还只用ARM编译器编译,那么在Android-X86系统上肯定跑不起来,X86处理器怎么可能会执行ARM指令呢。但是,真的有很多应用是有ARM版的.so库的,这就会造成Intel处理器平板的兼容性问题。好在Intel意识到了这个问题,提供了一个叫做houdini的模块,可以让X86处理器动态解释执行ARM指令集。但是,要把它集成进现有的ART虚拟机中,需要处理许多繁琐的逻辑,直觉上来说肯定要对原有代码做比较大的修改。

不过,真正查看源码的时候才发现,从Android从5.0开始,在其ART虚拟机的实现中,引入了一个叫做NativeBridge的中间模块。这个模块基本上就是为了在JNI调用时进行动态转码用的,自带了基本上所有的处理逻辑。因此,实际上在Android-X86项目中集成进houdini所修改的代码其实很少。由于最近在研究Android-X86以及houdini,对这部分有一点研究,所以本文下面将对这个NativeBridge做一个简单的介绍。

首先,我们来看看NativeBridge的初始化过程。大家知道,ART虚拟机的初始化是在Runtime::Init函数中进行的,在这个函数的最后,有下面这行代码(代码位于art\runtime\runtime.cc文件中):

bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
    ......
    is_native_bridge_loaded_ = LoadNativeBridge(options->native_bridge_library_filename_);
    ......
}

可以看到在初始化的最后,调用了LoadNativeBridge函数,并传入了一个文件名作为参数(代码位于art\runtime\native_bridge_art_interface.cc文件中):

bool LoadNativeBridge(std::string& native_bridge_library_filename) {
  return android::LoadNativeBridge(native_bridge_library_filename.c_str(),
                                   &native_bridge_art_callbacks_);
}

这个函数基本上没干什么,直接调用了android::LoadNativeBridge函数,不过多带了一个参数native_bridge_art_callbacks_,看名字应该可以猜出来是一组回调函数表(代码位于art\runtime\native_bridge_art_interface.cc文件中):

static android::NativeBridgeRuntimeCallbacks native_bridge_art_callbacks_ {
  GetMethodShorty, GetNativeMethodCount, GetNativeMethods
};

GetMethodShorty、GetNativeMethodCount和GetNativeMethods都是定义在native_bridge_art_interface.cc文件中的函数,而android::NativeBridgeRuntimeCallbacks的定义如下(代码位于system\core\include\nativebridge\native_bridge.h文件中):

struct NativeBridgeRuntimeCallbacks {
  const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid);
  uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz);
  uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods,
                               uint32_t method_count);
};

这三个回调函数的功能如下:

1)getMethodShorty:获得指定JNI方法的短描述符;

2)getNativeMethodCount:获得指定类内部定义的JNI函数的个数;

3)getNativeMethods:获得指定类内部的method_count个JNI方法。

接下来,我们继续来看看android::LoadNativeBridge函数做了些什么(代码位于system\core\libnativebridge\native_bridge.cc文件中):

bool LoadNativeBridge(const char* nb_library_filename,
                      const NativeBridgeRuntimeCallbacks* runtime_cbs) {
  if (state != NativeBridgeState::kNotSetup) {
    if (nb_library_filename != nullptr) {
      ALOGW("Called LoadNativeBridge for an already set up native bridge. State is %s.",
            GetNativeBridgeStateString(state));
    }
    had_error = true;
    return false;
  }

  if (nb_library_filename == nullptr || *nb_library_filename == 0) {
    CloseNativeBridge(false);
    return false;
  } else {
    if (!NativeBridgeNameAcceptable(nb_library_filename)) {
      CloseNativeBridge(true);
    } else {
      void* handle = dlopen(nb_library_filename, RTLD_LAZY);
      if (handle != nullptr) {
        callbacks = reinterpret_cast(dlsym(handle,
                                                                   kNativeBridgeInterfaceSymbol));
        if (callbacks != nullptr) {
          if (VersionCheck(callbacks)) {
            native_bridge_handle = handle;
          } else {
            callbacks = nullptr;
            dlclose(handle);
            ALOGW("Unsupported native bridge interface.");
          }
        } else {
          dlclose(handle);
        }
      }

      if (callbacks == nullptr) {
        CloseNativeBridge(true);
      } else {
        runtime_callbacks = runtime_cbs;
        state = NativeBridgeState::kOpened;
      }
    }
    return state == NativeBridgeState::kOpened;
  }
}

首先是看一下是否已经初始化过NativeBridge了,如果已经初始化过了的话直接报错返回。再检查传入的文件名是否为空,且是否满足格式的要求,即名字的首字符必须是字母(大小写都可以),后面的字符可以是字母、数字或者点(“.”)、下划线(“_”)或连字符(“-”)(代码位于system\core\libnativebridge\native_bridge.cc文件中):

static bool CharacterAllowed(char c, bool first) {
  if (first) {
    return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
  } else {
    return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') ||
           (c == '.') || (c == '_') || (c == '-');
  }
}

如果文件名满足要求,接着调用dlopen打开文件名指定的那个动态链接库(所以文件名应该是以.so结尾的)。如果打开成功的话,再调用dlsym,在这个动态链接库中查找由kNativeBridgeInterfaceSymbol指定的那个符号的地址,并将其强制转换成指向NativeBridgeCallbacks结构体的指针。kNativeBridgeInterfaceSymbol变量的定义如下(代码位于system\core\libnativebridge\native_bridge.cc文件中):

static constexpr const char* kNativeBridgeInterfaceSymbol = "NativeBridgeItf";

而NativeBridgeCallbacks结构体的定义如下(代码位于system\core\include\nativebridge\native_bridge.h文件中):

struct NativeBridgeCallbacks {
  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);
};

所以,所谓的NativeBridge应该是一个动态链接库,这个动态链接库中必须要包含一个符号名字为“NativeBridgeItf”的结构体,这个结构体的前4个字节表示这个NativeBridge的版本号。实际上VersionCheck函数就是看一下这个版本号是否和自己定义的版本号一致的(代码位于system\core\libnativebridge\native_bridge.cc文件中):

static constexpr uint32_t kNativeBridgeCallbackVersion = 1;
......
static bool VersionCheck(NativeBridgeCallbacks* cb) {
  return cb != nullptr && cb->version == kNativeBridgeCallbackVersion;
}

所以版本号必须为1,否则不兼容。这个结构体后面接着的5个4字节,分别是五个指向函数的指针,这些函数都是在实现动态转指令集功能的动态库中提供的,它们的功能如下:

1)initialize:顾名思义,就是用来做初始化的,初始化的时候要传入前面提到的由ART虚拟机提供的几个函数,包含在android::NativeBridgeRuntimeCallbacks结构体中;

2)loadLibrary:加载一个指定路径的动态库;

3)getTrampoline:获得一个跳转函数,通过它再调用那个不同指令集的真正的函数,这样对于虚拟机来说,根本就不用管是否有NativeBridge的存在了,因为转指令集对其来说完全是透明的;

4)isSupported:查看一个指定路径的动态库是否可以用这个转指令库进行指令转换;

5)getAppEnv:获得这个动态转指令集动态库所支持的平台信息。

在后面碰到它们的时候,还会一一详细解释。

还有一个问题,实现动态转指令集功能的动态链接库文件名是在哪指定的呢?在Runtime::Init函数中,调用LoadNativeBridge的时候,传入的是options->native_bridge_library_filename_,通过进一步阅读代码可以发现,这个选项是在AndroidRuntime::startVm函数中(代码位于frameworks\base\core\jni\AndroidRuntime.cpp文件中):

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
    ......
    if (libart) {
        ......
        property_get("ro.dalvik.vm.native.bridge", propBuf, "");
        if (propBuf[0] == '\0') {
            ALOGW("ro.dalvik.vm.native.bridge is not expected to be empty");
        } else if (strcmp(propBuf, "0") != 0) {
            snprintf(nativeBridgeLibrary, sizeof("-XX:NativeBridge=") + PROPERTY_VALUE_MAX,
                     "-XX:NativeBridge=%s", propBuf);
            addOption(nativeBridgeLibrary);
        }
    }
    ......
}    

所以,其实是读的“ro.dalvik.vm.native.bridge”这个系统属性的值,而它一般会被定义在文件系统根目录下的default.prop文件中。如果这个属性值根本没被定义,或者被定义为0,则表示禁用NativeBridge;而如果被定义为一个可以被找到的动态链接库的文件名(一般是放在/system/lib目录下的),则表示打开NativeBridge(当然这个动态链接库必须要满足上面说的那些条件)。

通过阅读以上的代码,可以发现NativeBridge的动态库是在ART虚拟机启动的时候就已经被加载进来的,当然包括所有应用程序的父进程,即Zygote进程。也就是说只要你在那个系统属性中指定了,那么这个NativeBridge在系统一启动的时候就会被加载进来。

好了,我们接下来看,当要启动一个应用程序的时候,都是从Zygote进程fork出来的,具体的fork动作是在ForkAndSpecializeCommon这个JNI函数来实现的,在这个函数中也相应加入了对NativeBridge的支持代码(代码位于framworks\base\core\jni\com_android_internal_os_Zygote.cpp文件中):

static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
                                     jint debug_flags, jobjectArray javaRlimits,
                                     jlong permittedCapabilities, jlong effectiveCapabilities,
                                     jint mount_external,
                                     jstring java_se_info, jstring java_se_name,
                                     bool is_system_server, jintArray fdsToClose,
                                     jstring instructionSet, jstring dataDir) {
  ......
  pid_t pid = fork();

  if (pid == 0) {
    ......
    bool use_native_bridge = !is_system_server && (instructionSet != NULL)
        && android::NativeBridgeAvailable();
    if (use_native_bridge) {
      ScopedUtfChars isa_string(env, instructionSet);
      use_native_bridge = android::NeedsNativeBridge(isa_string.c_str());
    }
    if (use_native_bridge && dataDir == NULL) {
      use_native_bridge = false;
      ALOGW("Native bridge will not be used because dataDir == NULL.");
    }
    ......
    if (use_native_bridge) {
      ScopedUtfChars isa_string(env, instructionSet);
      ScopedUtfChars data_dir(env, dataDir);
      android::PreInitializeNativeBridge(data_dir.c_str(), isa_string.c_str());
    }
    ......
  } else if (pid > 0) {
  }
  return pid;
}

所以,要正式在新应用程序进程中启用NativeBridge,必须要满足几个条件:

1)不是SystemServer进程(NativeBridge主要是用来解决JNI函数的兼容性问题的,SystemServer是fork自Zygote,但是它属于系统的一部分,肯定是根据所在平台而编译的,因此肯定不需要转指令集)。

2)NativeBridge已经准备好了(代码位于system\core\libnativebridge\native_bridge.cc文件中):

bool NativeBridgeAvailable() {
  return state == NativeBridgeState::kOpened
      || state == NativeBridgeState::kPreInitialized
      || state == NativeBridgeState::kInitialized;
}

前面在android::LoadNativeBridge函数中,在成功加载了NativeBridge之后,会把状态设置成NativeBridgeState::kOpened,因此一般情况下NativeBridge是可用的,这个函数会返回true。

3)真的需要NativeBridge来转指令集(代码位于system\core\libnativebridge\native_bridge.cc文件中):

bool NeedsNativeBridge(const char* instruction_set) {
  if (instruction_set == nullptr) {
    ALOGE("Null instruction set in NeedsNativeBridge.");
    return false;
  }
  return strncmp(instruction_set, kRuntimeISA, strlen(kRuntimeISA) + 1) != 0;
}

instruction_set是要运行的应用程序所支持的指令集,而kRuntimeISA是指当前Android系统所运行平台的指令集,它是在编译时就指定好了的(代码位于system\core\libnativebridge\native_bridge.cc文件中):

#if defined(__arm__)
static const char* kRuntimeISA = "arm";
#elif defined(__aarch64__)
static const char* kRuntimeISA = "arm64";
#elif defined(__mips__)
static const char* kRuntimeISA = "mips";
#elif defined(__i386__)
static const char* kRuntimeISA = "x86";
#elif defined(__x86_64__)
static const char* kRuntimeISA = "x86_64";
#else
static const char* kRuntimeISA = "unknown";
#endif

什么时候需要NativeBridge呢?当然是这两个指令集不相同的时候了。要在只支持Intel X86指令集的平台上跑只支持ARM指令集的程序,一般情况下是不可能的,NativeBridge就是为了把它变成可能才存在的。

4)有自己的数据目录,一般应用程序是有的,在/data/data/目录下。

如果决定要启用NativeBridge,则还需要调用android::PreInitializeNativeBridge对其做预初始化:

static constexpr const char* kCodeCacheDir = "code_cache";
......
bool PreInitializeNativeBridge(const char* app_data_dir_in, const char* instruction_set) {
  if (state != NativeBridgeState::kOpened) {
    ALOGE("Invalid state: native bridge is expected to be opened.");
    CloseNativeBridge(true);
    return false;
  }

  if (app_data_dir_in == nullptr) {
    ALOGE("Application private directory cannot be null.");
    CloseNativeBridge(true);
    return false;
  }

  const size_t len = strlen(app_data_dir_in) + strlen(kCodeCacheDir) + 2; // '\0' + '/'
  app_code_cache_dir = new char[len];
  snprintf(app_code_cache_dir, len, "%s/%s", app_data_dir_in, kCodeCacheDir);

  state = NativeBridgeState::kPreInitialized;

#ifndef __APPLE__
  if (instruction_set == nullptr) {
    return true;
  }
  size_t isa_len = strlen(instruction_set);
  if (isa_len > 10) {
    ALOGW("Instruction set %s is malformed, must be less than or equal to 10 characters.",
          instruction_set);
    return true;
  }

  char cpuinfo_path[1024];

#ifdef HAVE_ANDROID_OS
  snprintf(cpuinfo_path, sizeof(cpuinfo_path), "/system/lib"
#ifdef __LP64__
      "64"
#endif  // __LP64__
      "/%s/cpuinfo", instruction_set);
#else
  snprintf(cpuinfo_path, sizeof(cpuinfo_path), "./cpuinfo");
#endif

  if (TEMP_FAILURE_RETRY(mount(cpuinfo_path,        // Source.
                               "/proc/cpuinfo",     // Target.
                               nullptr,             // FS type.
                               MS_BIND,             // Mount flags: bind mount.
                               nullptr)) == -1) {   // "Data."
    ALOGW("Failed to bind-mount %s as /proc/cpuinfo: %s", cpuinfo_path, strerror(errno));
  }
#else  // __APPLE__
  UNUSED(instruction_set);
  ALOGW("Mac OS does not support bind-mounting. Host simulation of native bridge impossible.");
#endif

  return true;
}

除去错误检查之外,这个函数主要做了两件事情,一是在传入的应用程序数据目录中建立一个名字叫做“code_cache”的目录,猜想是为了做转码缓存用的;二是重新绑定指定文件到/pro/cpuinfo上,猜测是有些应用程序会通过读取/proc/cpuinfo来获得当前应用程序运行在什么CPU上,如果发现不对就会退出,比如如果当前系统运行在Intel X86平台上,那么读取这个文件就是Intel CPU的信息,而如果应用程序会通过这个判断自己是否运行在ARM平台上的话,就会出问题,因此可以通过这种方法进行修正。

对于第一步来说,是必须的,如果创建目录不成功会直接报错退出;而对于第二步,则是可选的,即使失败了,也还是会返回true。

好了,完成了预初始化,下面就要正式初始化NativeBridge了,这一步是在ZygoteHooks_nativePostForkChild函数中进行的(代码位于art\runtime\native\dalvik_system_ZygoteHooks.cc文件中):

static void ZygoteHooks_nativePostForkChild(JNIEnv* env, jclass, jlong token, jint debug_flags,
                                            jstring instruction_set) {
  ......
  if (instruction_set != nullptr) {
    ScopedUtfChars isa_string(env, instruction_set);
    InstructionSet isa = GetInstructionSetFromString(isa_string.c_str());
    Runtime::NativeBridgeAction action = Runtime::NativeBridgeAction::kUnload;
    if (isa != kNone && isa != kRuntimeISA) {
      action = Runtime::NativeBridgeAction::kInitialize;
    }
    Runtime::Current()->DidForkFromZygote(env, action, isa_string.c_str());
  } else {
    Runtime::Current()->DidForkFromZygote(env, Runtime::NativeBridgeAction::kUnload, nullptr);
  }
}

这个函数是在Zygote进程fork出来新的应用程序进程之后调用的。这里还是要再判断一下平台的指令集和应用程序支持的指令集是否一样,如果一样的话就根本不需要NativeBridge了,就会将其从进程中卸载出去;而如果不一样,就会让新的进程初始化NativeBridge。这些都是在Runtime::DidForkFromZygote函数中实现的(代码位于art\runtime\runtime.cc文件中):

void Runtime::DidForkFromZygote(JNIEnv* env, NativeBridgeAction action, const char* isa) {
  ......
  if (is_native_bridge_loaded_) {
    switch (action) {
      case NativeBridgeAction::kUnload:
        UnloadNativeBridge();
        is_native_bridge_loaded_ = false;
        break;

      case NativeBridgeAction::kInitialize:
        InitializeNativeBridge(env, isa);
        break;
    }
  }
  ......
}

变量is_native_bridge_loaded_是在最前面Runtime::Init函数中调用LoadNatveBridge函数的返回值,如果NativeBridge加载成功了就会被设置成true。函数的第二个参数是要进行的动作,要么卸载NativeBridge,要么继续初始化。我们继续看InitializeNativeBridge函数做了些什么(代码位于art\runtime\native_bridge_art_interface.cc文件中):

bool LoadNativeBridge(std::string& native_bridge_library_filename) {
  return android::LoadNativeBridge(native_bridge_library_filename.c_str(),
                                   &native_bridge_art_callbacks_);
}

只是继续调用了android::LoadNativeBridge函数(代码位于system\core\libnativebridge\native_bridge.cc):

bool InitializeNativeBridge(JNIEnv* env, const char* instruction_set) {
  if (state == NativeBridgeState::kPreInitialized) {
    struct stat st;
    if (stat(app_code_cache_dir, &st) == -1) {
      if (errno == ENOENT) {
        if (mkdir(app_code_cache_dir, S_IRWXU | S_IRWXG | S_IXOTH) == -1) {
          ALOGE("Cannot create code cache directory %s: %s.", app_code_cache_dir, strerror(errno));
          CloseNativeBridge(true);
        }
      } else {
        ALOGE("Cannot stat code cache directory %s: %s.", app_code_cache_dir, strerror(errno));
        CloseNativeBridge(true);
      }
    } else if (!S_ISDIR(st.st_mode)) {
      ALOGE("Code cache is not a directory %s.", app_code_cache_dir);
      CloseNativeBridge(true);
    }

    if (state == NativeBridgeState::kPreInitialized) {
      if (callbacks->initialize(runtime_callbacks, app_code_cache_dir, instruction_set)) {
        SetupEnvironment(callbacks, env, instruction_set);
        state = NativeBridgeState::kInitialized;
        delete[] app_code_cache_dir;
        app_code_cache_dir = nullptr;
      } else {
        dlclose(native_bridge_handle);
        CloseNativeBridge(true);
      }
    }
  } else {
    CloseNativeBridge(true);
  }

  return state == NativeBridgeState::kInitialized;
}

代码的前半部分主要是确保那个在应用程序私有数据目录下的“code_cache”目录真的被创建出来了(/data/data//code_cache)。而后半部分先调用了NativeBridge动态库中提供的initialize函数,让其自己做初始化的动作,完成了之后,调用SetupEnvironment函数来修改应用程序进程中的运行环境参数(代码位于art\runtime\native_bridge_art_interface.cc文件中):

static void SetupEnvironment(NativeBridgeCallbacks* callbacks, JNIEnv* env, const char* isa) {
  ......
  const struct NativeBridgeRuntimeValues* env_values = callbacks->getAppEnv(isa);
  if (env_values == nullptr) {
    return;
  }
  ......
  if (env_values->cpu_abi != nullptr || env_values->cpu_abi2 != nullptr ||
      env_values->abi_count >= 0) {
    jclass bclass_id = env->FindClass("android/os/Build");
    if (bclass_id != nullptr) {
      SetCpuAbi(env, bclass_id, "CPU_ABI", env_values->cpu_abi);
      SetCpuAbi(env, bclass_id, "CPU_ABI2", env_values->cpu_abi2);
    } else {
      // For example in a host test environment.
      env->ExceptionClear();
      ALOGW("Could not find Build class.");
    }
  }

  if (env_values->os_arch != nullptr) {
    jclass sclass_id = env->FindClass("java/lang/System");
    if (sclass_id != nullptr) {
      jmethodID set_prop_id = env->GetStaticMethodID(sclass_id, "initUnchangeableSystemProperty",
          "(Ljava/lang/String;Ljava/lang/String;)V");
      if (set_prop_id != nullptr) {
        // Init os.arch to the value reqired by the apps running with native bridge.
        env->CallStaticVoidMethod(sclass_id, set_prop_id, env->NewStringUTF("os.arch"),
            env->NewStringUTF(env_values->os_arch));
      } else {
        env->ExceptionClear();
        ALOGW("Could not find initUnchangeableSystemProperty method.");
      }
    } else {
      env->ExceptionClear();
      ALOGW("Could not find System class.");
    }
  }
  ......
}

这个函数先是调用了NativeBridge提供的getAppEnv函数,这个函数会返回NativeBridge所支持的平台、指令集等特性。有了这些特性之后,要相应的更改android.os.Build.CPU_ABI、android.os.Build.CPU_ABI2还有系统属性os.arch的值。为什么要做这一步呢,因为如果不改的话,这些值还是Android平台的真实值,比如对于Android X86来说,它们还是X86。但是有些应用程序会试着获得这些值,并可能根据这些值做判断用,从而导致执行起来和在真实的未转码的平台上有偏差,这和前面要重新绑定/proc/cpuinfo的道理是一样的。题外话,android.os.Build.CPU_ABI和android.os.Build.CPU_ABI2,从Android 5.0开始已经被废弃了,取而代之的是android.os.Build.SUPPORTED_ABIS,看样子这里忘记了更新。

到这里为止,NativeBridge就全部初始化完毕了,所有的初始化动作都是在新的应用程序进程从Zygote进程fork出来之后完成的。

光初始化还没用,在真的加载某个类且这个类有JNI函数调用的时候,会将一个对应的.so动态库加载进来,并调用其中的JNI_OnLoad函数,这些功能是在JavaVMExt::LoadNativeLibrary函数中实现的(代码位于art\runtime\jni_internal.cc文件中):

bool JavaVMExt::LoadNativeLibrary(const std::string& path,
                                  Handle class_loader,
                                  std::string* detail) {
  ......
  const char* path_str = path.empty() ? nullptr : path.c_str();
  void* handle = dlopen(path_str, RTLD_LAZY);
  bool needs_native_bridge = false;
  if (handle == nullptr) {
    if (android::NativeBridgeIsSupported(path_str)) {
      handle = android::NativeBridgeLoadLibrary(path_str, RTLD_LAZY);
      needs_native_bridge = true;
    }
  }
  ......
  bool created_library = false;
  {
    MutexLock mu(self, libraries_lock);
    library = libraries->Get(path);
    if (library == nullptr) {  // We won race to get libraries_lock
      library = new SharedLibrary(path, handle, class_loader.Get());
      libraries->Put(path, library);
      created_library = true;
    }
  }
  ......
  bool was_successful = false;
  void* sym = nullptr;
  if (UNLIKELY(needs_native_bridge)) {
    library->SetNeedsNativeBridge();
    sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
  } else {
    sym = dlsym(handle, "JNI_OnLoad");
  }
  ......
}

函数中会先使用本平台自带的dlopen函数打开指定的.so动态库。如果打开失败的话(比如用X86平台上的dlopen打开ARM指令集编译的.so动态库一定是失败的),不是直接返回错误,而是再试着用NativeBridge提供的函数加载一次。这里调用了两个函数,一是android::NativeBridgeIsSupported,它是用来判断NativeBridge是否可以支持这个动态库的:

bool NativeBridgeIsSupported(const char* libpath) {
  if (NativeBridgeInitialized()) {
    return callbacks->isSupported(libpath);
  }
  return false;
}

它就是直接调用了NativeBridge自己提供的isSupported函数。而第二个是android::NativeBridgeLoadLibrary函数,如果前面返回支持的话,会在这个函数中正式将这个“异类”动态库加载进来:

void* NativeBridgeLoadLibrary(const char* libpath, int flag) {
  if (NativeBridgeInitialized()) {
    return callbacks->loadLibrary(libpath, flag);
  }
  return nullptr;
}

同样,也是通过直接调用NativeBridge提供的loadLibray函数来实现的。每一个.so动态库,都对应一个SharedLibrary对象,下面直接通过调用SharedLibrary::FindSymbolWithNativeBridge来在这个动态库中查找名字为“JNI_OnLoad”函数,并会接着调用它,完成JNI函数的注册工作。SharedLibrary::FindSymbolWithNativeBridge函数主要就是用来在“异类”动态库中查找指定名字的函数的,不光是这里的“JNI_OnLoad”而是所有这个库中的函数,当然如果不用NativeBridge的话直接调用dlsym函数就行了(代码位于art\runtime\jni_internal.cc文件中):

class SharedLibrary {
  ......
  void* FindSymbolWithNativeBridge(const std::string& symbol_name, mirror::ArtMethod* m)
      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
    CHECK(NeedsNativeBridge());

    uint32_t len = 0;
    const char* shorty = nullptr;
    if (m != nullptr) {
      shorty = m->GetShorty(&len);
    }
    return android::NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len);
  }
  ......
}

基本上就是直接调用了android::NativeBridgeGetTrampoline函数():

void* NativeBridgeGetTrampoline(void* handle, const char* name, const char* shorty,
                                uint32_t len) {
  if (NativeBridgeInitialized()) {
    return callbacks->getTrampoline(handle, name, shorty, len);
  }
  return nullptr;
}

而这个函数就是直接调用了NativeBridge提供的getTrampoline函数。“Trampoline”的中文意思是蹦床,通过这个Wrapper函数,“蹦”到真的指令集不兼容的JNI函数上去。所以,以后对这个Native函数的调用,就被这个函数替换掉了,通过它再跳转过去,所有这个动态库中的函数都会有一个对应的跳板。

最后,做一个简单的总结:

1)所谓的NativeBridge,其实是Android为了支持JNI函数运行时动态转指令集而加入的一个逻辑抽象层,可以将动态转指令集的库与ART虚拟机的运行逻辑进行解耦。

2)对于虚拟机来说(目前是ART,未来换成别的也可以),需要提供android::NativeBridgeRuntimeCallbacks结构体所定义的三个函数;

3)对于真正实现动态转指令集的库来说,要有一个android::NativeBridgeCallbacks全局结构体变量及提供其定义的5个函数,且这个结构体变量的名字叫做“NativeBridgeItf”;

4)可以通过更改系统属性“ro.dalvik.vm.native.bridge”来打开和关闭动态转指令集的功能,将其设置为0表示关闭,而将其设置成一个有效的动态库的路径表示打开。

houdini正是利用了这个NativeBridge,它只是提供了第三点的功能,使得集成它的代码改动量很小。

你可能感兴趣的:(Android)