Android JNI之System.loadLibrary()流程

文档目的

JNI全称Java Native Interface,相当于java语言和C/C++语言打交道的桥梁。主要是用来在java语言中加载lib库。然后就可以在java中使用native中定义的方法。

用法如下:


static {
    System.loadLibrary("TestJni");
}
private static native String getResult();

本文主要来跟踪一下System.loadLibrary的调用流程,不对jni文件实现做探究。


Library查找过程

以Android N代码为例,System.loadLibrary源码位于:

./libcore/ojluni/src/main/java/java/lang/System.java

    /**
     * Loads the system library specified by the libname
     * argument. The manner in which a library name is mapped to the
     * actual system library is system dependent.
     * 

* The call System.loadLibrary(name) is effectively * equivalent to the call *

     * Runtime.getRuntime().loadLibrary(name)
     * 
* * @param libname the name of the library. * @exception SecurityException if a security manager exists and its * checkLink method doesn't allow * loading of the specified dynamic library * @exception UnsatisfiedLinkError if the library does not exist. * @exception NullPointerException if libname is * null * @see java.lang.Runtime#loadLibrary(java.lang.String) * @see java.lang.SecurityManager#checkLink(java.lang.String) */ public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname); }

可以看到然后又调用了Runtime.java的loadLibrary0方法:

./libcore/ojluni/src/main/java/java/lang/Runtime.java

    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);
    }

这个方法主要有两个作用:

    1. 找到lib的全称

    2. 调用doLoad加载lib库

首先会判断loader 不为空来执行上面两步,如果为空,则换个方法继续执行上面两步。

这个方法的第一个参数是ClassLoader,是获取系统的loader,一般情况下这个值不会为空,是通过ContextImpl.java中的getClassLoader来生成的,且实例化为PathClassLoader。PathClassLoader继承BaseDexClassLoader,BaseDexClassLoader继承ClassLoader。

所以loader.findLibrary(libraryName)最终是调用BaseDexClassLoader中的findLibrary。

代码位于:./libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

其中pathList是一个DexPathList对象,位于./libcore/dalvik/src/main/java/dalvik/system/DexPathList.java:

    /**
     * Finds the named native code library on any of the library
     * directories pointed at by this instance. This will find the
     * one in the earliest listed directory, ignoring any that are not
     * readable regular files.
     *
     * @return the complete path to the library or {@code null} if no
     * library was found
     */
    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;
    }

可以看到这里会先调用System.java中的mapLibraryName方法来对libraryName做处理,这是一个native方法,在System.c中实现:

./libcore/ojluni/src/main/native/System.c

JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    int len;
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);
  
    jchar chars[256];
    if (libname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return NULL;
    }
    len = (*env)->GetStringLength(env, libname);
    if (len > 240) {
        JNU_ThrowIllegalArgumentException(env, "name too long");
        return NULL;
    }
    cpchars(chars, JNI_LIB_PREFIX, prefix_len);
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
    len += prefix_len;
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
    len += suffix_len;
  
    return (*env)->NewString(env, chars, len);
}

这个方法主要目的是给传进来的name添加前缀lib和后缀.so,最开始我们传进来的name是TestJni,所以此处处理后返回的是libTestJni.so.

接下来使用for循环对nativeLibraryPathElements调用findNativeLibrary方法。我们先看一下nativeLibraryPathElements是在哪里赋值的。在DexPathList的构造函数中:

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
 
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
 
        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }
 
        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }
 
            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }
 
        this.definingContext = definingContext;
 
        ArrayList suppressedExceptions = new ArrayList();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);
 
        // Native libraries may exist in both the system and
        // application library paths, and we use this search order:
        //
        //   1. This class loader's library path for application libraries (librarySearchPath):
        //   1.1. Native library directories
        //   1.2. Path to libraries in apk-files
        //   2. The VM's library path from the system property for system libraries
        //      also known as java.library.path
        //
        // This order was reversed prior to Gingerbread; see http://b/2933456.
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
 
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
                                                          suppressedExceptions,
                                                          definingContext);
 
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

上述第44~50行代码就是nativeLibraryPathElements赋值的地方。

此处nativeLibraryPathElements依赖allNativeLibraryDirectories并通过makePathElements方法生成。

allNativeLibraryDirectories是一个list,由nativeLibraryDirectories和systemNativeLibraryDirectories组成。

librarySearchPath是由LoadedApk.java中createOrUpdateClassLoaderLocked方法中赋值的:

./frameworks/base/core/java/android/app/LoadedApk.java

final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);

然后在ApplicationLoaders中实例化PathClassLoader时传递过去。在一般的app中类似于如下目录:

/data/app/com.example.testjni-1/lib/arm64

systemNativeLibraryDirectories是从系统java属性java.library.path中获取。Java.library.path这个值是在System.c的System_specialProperties方法中调用系统内核方法android_get_LD_LIBRARY_PATH赋值的。

android_get_LD_LIBRARY_PATH最终是在系统内核源码./bionic/linker/linker.cpp中实现:

void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
  // Use basic string manipulation calls to avoid snprintf.
  // snprintf indirectly calls pthread_getspecific to get the size of a buffer.
  // When debug malloc is enabled, this call returns 0. This in turn causes
  // snprintf to do nothing, which causes libraries to fail to load.
  // See b/17302493 for further details.
  // Once the above bug is fixed, this code can be modified to use
  // snprintf again.
  size_t required_len = 0;
  for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) {
    required_len += strlen(g_default_ld_paths[i]) + 1;
  }
  if (buffer_size < required_len) {
    __libc_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: "
                 "buffer len %zu, required len %zu", buffer_size, required_len);
  }
  char* end = buffer;
  for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) {
    if (i > 0) *end++ = ':';
    end = stpcpy(end, g_default_ld_paths[i]);
  }
}

g_default_ld_paths在init_default_namespace的时候被赋值:

  if (bname && (strcmp(bname, "linker_asan") == 0 || strcmp(bname, "linker_asan64") == 0)) {
    g_default_ld_paths = kAsanDefaultLdPaths;
  } else {
    g_default_ld_paths = kDefaultLdPaths;
  }


static const char* const kDefaultLdPaths[] = {
#if defined(__LP64__)
  "/system/lib64",
  "/vendor/lib64",
#else
  "/system/lib",
  "/vendor/lib",
#endif
  nullptr
};

所以systemNativeLibraryDirectories得值为 /system/lib64:/vendor/lib64

再返回到DexPathList的findLibrary方法,查看for循环,调用Element的findNativeLibrary方法查找fileName是否存在且可读,返回第一个存在且可读的path。

总结一下loadLibrary0的第一步,主要是调用loader.findLibrary(libraryName)完成以下任务:

    1) 调用System.mapLibraryName(libraryName)拼接处完整的lib名字

    2) 在下述3个路径查找lib库是否存在并返回第一个可读的文件路径:

        /data/app/com.example.testjni-1/lib/arm64

        /vendor/lib64

        /system/lib64

 

Library加载过程

如果lib库的路径不为空,接下来就调用doLoad进行下载。我们看下doLoad方法:

    private String doLoad(String name, ClassLoader loader) {
        // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
        // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
 
        // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
        // libraries with no dependencies just fine, but an app that has multiple libraries that
        // depend on each other needed to load them in most-dependent-first order.
 
        // We added API to Android's dynamic linker so we can update the library path used for
        // the currently-running process. We pull the desired path out of the ClassLoader here
        // and pass it to nativeLoad so that it can call the private dynamic linker API.
 
        // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
        // beginning because multiple apks can run in the same process and third party code can
        // use its own BaseDexClassLoader.
 
        // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
        // dlopen(3) calls made from a .so's JNI_OnLoad to work too.
 
        // So, find out what the native library search path is for the ClassLoader in question...
        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,然后调用nativeLoad加载。

nativeLoad实现在libcore/ojluni/src/main/native/Runtime.c中:

JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jstring javaLibrarySearchPath)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);
}

接着调用 ./art/runtime/openjdkjvm/OpenjdkJvm.cc中的JVM_NativeLoad

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jstring javaLibrarySearchPath) {
  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;
    }
  }

此处先获取当前运行的虚拟机,然后调用虚拟机的LoadNativeLibrary,代码位于:

./art/runtime/java_vm_ext.cc:

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;
}

此方法异常复杂,我们不做深入分析,该方法大致做以下几步:

    1) 调用android::OpenNativeLibrary打开lib库

    2) 调用library->FindSymbol("JNI_OnLoad", nullptr)找到lib中的JNI_OnLoad这个方法

    3) 执行JNI_OnLoad方法。

至此,java层真正调到lib库中,接下来的一些方法就需要jni层自己实现了。

你可能感兴趣的:(Android开发)