本文學習的源碼參考AndroidXRef,版本為Lollipop 5.1.0_r1。
在Android開發過程中,我們要想使用JNI機制調用一個native方法時,首先會需要調用一個system的方法,把定義了native方法的庫加載進來。今天,就從System.loadLibrary(“XXX”)來詳細看一下一個動态庫的加載過程。
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
這個loadLibrary是System的一個方法,同時也說明了JNI标準本身就是Java語言的一部分。
可以看到這個方法裡實際調用了Runtime的loadLibrary方法。
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
loadLibrary首先根據傳入的loader,調用它的
findLibrary(libraryName)
方法去擷取要加載的so的檔案路徑,然後判斷一下獲得的路徑是否為空,不為空,則調用
doLoad(filename, loader)
去執行so的加載連結等等過程。
若傳入的loader為空,則首先去通過System的
mapLibraryName(libraryName)
方法擷取so的檔案名,然後在一個mLibPaths的變量裡面去找這個so檔案,并最終拼接形成最終的so檔案路徑,最後還是調用
doLoad(candidate, loader)
去執行so的加載連結過程。
看下doLoad的實作:
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
}
synchronized (this) {
return nativeLoad(name, loader, ldLibraryPath);
}
}
注釋中提到,Android應用從Zygote程序中建立出來,是以他們沒有自定義的LD_LIBRARY_PATH,so檔案的預設路徑肯定也不在這個變量中;而PathClassLoader知道正确的路徑,是以我們可以暫時先不加載依賴庫,但是後面需要按依賴順序加載。我們需要将API添加到Android的動态連結器中,這樣我們就可以為目前運作的程序更新所使用的庫路徑,此處就是将classloader中的路徑傳遞給nativeload。
可以看到doLoad先提取了ldLibraryPath的值,然後和so檔案的名字以及loader一起傳遞給了nativeLoad函數。很顯然,這是一個native方法:
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
if (javaLdLibraryPath != NULL) {
ScopedUtfChars ldLibraryPath(env, javaLdLibraryPath);
if (ldLibraryPath.c_str() == NULL) {
return NULL;
}
void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");
if (sym != NULL) {
typedef void (*Fn)(const char*);
Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);
(*android_update_LD_LIBRARY_PATH)(ldLibraryPath.c_str());
} else {
LOG(ERROR) << "android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!";
}
}
std::string detail;
{
ScopedObjectAccess soa(env);
StackHandleScope<> hs(soa.Self());
Handle<mirror::ClassLoader> classLoader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
JavaVMExt* vm = Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(filename.c_str(), classLoader, &detail);
if (success) {
return nullptr;
}
}
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(detail.c_str());
}
這個方法首先做了javaFilename和javaLdLibraryPath的類型轉換,然後更新了LD_LIBRARY_PATH,就是我們上面說的路徑設定。接下來,最主要的,調用
GetJavaVM()
擷取對應的android虛拟機,調用它的LoadNativeLibrary方法去完成so的加載。
bool JavaVMExt::LoadNativeLibrary(const std::string& path,
Handle<mirror::ClassLoader> class_loader,
std::string* detail) {
detail->clear();
// See if we've already loaded this library. If we have, and the class loader
// matches, return successfully without doing anything.
// TODO: for better results we should canonicalize the pathname (or even compare
// inodes). This implementation is fine if everybody is using System.loadLibrary.
SharedLibrary* library;
Thread* self = Thread::Current();
{
// TODO: move the locking (and more of this logic) into Libraries.
MutexLock mu(self, libraries_lock);
library = libraries->Get(path);
}
if (library != nullptr) {
if (library->GetClassLoader() != class_loader.Get()) {
// The library will be associated with class_loader. The JNI
// spec says we can't load the same library into more than one
// class loader.
StringAppendF(detail, "Shared library \"%s\" already opened by "
"ClassLoader %p; can't open in ClassLoader %p",
path.c_str(), library->GetClassLoader(), class_loader.Get());
LOG(WARNING) << detail;
return false;
}
VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
<< "ClassLoader " << class_loader.Get() << "]";
if (!library->CheckOnLoadResult()) {
StringAppendF(detail, "JNI_OnLoad failed on a previous attempt "
"to load \"%s\"", path.c_str());
return false;
}
return true;
}
// Open the shared library. Because we're using a full path, the system
// doesn't have to search through LD_LIBRARY_PATH. (It may do so to
// resolve this library's dependencies though.)
// Failures here are expected when java.library.path has several entries
// and we have to hunt for the lib.
// Below we dlopen but there is no paired dlclose, this would be necessary if we supported
// class unloading. Libraries will only be unloaded when the reference count (incremented by
// dlopen) becomes zero from dlclose.
// This can execute slowly for a large library on a busy system, so we
// want to switch from kRunnable while it executes. This allows the GC to ignore us.
self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad);
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;
}
}
self->TransitionFromSuspendedToRunnable();
VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]";
if (handle == nullptr) {
*detail = dlerror();
LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *detail;
return false;
}
// Create a new entry.
// TODO: move the locking (and more of this logic) into Libraries.
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;
}
}
if (!created_library) {
LOG(INFO) << "WOW: we lost a race to add shared library: "
<< "\"" << path << "\" ClassLoader=" << class_loader.Get();
return library->CheckOnLoadResult();
}
VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader.Get()
<< "]";
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");
}
if (sym == nullptr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
was_successful = true;
} else {
// Call JNI_OnLoad. We have to override the current class
// loader, which will always be "null" since the stuff at the
// top of the stack is around Runtime.loadLibrary(). (See
// the comments in the JNI FindClass function.)
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
StackHandleScope<> hs(self);
Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader.Get());
int version = ;
{
ScopedThreadStateChange tsc(self, kNative);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
version = (*jni_on_load)(this, nullptr);
}
if (runtime->GetTargetSdkVersion() != && runtime->GetTargetSdkVersion() <= ) {
fault_manager.EnsureArtActionInFrontOfSignalChain();
}
self->SetClassLoaderOverride(old_class_loader.Get());
if (version == JNI_ERR) {
StringAppendF(detail, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
} else if (IsBadJniVersion(version)) {
StringAppendF(detail, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
path.c_str(), version);
// It's unwise to call dlclose() here, but we can mark it
// as bad and ensure that future load attempts will fail.
// We don't know how far JNI_OnLoad got, so there could
// be some partially-initialized stuff accessible through
// newly-registered native method calls. We could try to
// unregister them, but that doesn't seem worthwhile.
} else {
was_successful = true;
}
VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
<< " from JNI_OnLoad in \"" << path << "\"]";
}
library->SetResult(was_successful);
return was_successful;
}
有點長,大緻分為四個部分:
第一部分,檢查我們是否已經加載了這個so庫,并且對應的classloader是否比對,如果都通過了則直接傳回成功;
第二部分,去打開那個so庫,并傳回一個handle句柄,也就是我們很熟悉的
dlopen(path_str, RTLD_LAZY)
;
第三部分,建立一個SharedLibrary對象,後面去執行裡面的方法都需要通過這個對象來完成。例如,我們可以通過
FindSymbolWithNativeBridge("JNI_OnLoad", nullptr)
獲得JNI_OnLoad的函數指針。當然,也可以通過
dlsym(handle, "JNI_OnLoad")
的方式來擷取;
最後,當然就是去執行JNI_OnLoad方法了。
剛剛提到的dlopen,就是so加載的核心函數了,我們看下這個方法的實作:
void* dlopen(const char* filename, int flags) {
return dlopen_ext(filename, flags, nullptr);
}
static void* dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo) {
ScopedPthreadMutexLocker locker(&g_dl_mutex);
soinfo* result = do_dlopen(filename, flags, extinfo);
if (result == nullptr) {
__bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
return nullptr;
}
return result;
}
終于進入到linker的源碼了。
可以看到這個函數最終是調用了
do_dlopen(filename, flags, extinfo)
。
這裡提一下dlopen的那個flag的參數,總共可以取三個值:
- RTLD_LAZY:在dlopen傳回前,對于動态庫中存在的未定義的變量不執行解析;
- RTLD_NOW:與上面相反,這個值表示在dlopen傳回前,立即解析每個未定義變量的位址,否則會傳回undefined symbol的錯誤;
- RTLD_GLOBAL:它的含義是使得庫中的解析的變量具有全局性,也即随後的其它的連結庫中也可以使用。
繼續看do_dlopen的實作:
soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) {
if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NOLOAD)) != ) {
DL_ERR("invalid flags to dlopen: %x", flags);
return nullptr;
}
if (extinfo != nullptr) {
if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != ) {
DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == &&
(extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != ) {
DL_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags);
return nullptr;
}
}
protect_data(PROT_READ | PROT_WRITE);
soinfo* si = `find_library(name, flags, extinfo)`;
if (si != nullptr) {
si->CallConstructors();
}
protect_data(PROT_READ);
return si;
}
前面是做了下參數的判斷,重點在後面,先修改記憶體權限為可讀可寫,因為我們要加載so庫了;
然後調用
find_library(name, flags, extinfo)
進行so的加載,傳回一個soinfo的指針,這個soinfo就是包含了我們加載後的so的所有資訊的一個結構體;
接下來,若si不為空,也即so加載成功了,去調用
CallConstructors()
方法,後面我們就會知道這裡是去執行了so的init和init_array段,完成一些初始化的操作;
最後,将記憶體改回可讀。