天天看点

Android源码学习——linker(1)

本文学习的源码参考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<> 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<> hs(self);
    Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader.Get());

    int version = ;
    {
      ScopedThreadStateChange tsc(self, kNative);
      VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
      version = (*jni_on_load)(this, nullptr);
    }

    if (runtime->GetTargetSdkVersion() !=  && runtime->GetTargetSdkVersion() <= ) {
      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)) != ) {
    DL_ERR("invalid flags to dlopen: %x", flags);
    return nullptr;
  }
  if (extinfo != nullptr) {
    if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != ) {
      DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
      return nullptr;
    }
    if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) ==  &&
        (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != ) {
      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段,完成一些初始化的操作;

最后,将内存改回可读。