.preinit_array,.init_array,.init和JNI_OnLoad

https://www.cnblogs.com/revercc/p/16299712.html
在linker初始化的时候,linker_main函数在加载了ELF文件依赖的所有so库后,会先调用call_pre_init_constructors()和call_constructors()函数对so库执行一些初始化后再返回ELF文件的入口地址。

  //加载所有的依赖库文件并进行重定位处理。
  if (needed_libraries_count > 0 &&
      !find_libraries(&g_default_namespace,
                      si,
                      needed_library_names,
                      needed_libraries_count,
                      nullptr,
                      &g_ld_preloads,
                      ld_preloads_count,
                      RTLD_GLOBAL,
                      nullptr,
                      true /* add_as_children */,
                      true /* search_linked_namespaces */,
                      &namespaces)) {
    __linker_cannot_link(g_argv[0]);
  } else if (needed_libraries_count == 0) {
    if (!si->link_image(g_empty_list, soinfo_list_t::make_list(si), nullptr, nullptr)) {
      __linker_cannot_link(g_argv[0]);
    }
    si->increment_ref_count();
  }

  //对所有加载的依赖库进行初始化。
  si->call_pre_init_constructors();
  si->call_constructors();
  //返回ELF文件的入口地址
  ElfW(Addr) entry = exe_info.entry_point;
  TRACE("[ Ready to execute \"%s\" @ %p ]", si->get_realpath(), reinterpret_cast<void*>(entry));
  return entry;

.preinit_array

call_pre_init_constructors函数会调用call_array加载DT_PREINIT_ARRAY数组中的所有函数并执行。

.init_array和.init

call_constructors函数调用call_function执行DT_INIT中的函数,调用call_array执行DT_INIT_ARRAY数组中的所有函数。

.init和.init_array是在so加载阶段执行一些初始化操作,例如c++中的全局/静态对象的构造函数。相对应的.fini和.fini_array是在so卸载的时候执行一些清理操作,例如c++全局/静态对象的析构函数。gcc的一些高级特性对这些操作提供支持。

JNI_Onload

JAVA层加载一个so文件函数调用链如下

System.loadLibrary()
 ├─ loadLibrary0
 │  └─ nativeload(Runtime_nativeLoad进入native层)
 │      └─ JVM_NativeLoad
 │          └─ JavaVMExt::LoadNativeLibrary
 │               └─ OpenNativeLibrary
 │                  FindSymbol
 └──────────────────jni_on_load

System.loadLibrary

JAVA层调用System.loadLibrary函数加载一个so文件,此函数会调用loadLibrary0

loadLibrary0

loadLibrary0会继续调用native函数nativeload去加载so文件。

Runtime_nativeLoad

nativeload对应的native层函数为Runtime_nativeLoad,此函数直接调用另一个函数JVM_NativeLoad

//libcore/ojluni/src/main/native/Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jclass caller)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

JVM_NativeLoad

JVM_NativeLoad在进行一些参数的检查后,通过JavaVM调用LoadNativeLibrary去加载so。

//art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jclass caller) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == nullptr) {
    return nullptr;
  }

  std::string error_msg;
  {
    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         caller,
                                         &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());
}

JavaVMExt::LoadNativeLibrary

  • 调用OpenNativeLibrary,其内部会调用android_dlopen_ext加载so库(android_dlopen_ext最终会调用do_dlopen加载so库和其所有的依赖库之后,会和linker初始化一样先对此so库进行初始化,调用.preinit_array,.init,.init_array)
  • 调用FindSymbol,其内部会调用dlsym获取JNI_OnLoad导出函数的地址
  • 最后调用JNI_OnLoad函数进行动态注册
//art/runtime/jni/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  std::string* error_msg) {

  //OpenNativeLibrary会调用android_dlopen_ext加载so库
  void* handle = android::OpenNativeLibrary(
      env,
      runtime_->GetTargetSdkVersion(),
      path_str,
      class_loader,
      (caller_location.empty() ? nullptr : caller_location.c_str()),
      library_path.get(),
      &needs_native_bridge,
      &nativeloader_error_msg);
  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";

  //FindSymbol会调用dlsym获取JNI_OnLoad导出函数的地址
  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
  if (sym == nullptr) {
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
    was_successful = true;
  } else {
    
    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
    using JNI_OnLoadFn = int(*)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    //调用JNI_OnLoad函数
    int version = (*jni_on_load)(this, nullptr);
  }
}

总结

.preinit_array,.init_array,.init和JNI_OnLoad的执行顺序就是 .preinit_array,.init,.init_array,JNI_OnLoad。(源码是基于android10.0.0_r7)
参考:Linux和UNIX系统编程手册

posted @ 2022-11-04 23:27  怎么可以吃突突  阅读(843)  评论(0编辑  收藏  举报