Android SO库加载流程

      今天面试,遇到一个问题,技术面问我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<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的分支。

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值