Android源码学习——linker(1)

来源:互联网 发布:java编写cad绘图工具 编辑:程序博客网 时间:2024/05/20 15:42

本文学习的源码参考AndroidXRef,版本为Lollipop 5.1.0_r1。


在Android开发过程中,我们要想使用JNI机制调用一个native方法时,首先会需要调用一个system的方法,把定义了native方法的库加载进来。今天,就从System.loadLibrary(“XXX”)来详细看一下一个动态库的加载过程。

    public static void loadLibrary(String libName) {        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());    }

这个loadLibrary是System的一个方法,同时也说明了JNI标准本身就是Java语言的一部分。
可以看到这个方法里实际调用了Runtime的loadLibrary方法。

void loadLibrary(String libraryName, ClassLoader loader) {        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 : mLibPaths) {            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);    }

loadLibrary首先根据传入的loader,调用它的findLibrary(libraryName)方法去获取要加载的so的文件路径,然后判断一下获得的路径是否为空,不为空,则调用doLoad(filename, loader)去执行so的加载链接等等过程。
若传入的loader为空,则首先去通过System的mapLibraryName(libraryName)方法获取so的文件名,然后在一个mLibPaths的变量里面去找这个so文件,并最终拼接形成最终的so文件路径,最后还是调用doLoad(candidate, loader)去执行so的加载链接过程。

看下doLoad的实现:

private String doLoad(String name, ClassLoader loader) {        String ldLibraryPath = null;        if (loader != null && loader instanceof BaseDexClassLoader) {            ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();        }        synchronized (this) {            return nativeLoad(name, loader, ldLibraryPath);        }    }

注释中提到,Android应用从Zygote进程中创建出来,因此他们没有自定义的LD_LIBRARY_PATH,so文件的默认路径肯定也不在这个变量中;而PathClassLoader知道正确的路径,因此我们可以暂时先不加载依赖库,但是后面需要按依赖顺序加载。我们需要将API添加到Android的动态链接器中,这样我们就可以为当前运行的进程更新所使用的库路径,此处就是将classloader中的路径传递给nativeload。

可以看到doLoad先提取了ldLibraryPath的值,然后和so文件的名字以及loader一起传递给了nativeLoad函数。很显然,这是一个native方法:

static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath) {  ScopedUtfChars filename(env, javaFilename);  if (filename.c_str() == NULL) {    return NULL;  }  if (javaLdLibraryPath != NULL) {    ScopedUtfChars ldLibraryPath(env, javaLdLibraryPath);    if (ldLibraryPath.c_str() == NULL) {      return NULL;    }    void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");    if (sym != NULL) {      typedef void (*Fn)(const char*);      Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);      (*android_update_LD_LIBRARY_PATH)(ldLibraryPath.c_str());    } else {      LOG(ERROR) << "android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!";    }  }  std::string detail;  {    ScopedObjectAccess soa(env);    StackHandleScope<1> hs(soa.Self());    Handle<mirror::ClassLoader> classLoader(        hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));    JavaVMExt* vm = Runtime::Current()->GetJavaVM();    bool success = vm->LoadNativeLibrary(filename.c_str(), classLoader, &detail);    if (success) {      return nullptr;    }  }  // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.  env->ExceptionClear();  return env->NewStringUTF(detail.c_str());}

这个方法首先做了javaFilename和javaLdLibraryPath的类型转换,然后更新了LD_LIBRARY_PATH,就是我们上面说的路径设置。接下来,最主要的,调用GetJavaVM()获取对应的android虚拟机,调用它的LoadNativeLibrary方法去完成so的加载。

bool JavaVMExt::LoadNativeLibrary(const std::string& path,                                  Handle<mirror::ClassLoader> class_loader,                                  std::string* detail) {  detail->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, libraries_lock);    library = libraries->Get(path);  }  if (library != nullptr) {    if (library->GetClassLoader() != class_loader.Get()) {      // 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(detail, "Shared library \"%s\" already opened by "          "ClassLoader %p; can't open in ClassLoader %p",          path.c_str(), library->GetClassLoader(), class_loader.Get());      LOG(WARNING) << detail;      return false;    }    VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "              << "ClassLoader " << class_loader.Get() << "]";    if (!library->CheckOnLoadResult()) {      StringAppendF(detail, "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.  // This can execute slowly for a large library on a busy system, so we  // want to switch from kRunnable while it executes.  This allows the GC to ignore us.  self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad);  const char* path_str = path.empty() ? nullptr : path.c_str();  void* handle = dlopen(path_str, RTLD_LAZY);  bool needs_native_bridge = false;  if (handle == nullptr) {    if (android::NativeBridgeIsSupported(path_str)) {      handle = android::NativeBridgeLoadLibrary(path_str, RTLD_LAZY);      needs_native_bridge = true;    }  }  self->TransitionFromSuspendedToRunnable();  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]";  if (handle == nullptr) {    *detail = dlerror();    LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *detail;    return false;  }  // Create a new entry.  // TODO: move the locking (and more of this logic) into Libraries.  bool created_library = false;  {    MutexLock mu(self, libraries_lock);    library = libraries->Get(path);    if (library == nullptr) {  // We won race to get libraries_lock      library = new SharedLibrary(path, handle, class_loader.Get());      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.Get();    return library->CheckOnLoadResult();  }  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader.Get()      << "]";  bool was_successful = false;  void* sym = nullptr;  if (UNLIKELY(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.)    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);    StackHandleScope<1> hs(self);    Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride()));    self->SetClassLoaderOverride(class_loader.Get());    int version = 0;    {      ScopedThreadStateChange tsc(self, kNative);      VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";      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(detail, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());    } else if (IsBadJniVersion(version)) {      StringAppendF(detail, "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;}

有点长,大致分为四个部分:
第一部分,检查我们是否已经加载了这个so库,并且对应的classloader是否匹配,如果都通过了则直接返回成功;
第二部分,去打开那个so库,并返回一个handle句柄,也就是我们很熟悉的dlopen(path_str, RTLD_LAZY)
第三部分,创建一个SharedLibrary对象,后面去执行里面的方法都需要通过这个对象来完成。例如,我们可以通过FindSymbolWithNativeBridge("JNI_OnLoad", nullptr)获得JNI_OnLoad的函数指针。当然,也可以通过dlsym(handle, "JNI_OnLoad")的方式来获取;
最后,当然就是去执行JNI_OnLoad方法了。

刚刚提到的dlopen,就是so加载的核心函数了,我们看下这个方法的实现:

void* dlopen(const char* filename, int flags) {  return dlopen_ext(filename, flags, nullptr);}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;}

终于进入到linker的源码了。
可以看到这个函数最终是调用了do_dlopen(filename, flags, extinfo)
这里提一下dlopen的那个flag的参数,总共可以取三个值:

  • RTLD_LAZY:在dlopen返回前,对于动态库中存在的未定义的变量不执行解析;
  • RTLD_NOW:与上面相反,这个值表示在dlopen返回前,立即解析每个未定义变量的地址,否则会返回undefined symbol的错误;
  • RTLD_GLOBAL:它的含义是使得库中的解析的变量具有全局性,也即随后的其它的链接库中也可以使用。

继续看do_dlopen的实现:

soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) {  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|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;    }  }  protect_data(PROT_READ | PROT_WRITE);  soinfo* si = `find_library(name, flags, extinfo)`;  if (si != nullptr) {    si->CallConstructors();  }  protect_data(PROT_READ);  return si;}

前面是做了下参数的判断,重点在后面,先修改内存权限为可读可写,因为我们要加载so库了;
然后调用find_library(name, flags, extinfo)进行so的加载,返回一个soinfo的指针,这个soinfo就是包含了我们加载后的so的所有信息的一个结构体;
接下来,若si不为空,也即so加载成功了,去调用CallConstructors()方法,后面我们就会知道这里是去执行了so的init和init_array段,完成一些初始化的操作;
最后,将内存改回可读。


原创粉丝点击