Android 9.0 System.loadLibrary 的源码解析

本文主要讲解下Android 9.0 System.loadLibrary 的源码实现。

源码分析

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

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

//将libname加上前缀和后缀,即`lib<libname>.so`
public static native String mapLibraryName(String libname);

System 的 loadLibrary 最终调用的是 Runtime 中的 loadLibrary0 方法。 mapLibraryName 方法主要是给 libname 加上 “lib” 的前缀和 “.so” 的后缀。所以我们加载 so 库时,不用带 “lib” 和 “.so” 的字符串。

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

static void cpchars(jchar *dst, char *src, int n)
{
    int i;
    for (i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    int len;
    int prefix_len = (int) strlen(JNI_LIB_PREFIX); //JNI_LIB_PREFIX="lib"
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX); //JNI_LIB_SUFFIX=".so"

    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); //chars="lib"
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);//chars="lib"<libname>
    len += prefix_len;
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len); //chars="lib"<libname>".so"
    len += suffix_len;

    return (*env)->NewString(env, chars, len);
}

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) { //loader 不为空就,调用的 findLibrary。
        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) + "\"");
        }
        //filename = /data/app/<packagename>-xyz==/lib/arm64/lib<libraryName>.so
        String error = nativeLoad(filename, loader);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
        return;
    }

	//filename=lib<libraryName>.so
    String filename = System.mapLibraryName(libraryName);
    List<String> candidates = new ArrayList<String>();
    String lastError = null;
    //getLibPaths()=/system/lib64/
    for (String directory : getLibPaths()) {
    	//candidate=/system/lib64/lib<libraryName>.so
        String candidate = directory + filename;
        candidates.add(candidate);

        //如果存在/system/lib64/lib<libraryName>.so就加载so
        if (IoUtils.canOpenReadOnly(candidate)) {
            String error = nativeLoad(candidate, loader);
            if (error == null) {
                return; // We successfully loaded the library. Job done.
            }
            lastError = error;
        }
    }

    if (lastError != null) {
        throw new UnsatisfiedLinkError(lastError);
    }
    //如果都没有找到so就抛出异常。
    throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}

private volatile String[] mLibPaths = null;

private String[] getLibPaths() {
    if (mLibPaths == null) {
        synchronized(this) {
            if (mLibPaths == null) {
                mLibPaths = initLibPaths();
            }
        }
    }
    return mLibPaths;
}

private static String[] initLibPaths() {
    String javaLibraryPath = System.getProperty("java.library.path"); //java.library.path="/system/lib64"
    if (javaLibraryPath == null) {
        return EmptyArray.STRING;
    }
    String[] paths = javaLibraryPath.split(":");
    // Add a '/' to the end of each directory so we don't have to do it every time.
    for (int i = 0; i < paths.length; ++i) {
        if (!paths[i].endsWith("/")) {
            paths[i] += "/";
        }
    }
    return paths;
}

loadLibrary0 在加载 so 时,先判断 loader 是否为 null 。如果不为 null,就通过 loader 去找到 so 的绝对路径,然后再加载。如果为 null,就从 /system/lib64/ 中去找是否存在要加载的 so 。如果都没有找到就抛出异常。下面分析下 loader.findLibrary 方法。loader 集成自 BaseDexClassLoader。所以它最终调用的 BaseDexClassLoader 的 findLibrary 方法。

libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

    if (reporter != null) {
        reportClassLoaderChain();
    }
}

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

BaseDexClassLoader 的 findLibrary 方法最终由 DexPathList 的 findLibrary 实现。而 DexPathList 的 findLibrary 是在 nativeLibraryPathElements 中遍历查找 存在的 so。nativeLibraryPathElements 的值来自 librarySearchPath 和 System.getProperty(“java.library.path”) 。librarySearchPath 主要是 /data/app/<packagename>-xyz==/lib/arm64:/data/app/<packagename>-xyz==/base.apk!/lib/arm64-v8a 的路径。System.getProperty(“java.library.path”) 为 /system/lib64/ 。所以 DexPathList 是在这几个目录下查找 so 是否存在。如果存在就返回其绝对路径即可。

libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

DexPathList(ClassLoader definingContext, String dexPath,
        String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
    ...
    //librarySearchPath="/data/app/<packagename>-xyz==/lib/arm64:/data/app/<packagename>-xyz==/base.apk!/lib/arm64-v8a"
	this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
    this.systemNativeLibraryDirectories =
            splitPaths(System.getProperty("java.library.path"), true); //"java.library.path"="/system/lib64"
    List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
    allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

    this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
	...
}

private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
    List<File> result = new ArrayList<>();

    if (searchPath != null) {
        for (String path : searchPath.split(File.pathSeparator)) {
            if (directoriesOnly) {
                try {
                    StructStat sb = Libcore.os.stat(path);
                    if (!S_ISDIR(sb.st_mode)) {
                        continue;
                    }
                } catch (ErrnoException ignored) {
                    continue;
                }
            }
            result.add(new File(path));
        }
    }

    return result;
}

private static NativeLibraryElement[] makePathElements(List<File> files) {
    NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];
    int elementsPos = 0;
    for (File file : files) {
        String path = file.getPath();

        if (path.contains(zipSeparator)) { //zipSeparator="!/"
            String split[] = path.split(zipSeparator, 2);
            File zip = new File(split[0]);
            String dir = split[1];
            elements[elementsPos++] = new NativeLibraryElement(zip, dir);
        } else if (file.isDirectory()) {
            // We support directories for looking up native libraries.
            elements[elementsPos++] = new NativeLibraryElement(file);
        }
    }
    if (elementsPos != elements.length) {
        elements = Arrays.copyOf(elements, elementsPos);
    }
    return elements;
}

public String findLibrary(String libraryName) {
    String fileName = System.mapLibraryName(libraryName);

    for (NativeLibraryElement element : nativeLibraryPathElements) {
        String path = element.findNativeLibrary(fileName);

        if (path != null) {
            return path;
        }
    }

    return null;
}

static class NativeLibraryElement {
	public NativeLibraryElement(File dir) {
        this.path = dir;
        this.zipDir = null;
    }

    public NativeLibraryElement(File zip, String zipDir) {
        this.path = zip;
        this.zipDir = zipDir;

        if (zipDir == null) {
          throw new IllegalArgumentException();
        }
    }

	public synchronized void maybeInit() {
        if (initialized) {
            return;
        }

        if (zipDir == null) {
            initialized = true;
            return;
        }

        try {
            urlHandler = new ClassPathURLStreamHandler(path.getPath());
        } catch (IOException ioe) {
            System.logE("Unable to open zip file: " + path, ioe);
            urlHandler = null;
        }

        initialized = true;
    }

	public String findNativeLibrary(String name) {
        maybeInit();

        if (zipDir == null) {
            String entryPath = new File(path, name).getPath();
            if (IoUtils.canOpenReadOnly(entryPath)) {
                return entryPath;
            }
        } else if (urlHandler != null) {
            String entryName = zipDir + '/' + name;
            if (urlHandler.isEntryStored(entryName)) {
              return path.getPath() + zipSeparator + entryName;
            }
        }

        return null;
    }
}

找到 so 的绝对路径后就是加载 so 了。so 的加载是通过 nativeLoad 方法实现的。java 层的 nativeLoad 对应的就是 c 层的 Runtime_nativeLoad 方法。

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

#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, (void*)(className ## _ ## functionName) }


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

static JNINativeMethod gMethods[] = {
  FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
  NATIVE_METHOD(Runtime, gc, "()V"),
  NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
  NATIVE_METHOD(Runtime, nativeLoad,
                "(Ljava/lang/String;Ljava/lang/ClassLoader;)"
                    "Ljava/lang/String;"),
  //{"nativeLoad", "(Ljava/lang/String;Ljava/lang/ClassLoader;)"
                    "Ljava/lang/String;", (void*)Runtime_nativeLoad}
};

void register_java_lang_Runtime(JNIEnv* env) {
  jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}

上面代码可以看出 Runtime_nativeLoad 调用的是 JVM_NativeLoad 方法。而 JVM_NativeLoad 真正实现so 的加载是在 vm->LoadNativeLibrary 方法中。

art/openjdkjvm/OpenjdkJvm.cc

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

art/runtime/java_vm_ext.cc

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  std::string* error_msg) {
  ...

  // 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.

  // Retrieve the library path from the classloader, if necessary.
  ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));

  Locks::mutator_lock_->AssertNotHeld(self);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  bool needs_native_bridge = false;
  void* handle = android::OpenNativeLibrary(env,
                                            runtime_->GetTargetSdkVersion(),
                                            path_str,
                                            class_loader,
                                            library_path.get(),
                                            &needs_native_bridge,
                                            error_msg);

  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";

  if (handle == nullptr) {
    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,
                          needs_native_bridge,
                          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 = 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) {
      // Make sure that sigchain owns SIGSEGV.
      EnsureFrontOfChain(SIGSEGV);
    }

    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 (JavaVMExt::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;
}


class SharedLibrary {
    void SetNeedsNativeBridge(bool needs) {
        needs_native_bridge_ = needs;
    }

    bool NeedsNativeBridge() const {
    return needs_native_bridge_;
    }

    // No mutator lock since dlsym may block for a while if another thread is doing dlopen.
    void* FindSymbol(const std::string& symbol_name, const char* shorty = nullptr)
      REQUIRES(!Locks::mutator_lock_) {
    return NeedsNativeBridge()
        ? FindSymbolWithNativeBridge(symbol_name.c_str(), shorty)
        : FindSymbolWithoutNativeBridge(symbol_name.c_str());
    }

    // No mutator lock since dlsym may block for a while if another thread is doing dlopen.
    void* FindSymbolWithoutNativeBridge(const std::string& symbol_name)
      REQUIRES(!Locks::mutator_lock_) {
    CHECK(!NeedsNativeBridge());

    return dlsym(handle_, symbol_name.c_str());
    }

    void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty)
      REQUIRES(!Locks::mutator_lock_) {
    CHECK(NeedsNativeBridge());

    uint32_t len = 0;
    return android::NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len);
    }
}

LoadNativeLibrary 最终会通过 android::OpenNativeLibrary 去加载 so 库。然后判断 JNI_OnLoad 方法是否存在。如果存在就调用其方法。这也就是为什么我们在做 jni 开发时,要实现 JNI_OnLoad 方法来做一些初始化的操作。下面列出 OpenNativeLibrary 的源码实现,不在深入分析。现在新版本的加入了名字空间的概念,通过它来实现系统的私有 so 库,不被第三方加载。加载so不像之前的是用dlopen去加载 so 了。

system/core/libnativeloader/native_loader.cpp

void* OpenNativeLibrary(JNIEnv* env,
                        int32_t target_sdk_version,
                        const char* path,
                        jobject class_loader,
                        jstring library_path,
                        bool* needs_native_bridge,
                        std::string* error_msg) {
                        
#if defined(__ANDROID__)
  UNUSED(target_sdk_version);
  if (class_loader == nullptr) {
    *needs_native_bridge = false;
    return dlopen(path, RTLD_NOW);
  }

  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace ns;

  if (!g_namespaces->FindNamespaceByClassLoader(env, class_loader, &ns)) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    if (!g_namespaces->Create(env,
                              target_sdk_version,
                              class_loader,
                              false /* is_shared */,
                              false /* is_for_vendor */,
                              library_path,
                              nullptr,
                              &ns,
                              error_msg)) {
      return nullptr;
    }
  }

  if (ns.is_android_namespace()) {
    android_dlextinfo extinfo;
    extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
    extinfo.library_namespace = ns.get_android_ns();

    void* handle = android_dlopen_ext(path, RTLD_NOW, &extinfo);
    if (handle == nullptr) {
      *error_msg = dlerror();
    }
    *needs_native_bridge = false;
    return handle;
  } else {
    void* handle = NativeBridgeLoadLibraryExt(path, RTLD_NOW, ns.get_native_bridge_ns());
    if (handle == nullptr) {
      *error_msg = NativeBridgeGetError();
    }
    *needs_native_bridge = true;
    return handle;
  }
#else
  UNUSED(env, target_sdk_version, class_loader);

  // Do some best effort to emulate library-path support. It will not
  // work for dependencies.
  //
  // Note: null has a special meaning and must be preserved.
  std::string c_library_path;  // Empty string by default.
  if (library_path != nullptr && path != nullptr && path[0] != '/') {
    ScopedUtfChars library_path_utf_chars(env, library_path);
    c_library_path = library_path_utf_chars.c_str();
  }

  std::vector<std::string> library_paths = base::Split(c_library_path, ":");

  for (const std::string& lib_path : library_paths) {
    *needs_native_bridge = false;
    const char* path_arg;
    std::string complete_path;
    if (path == nullptr) {
      // Preserve null.
      path_arg = nullptr;
    } else {
      complete_path = lib_path;
      if (!complete_path.empty()) {
        complete_path.append("/");
      }
      complete_path.append(path);
      path_arg = complete_path.c_str();
    }
    void* handle = dlopen(path_arg, RTLD_NOW);
    if (handle != nullptr) {
      return handle;
    }
    if (NativeBridgeIsSupported(path_arg)) {
      *needs_native_bridge = true;
      handle = NativeBridgeLoadLibrary(path_arg, RTLD_NOW);
      if (handle != nullptr) {
        return handle;
      }
      *error_msg = NativeBridgeGetError();
    } else {
      *error_msg = dlerror();
    }
  }
  return nullptr;
#endif
}

总结

System.loadLibrary 的源码分析基本完成了。现在我们来重新整理下它们的调用关系。

System.loadLibrary
    Runtime.loadLibrary0
        Runtime_nativeLoad
            JVM_NativeLoad
                LoadNativeLibrary
                    OpenNativeLibrary

Android 7.0 开始,禁止加载非NDK库,也就是说系统禁止了应用去链接系统的私有库。它通过名字空间的方式来实现其方法。所以就看到了,我们加载 so 的时候是用 OpenNativeLibrary 方法,而不是以往的 dlopen 方法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值