用於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<NativeBridgeCallbacks*>(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/<Package Name>目錄下。
如果決定要啟用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/<Package Name>/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<mirror::ClassLoader> 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,它只是提供了第三點的功能,使得整合它的程式碼改動量很小。