2019独角兽企业重金招聘Python工程师标准>>>
今天面试,遇到一个问题,技术面问我SO库是如何被加载的。讲真,我还真不知道它到底是如何被加载的,于是翻阅资料整理了SO库被加载的流程。
这里只讲通常的JAVA层加载流程
public static void loadLibrary(String libname) {Runtime().CallingClassLoader(), libname); }CallingClassLoader是获取ClassLoader,可以使用getClassLoader().toString()来打印类。
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.
@Overridepublic 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 = LdLibraryPath();}// 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_NativeLoad
JNIEXPORT 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 v->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 = pty() ? 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 = lease();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_());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);
#elseUNUSED(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库的加载完成。
转载于:
本文发布于:2024-01-29 10:54:37,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170649688014779.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |