.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系统编程手册
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具