Android JNI之System.loadLibrary()流程

    xiaoxiao2021-12-12  14

    文档目的

    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 <code>libname</code> * argument. The manner in which a library name is mapped to the * actual system library is system dependent. * <p> * The call <code>System.loadLibrary(name)</code> is effectively * equivalent to the call * <blockquote><pre> * Runtime.getRuntime().loadLibrary(name) * </pre></blockquote> * * @param libname the name of the library. * @exception SecurityException if a security manager exists and its * <code>checkLink</code> method doesn't allow * loading of the specified dynamic library * @exception UnsatisfiedLinkError if the library does not exist. * @exception NullPointerException if <code>libname</code> is * <code>null</code> * @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<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); }

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

        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<IOException> suppressedExceptions = new ArrayList<IOException>(); // 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<File> 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<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; }

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

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

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

        3) 执行JNI_OnLoad方法。

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

    转载请注明原文地址: https://ju.6miu.com/read-900077.html

    最新回复(0)