Android中的加载so文件两种方式
Android系统中使用ndk进行编程,有很多的好处,具体有什么好处,这里就不多说了。要使用ndk进行编程,在Java层就必须要对so进行加载。Java层加载so的函数有两个
System.load(String pathName) System.loadLibraray(String libName)
两个函数的区别就是load函数的参数是so文件的绝对地址。loadLibrary的参数是so的名称,这个so文件必须放在apk的lib目录下,而且so的名称必须去掉前面的lib和后边的“.so”。如下所示:
System.load("/data/local/tmp/libhello.so"); System.loadLibrary("hello");
Syste.Load源码分析
- 首先来看System.load的实现:
public static void load(String pathName) { Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader()); }
然后继续追踪:
/* * Loads the given shared library using the given ClassLoader. */ void load(String absolutePath, ClassLoader loader) { if (absolutePath == null) { throw new NullPointerException("absolutePath == null"); } String error = doLoad(absolutePath, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } }
从这里可以看到doLoad这个函数我们需要继续关注的函数。
private String doLoad(String name, ClassLoader loader) { // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH, // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH. // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load // libraries with no dependencies just fine, but an app that has multiple libraries that // depend on each other needed to load them in most-dependent-first order. // We added API to Android's dynamic linker so we can update the library path used for // the currently-running process. We pull the desired path out of the ClassLoader here // and pass it to nativeLoad so that it can call the private dynamic linker API. // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the // beginning because multiple apks can run in the same process and third party code can // use its own BaseDexClassLoader. // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any // dlopen(3) calls made from a .so's JNI_OnLoad to work too. // So, find out what the native library search path is for the ClassLoader in question... String ldLibraryPath = null; if (loader != null && loader instanceof BaseDexClassLoader) { // 获得so的路径 ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath(); } // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized // internal natives. synchronized (this) { return nativeLoad(name, loader, ldLibraryPath); } }
接下来分析nativeLoad这个函数,通过分析源码,发现这个这个函数是一个native函数,在/art/runtime/native/javalangRuntime.cc中找到了这个函数的实现方式:
static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPathJstr) { ScopedUtfChars filename(env, javaFilename); if (filename.c_str() == nullptr) { return nullptr; } SetLdLibraryPath(env, javaLdLibraryPathJstr); std::string error_msg; { JavaVMExt* vm = Runtime::Current()->GetJavaVM(); bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg); if (success) { return nullptr; } } // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF. env->ExceptionClear(); return env->NewStringUTF(error_msg.c_str()); }
通过上面的代码,我们可以发现关键函数是vm-LoaaNativeLibrary
// 该代码的位置:art/runtime/java_vm_ext.cc#596 bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, std::string* error_msg) { error_msg->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, *Locks::jni_libraries_lock_); library = libraries_->Get(path); } if (library != nullptr) { if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) { // 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(error_msg, "Shared library \"%s\" already opened by " "ClassLoader %p; can't open in ClassLoader %p", path.c_str(), library->GetClassLoader(), class_loader); LOG(WARNING) << error_msg; return false; } VLOG(jni) << "[Shared library \"" << path << "\" already loaded in " << " ClassLoader " << class_loader << "]"; if (!library->CheckOnLoadResult()) { StringAppendF(error_msg, "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. Locks::mutator_lock_->AssertNotHeld(self); const char* path_str = path.empty() ? nullptr : path.c_str(); // 从这个地方可以看到,调用的还是dlopen void* handle = dlopen(path_str, RTLD_NOW); bool needs_native_bridge = false; if (handle == nullptr) { if (android::NativeBridgeIsSupported(path_str)) { handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW); needs_native_bridge = true; } } VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]"; if (handle == nullptr) { *error_msg = dlerror(); VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg; return false; } if (env->ExceptionCheck() == JNI_TRUE) { LOG(ERROR) << "Unexpected exception:"; env->ExceptionDescribe(); env->ExceptionClear(); } // Create a new entry. // TODO: move the locking (and more of this logic) into Libraries. bool created_library = false; { // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering. std::unique_ptr<SharedLibrary> new_library( new SharedLibrary(env, self, path, handle, class_loader)); MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); if (library == nullptr) { // We won race to get libraries_lock. library = new_library.release(); 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; return library->CheckOnLoadResult(); } VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]"; bool was_successful = false; void* sym; if (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.) ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader); VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; typedef int (*JNI_OnLoadFn)(JavaVM*, void*); JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); int version = (*jni_on_load)(this, nullptr); if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) { fault_manager.EnsureArtActionInFrontOfSignalChain(); } self->SetClassLoaderOverride(old_class_loader.get()); if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); } else if (IsBadJniVersion(version)) { StringAppendF(error_msg, "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; }
从上面的代码可以看到,其实java层的System.loda()函数底层也调用的是dlopen函数。
source@/bionic/linker/dlfcn.cpp void* dlopen(const char* filename, int flags) { return dlopen_ext(filename, flags, nullptr); }
dlopen函数直接调用dlopenext(),dlopenext()的实现如下所示:
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; }
继续查看dodlopen的代码
source @ bionic/linker/linker.cpp#1667 soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) { if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) { DL_ERR("invalid flags to dlopen: %x", flags); return nullptr; } if (extinfo != nullptr) { if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) { DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags); return nullptr; } if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) { DL_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without " "ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags); return nullptr; } } ProtectedDataGuard guard; soinfo* si = find_library(name, flags, extinfo); if (si != nullptr) { si->call_constructors(); } return si; }
findlibrary()函数继续的进行跟踪:
source @: /bionic/linker/linker.cpp static soinfo* find_library(const char* name, int rtld_flags, const android_dlextinfo* extinfo) { soinfo* si; if (name == nullptr) { si = somain; } else if (!find_libraries(nullptr, &name, 1, &si, nullptr, 0, rtld_flags, extinfo)) { return nullptr; } return si; }
findlibraries的函数实现如下所示:
static bool find_libraries(soinfo* start_with, const char* const library_names[], size_t library_names_count, soinfo* soinfos[], std::vector<soinfo*>* ld_preloads, size_t ld_preloads_count, int rtld_flags, const android_dlextinfo* extinfo) { // Step 0: prepare. LoadTaskList load_tasks; for (size_t i = 0; i < library_names_count; ++i) { const char* name = library_names[i]; load_tasks.push_back(LoadTask::create(name, start_with)); } // Construct global_group. soinfo::soinfo_list_t global_group = make_global_group(); // If soinfos array is null allocate one on stack. // The array is needed in case of failure; for example // when library_names[] = {libone.so, libtwo.so} and libone.so // is loaded correctly but libtwo.so failed for some reason. // In this case libone.so should be unloaded on return. // See also implementation of failure_guard below. if (soinfos == nullptr) { size_t soinfos_size = sizeof(soinfo*)*library_names_count; soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size)); memset(soinfos, 0, soinfos_size); } // list of libraries to link - see step 2. size_t soinfos_count = 0; auto failure_guard = make_scope_guard([&]() { // Housekeeping load_tasks.for_each([] (LoadTask* t) { LoadTask::deleter(t); }); for (size_t i = 0; i<soinfos_count; ++i) { soinfo_unload(soinfos[i]); } }); // Step 1: load and pre-link all DT_NEEDED libraries in breadth first order. for (LoadTask::unique_ptr task(load_tasks.pop_front()); task.get() != nullptr; task.reset(load_tasks.pop_front())) { soinfo* si = find_library_internal(load_tasks, task->get_name(), rtld_flags, extinfo); if (si == nullptr) { return false; } soinfo* needed_by = task->get_needed_by(); if (needed_by != nullptr) { needed_by->add_child(si); } if (si->is_linked()) { si->increment_ref_count(); } // When ld_preloads is not null, the first // ld_preloads_count libs are in fact ld_preloads. if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) { // Add LD_PRELOADed libraries to the global group for future runs. // There is no need to explicitly add them to the global group // for this run because they are going to appear in the local // group in the correct order. si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL); ld_preloads->push_back(si); } if (soinfos_count < library_names_count) { soinfos[soinfos_count++] = si; } } // Step 2: link libraries. soinfo::soinfo_list_t local_group; walk_dependencies_tree( start_with == nullptr ? soinfos : &start_with, start_with == nullptr ? soinfos_count : 1, [&] (soinfo* si) { local_group.push_back(si); return true; }); // We need to increment ref_count in case // the root of the local group was not linked. bool was_local_group_root_linked = local_group.front()->is_linked(); bool linked = local_group.visit([&](soinfo* si) { if (!si->is_linked()) { if (!si->link_image(global_group, local_group, extinfo)) { return false; } si->set_linked(); } return true; }); if (linked) { failure_guard.disable(); } if (!was_local_group_root_linked) { local_group.front()->increment_ref_count(); } return linked; }