今天面试,遇到一个问题,技术面问我SO库是如何被加载的。讲真,我还真不知道它到底是如何被加载的,于是翻阅资料整理了SO库被加载的流程。
这里只讲通常的JAVA层加载流程
- 首先我们会调用System.loadLibrary方法。
查看代码public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname); }
VMStack.getCallingClassLoader是获取ClassLoader,可以使用getClassLoader().toString()来打印类。
D/load: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.carme.diandian-2/base.apk"],nativeLibraryDirectories=[/data/app/com.carme.diandian-2/lib/arm, /vendor/lib, /system/lib]]]
发现其实ClassLoader是PathClassLoader而它继承与BaseDexClassLoader
最终我们在其中找到了方法
- Runtime.loadLibaray0
synchronized void loadLibrary0(ClassLoader loader, String libname) { if (libname.indexOf((int)File.separatorChar) != -1) { throw new UnsatisfiedLinkError( "Directory separator should not appear in library name: " + libname); } String libraryName = libname; 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 : getLibPaths()) { 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); }
仔细看,我们会发现,其实我们在app中load的时候只走了loader!=null的分支。
-
load.findLibrary
我们知道,loader类型为PathClassLoader,继承与BaseDexClassLoader.@Override public String findLibrary(String name) { return pathList.findLibrary(name); }
调用了DexPathList中的方法,继续往下走:
public String findLibrary(String libraryName) { String fileName = System.mapLibraryName(libraryName); for (Element element : nativeLibraryPathElements) { String path = element.findNativeLibrary(fileName); if (path != null) { return path; } } return null; }
mapLibraryName方法是添加libxxx.so前后缀(和系统相关,所以是native方法)
关于nativeLibarayPath其实在之前的log中已经打印了出来,如下:
nativeLibraryDirectories=[/data/app/com.carme.diandian-2/lib/arm, /vendor/lib, /system/lib]
然后再上面列出的目录中寻找对应的lib库,找到后返回。
所以,从上面的代码中我们会发现,so库只有放在如上的三个地址才能被正确加载,其他地方无法加载(其中第一个地址是nativePath,和当前的设备和app相关,后面两个是systemPath)。 -
doLoad
获取了so的准确路径,之后就回到了Runtime.loadLibaray0的doLoad方法,如下:
private String doLoad(String name, ClassLoader loader) { String librarySearchPath = null; if (loader != null && loader instanceof BaseDexClassLoader) { BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; librarySearchPath = dexClassLoader.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, librarySearchPath); } }
首先我们会获取一个librarySearchPath来当做我们的LD_LIBRARY_PATH(由于zygote中并没有设置,所以fork得app当然也没有),关于LD_LIBRARY_PATH,它其实是一个linux的环境变量,用来表示依赖库的搜寻路径,如果在系统默认的lib路径中(android就是/system/lib 和 vendor/lib)没有搜索到需要的依赖库,就是在LD_LIBRARY_PATH标记的路径中搜索。
-
nativeLoad
对应到了native层Runtime.c中的Runtime_nativeLoad,进一步调用了JVM_NativeLoadJNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader, jstring javaLibrarySearchPath) { //转码jstring到c++字符串 ScopedUtfChars filename(env, javaFilename); if (filename.c_str() == NULL) { return NULL; } std::string error_msg; { art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM(); bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, javaLibrarySearchPath, &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()); }
-
LoadNativeLibrary
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, jstring library_path, 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); } void* class_loader_allocator = nullptr; { ScopedObjectAccess soa(env); // As the incoming class loader is reachable/alive during the call of this function, // it's okay to decode it without worrying about unexpectedly marking it alive. mirror::ClassLoader* loader = soa.Decode<mirror::ClassLoader*>(class_loader); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); if (class_linker->IsBootClassLoader(soa, loader)) { loader = nullptr; class_loader = nullptr; } class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader); CHECK(class_loader_allocator != nullptr); } if (library != nullptr) { // Use the allocator pointers for class loader equality to avoid unnecessary weak root decode. if (library->GetClassLoaderAllocator() != class_loader_allocator) { // 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(); void* handle = android::OpenNativeLibrary(env, runtime_->GetTargetSdkVersion(), path_str, class_loader, library_path); 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, class_loader_allocator)); 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->FindSymbol("JNI_OnLoad", nullptr); 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; }
核心的加载代码,涉及到非常多的内容,知识有限,ClassLinker 和 NativeBridge暂不分析。
首先在已经加载的库中寻找是否已经加载过指定的lib,如果已经加载,直接返回true。
然后调用OpenNativeLibrary加载so库,加载成功后会将该库添加到队列中表示已经加载,下次再遇到不需要重新加载。
之后搜索其中是否有JNI_OnLoad的索引,如果存在,那么就会调用该方法。 -
OpenNativeLibrary
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path, jobject class_loader, jstring library_path) { #if defined(__ANDROID__) UNUSED(target_sdk_version); if (class_loader == nullptr) { return dlopen(path, RTLD_NOW); } std::lock_guard<std::mutex> guard(g_namespaces_mutex); android_namespace_t* ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader); if (ns == nullptr) { // This is the case where the classloader was not created by ApplicationLoaders // In this case we create an isolated not-shared namespace for it. ns = g_namespaces->Create(env, class_loader, false, library_path, nullptr); if (ns == nullptr) { return nullptr; } } android_dlextinfo extinfo; extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE; extinfo.library_namespace = ns; return android_dlopen_ext(path, RTLD_NOW, &extinfo); #else UNUSED(env, target_sdk_version, class_loader, library_path); return dlopen(path, RTLD_NOW); #endif }
通过调用android_dlopen_ext 或者 dlopen来加载指定的库
void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo) { void* caller_addr = __builtin_return_address(0); return dlopen_ext(filename, flags, extinfo, caller_addr); } void* dlopen(const char* filename, int flags) { void* caller_addr = __builtin_return_address(0); return dlopen_ext(filename, flags, nullptr, caller_addr); }
其实两者并没有差别~~
至此,so库的加载完成。