博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android SO库加载流程
阅读量:6622 次
发布时间:2019-06-25

本文共 13935 字,大约阅读时间需要 46 分钟。

hot3.png

      今天面试,遇到一个问题,技术面问我SO库是如何被加载的。讲真,我还真不知道它到底是如何被加载的,于是翻阅资料整理了SO库被加载的流程。

      这里只讲通常的JAVA层加载流程

  1. 首先我们会调用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
    最终我们在其中找到了方法
     
  2. 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
    candidates = new ArrayList
    (); 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的分支。

  3. 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)。

  4. 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标记的路径中搜索。

  5. 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 with NewStringUTF.  env->ExceptionClear();  return env->NewStringUTF(error_msg.c_str());}

     

  6. 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
    (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
    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
    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
    (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的索引,如果存在,那么就会调用该方法。

  7. 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
    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库的加载完成。

转载于:https://my.oschina.net/zzxzzg/blog/854426

你可能感兴趣的文章
iphone http下载文件
查看>>
poj 1195:Mobile phones(二维树状数组,矩阵求和)
查看>>
Codeforces 433 C. Ryouko&#39;s Memory Note
查看>>
java中的Static class
查看>>
实例讲解Linux下的makefile
查看>>
json lib 2.4及其依赖包下载
查看>>
计算机中文核心期刊
查看>>
sql的left join 命令
查看>>
8148 8168 中移植live55 出现except rtsp 中途莫名的断流
查看>>
【BZOJ】3832: [Poi2014]Rally
查看>>
[转]看懂ExtJS的API
查看>>
宜昌民生大厦
查看>>
推荐15款制作 SVG 动画的 JavaScript 库
查看>>
转:OpenResty最佳实践(推荐了解lua语法)
查看>>
转:CEO, CFO, CIO, CTO, CSO是什么
查看>>
ROC曲线(receiver-operating-characteristic curve)-阈值评价标准(转)
查看>>
Swift 表达式
查看>>
FFmpeg(8)-打开音视频解码器,配置解码器上下文(avcodec_find_decoder()、avcodec_alloc_context3())...
查看>>
andriod自定义视图
查看>>
linux下vim更改注释颜色
查看>>