Android JNI开辟:System.loadLibrary加载机制

打印 上一主题 下一主题

主题 1817|帖子 1817|积分 5451

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
源码流程分析

在Android的JNI开辟中,加载.so文件通常使用System.loadLibrary函数。以下是一个简单的示例:
  1. public class MainActivity extends AppCompatActivity {
  2.     // Used to load the 'myapplication' library on application startup.
  3.     static {
  4.         System.loadLibrary("myapplication");
  5.     }
  6. }
复制代码
接下来,我们以Android 10的源码为例,详细分析System.loadLibrary函数的实现过程。起首,System.loadLibrary的定义位于/libcore/ojluni/src/main/java/java/lang/System.java,实现如下:
  1. public static void loadLibrary(String libname) {
  2.          Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
  3. }
复制代码
从中可以看出,System.loadLibrary实际上是调用了Runtime类的loadLibrary0方法。我们继承跟踪loadLibrary0的实现:
  1. void loadLibrary0(Class<?> fromClass, String libname) {
  2.     ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
  3.     loadLibrary0(classLoader, fromClass, libname);
  4. }
复制代码
在这里,通过传入的类获取了对应的类加载器(ClassLoader),然后调用了重载的loadLibrary0方法:
  1. private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
  2.     // Check if the library name contains a directory separator
  3.     if (libname.indexOf((int) File.separatorChar) != -1) {
  4.         throw new UnsatisfiedLinkError(
  5.             "Directory separator should not appear in library name: " + libname);
  6.     }
  7.    
  8.     String libraryName = libname;
  9.     // If loader is not null and not BootClassLoader, use it to find the library
  10.     if (loader != null && !(loader instanceof BootClassLoader)) {
  11.         String filename = loader.findLibrary(libraryName);
  12.         if (filename == null) {
  13.             throw new UnsatisfiedLinkError(loader + " couldn't find "" +
  14.                                            System.mapLibraryName(libraryName) + """);
  15.         }
  16.         String error = nativeLoad(filename, loader);
  17.         if (error != null) {
  18.             throw new UnsatisfiedLinkError(error);
  19.         }
  20.         return;
  21.     }
  22.     // If loader is null or is BootClassLoader, initialize library paths and load the library
  23.     getLibPaths();
  24.     String filename = System.mapLibraryName(libraryName);
  25.     String error = nativeLoad(filename, loader, callerClass);
  26.     if (error != null) {
  27.         throw new UnsatisfiedLinkError(error);
  28.     }
  29. }
复制代码
这里loadLibrary0有一句关键代码:
  1. oader.findLibrary:
  2. public String findLibrary(String name) {
  3.          return pathList.findLibrary(name);
  4. }
复制代码
实现如下:
  1. public String findLibrary(String libraryName) {
  2.         // 最后会调用到C层中JNIEXPORT jstring JNICALL System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
  3.         // 结果就是会返回libmyapplication.so,拼接字符串
  4.     String fileName = System.mapLibraryName(libraryName);
  5.         // 返回libmyapplication.so的全路径
  6.     for (NativeLibraryElement element : nativeLibraryPathElements) {
  7.         String path = element.findNativeLibrary(fileName);
  8.         if (path != null) {
  9.             return path;
  10.         }
  11.     }
  12.     return null;
  13. }
复制代码
可以看到,findLibrary最终会通过JNI调用System.mapLibraryName方法拼接出完备的.so库文件名,并在本地路径中查找该文件。
接着看loadLibrary0中nativeLoad实现:
  1. private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);
复制代码
发现此时已经在C层,实现如下:
  1. JNIEXPORT jstring JNICALL Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
  2.                     jobject javaLoader, jclass caller)
  3. {
  4.     return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
  5. }
复制代码
看来核心代码在JVM_NativeLoad函数中,继承跟进:
  1. JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
  2.                                   jstring javaFilename,
  3.                                   jobject javaLoader,
  4.                                   jclass caller) {
  5.     ScopedUtfChars filename(env, javaFilename);
  6.     if (filename.c_str() == nullptr) {
  7.         return nullptr;
  8.     }
  9.     std::string error_msg;
  10.     {
  11.         art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
  12.         bool success = vm->LoadNativeLibrary(env,
  13.                                               filename.c_str(),
  14.                                               javaLoader,
  15.                                               caller,
  16.                                               &error_msg);
  17.         if (success) {
  18.             return nullptr;
  19.         }
  20.     }
  21.     // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
  22.     env->ExceptionClear();
  23.     return env->NewStringUTF(error_msg.c_str());
  24. }
复制代码
vm->LoadNativeLibrary函数相对复杂,我们主要查看在这个函数内部调用android::OpenNativeLibrary函数:
  1. void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
  2.                         jobject class_loader, const char* caller_location, jstring library_path,
  3.                         bool* needs_native_bridge, char** error_msg) {
  4. #if defined(__ANDROID__)
  5.     UNUSED(target_sdk_version);
  6.     if (class_loader == nullptr) {
  7.         *needs_native_bridge = false;
  8.         if (caller_location != nullptr) {
  9.             android_namespace_t* boot_namespace = FindExportedNamespace(caller_location);
  10.             if (boot_namespace != nullptr) {
  11.                 const android_dlextinfo dlextinfo = {
  12.                     .flags = ANDROID_DLEXT_USE_NAMESPACE,
  13.                     .library_namespace = boot_namespace,
  14.                 };
  15.                 void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
  16.                 if (handle == nullptr) {
  17.                     *error_msg = strdup(dlerror());
  18.                 }
  19.                 return handle;
  20.             }
  21.         }
  22.         void* handle = dlopen(path, RTLD_NOW);
  23.         if (handle == nullptr) {
  24.             *error_msg = strdup(dlerror());
  25.         }
  26.         return handle;
  27.     }
  28.     // Additional code (not provided) would go here
  29. #endif
  30. }
复制代码
总算追踪到了我们的老朋侪android_dlopen_ext和dlopen,最后经过追踪会调用do_dlopen函数,这就到本日的核心
  1. void* do_dlopen(const char* name, int flags,const android_dlextinfo* extinfo,const void* caller_addr) {
  2.         ...
  3.         ProtectedDataGuard guard;
  4. soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
  5. loading_trace.End();
  6. if (si != nullptr) {
  7.     void* handle = si->to_handle();
  8.     LD_LOG(kLogDlopen,
  9.            "... dlopen calling constructors: realpath="%s", soname="%s", handle=%p",
  10.            si->get_realpath(), si->get_soname(), handle);
  11.     si->call_constructors();
  12.     failure_guard.Disable();
  13.     LD_LOG(kLogDlopen,
  14.            "... dlopen successful: realpath="%s", soname="%s", handle=%p",
  15.            si->get_realpath(), si->get_soname(), handle);
  16.     return handle;
  17. }
复制代码
也就是在在so加载完成以后会调用si->call_constructors()函数:
  1. void soinfo::call_constructors() {
  2.         // 主要看这样两行
  3.     call_function("DT_INIT", init_func_, get_realpath());
  4.     call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());
  5. }
复制代码
这里也就阐明白so中的init和init_array的实行时机在do_dlopen内部。此外,我们还需要相识JNI_OnLoad的实行时机。回到vm->LoadNativeLibrary函数中:
  1. bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
  2.                                   const std::string& path,
  3.                                   jobject class_loader,
  4.                                   jclass caller_class,
  5.                                   std::string* error_msg) {
  6.                                  
  7. void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
  8. if (sym == nullptr) {
  9.     VLOG(jni) << "[No JNI_OnLoad found in "" << path << ""]";
  10.     was_successful = true;
  11. } else {
  12.     ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
  13.     self->SetClassLoaderOverride(class_loader);
  14.     VLOG(jni) << "[Calling JNI_OnLoad in "" << path << ""]";
  15.     using JNI_OnLoadFn = int(*)(JavaVM*, void*);
  16.     JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
  17.     int version = (*jni_on_load)(this, nullptr);
  18. }
复制代码
可以看到就是通过library->FindSymbol查找JNI_OnLoad符号,然后使用函数指针进行JNI_OnLoad的函数调用。
总结

System.loadLibrary的核心功能是获取.so库的全路径,加载该.so库文件,然后依次调用init、init_array和JNI_OnLoad函数。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表