通过分析loadLibrary源码,可以知道init initarray jnionload这三个函数是在什么时候调用的,

在 Android App 开发中,如果涉及到 jni 开发,常常会使用 System.loadLibray 来加载生成的 so 文件。以下将通过安卓 10 的源码,追踪 System.loadLibrary 的内部流程。

Runtime类调用分析

Runtime 类源码路径:

1
libcore\ojluni\src\main\java\java\lang\Runtime.java

loadLibrary0 函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 //
void loadLibrary0(Class<?> fromClass, String libname) {
ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
loadLibrary0(classLoader, fromClass, libname);
}
private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
// Android-note: BootClassLoader doesn't implement findLibrary(). http://b/111850480
// Android's class.getClassLoader() can return BootClassLoader where the RI would
// have returned null; therefore we treat BootClassLoader the same as null here.
if (loader != null && !(loader instanceof BootClassLoader)) {
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 = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
// We know some apps use mLibPaths directly, potentially assuming it's not null.
// Initialize it here to make sure apps see a non-null value.
getLibPaths();
String filename = System.mapLibraryName(libraryName);
String error = nativeLoad(filename, loader, callerClass);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}

不具体讨论函数中的详细逻辑流程,loadLibrary0 在 so 未加载情况下会调用 nativeLoad 方法。nativeLoad 函数如下:

1
2
3
4
 private static String nativeLoad(String filename, ClassLoader loader) {
return nativeLoad(filename, loader, null);
}
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);

最终调用的是 native 方法 nativeLoad,所以接下来需要找到该方法的 jni 层实现。Runtime.java 中的 jni 方法是由 Runtime.c 注册实现的。接下来分析 Runtime.c 中的流程。

Runtime.c中的流程分析

Runtime.c 源码路径位于:

1
libcore\ojluni\src\main\native\Runtime.c

在该文件中 java 层 native 方法 nativeLoad 对应的 jni 层实现函数为 Runtime_nativeLoad, 函数实现如下:

1
2
3
4
5
6
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jclass caller)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

有以上代码逻辑中可分析调用了 JVM_NativeLoad 函数。通过强大的 xxgrep 搜索命令,搜到该方法被定义在 jvm.h 文件中, 实现文件在 OpenjdkJvm.cc 中。

jvm.h 文件路径:

1
libcore\ojluni\src\main\native\jvm.h

OpenjdkJvm.cc 文件路径:

1
art/openjdkjvm/OpenjdkJvm.cc

接下来分析 OpenjdkJvm 的实现。

OpenjdkJvm.cc中的流程分析

OpenjdkJvm.ccJVM_NativeLoad 的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

JavaVMExt中的流程分析

JavaVMExt.cc 源码路径位于:

1
art\runtime\jni\java_vm_ext.cc

该文件中 LoadNativeLibrary 的函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jclass caller_class,
std::string* error_msg) {
//..。省略
//使用OpenNativeLibrary打开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 << "]";
//...省略
//查找JNI_OnLoad方法,所以我们在jni开发中动态注册就需要重写这个方法是有道理额
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 << "\"]";
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);
//...省略
}
//...省略
}

以上方法中会打开 so 库并调用 JNI_OnLoad 方法。以上打开 so 使用了 “android::OpenNativeLibrary“, 下面准备追踪一下该方法的内部实现机制。

OpenNativeLibrary实现

在源码中搜索,找到 OpenNativeLibrary 实现源码路径如下:

1
system\core\libnativeloader\native_loader.cpp

该方法逻辑实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
jobject class_loader, const char* caller_location, jstring library_path,
bool* needs_native_bridge, char error_msg) {
#if defined(__ANDROID__)
UNUSED(target_sdk_version);
if (class_loader == nullptr) {
*needs_native_bridge = false;
if (caller_location != nullptr) {
android_namespace_t* boot_namespace = FindExportedNamespace(caller_location);
if (boot_namespace != nullptr) {
const android_dlextinfo dlextinfo = {
.flags = ANDROID_DLEXT_USE_NAMESPACE,
.library_namespace = boot_namespace,
};
void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
}
void* handle = dlopen(path, RTLD_NOW);
if (handle == nullptr) {
*error_msg = strdup(dlerror());
}
return handle;
}
std::lock_guard<std::mutex> guard(g_namespaces_mutex);
NativeLoaderNamespace* ns;
if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
// This is the case where the classloader was not created by ApplicationLoaders
// In this case we create an isolated not-shared namespace for it.
std::string create_error_msg;
if ((ns = g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */,
nullptr, library_path, nullptr, &create_error_msg)) == nullptr) {
*error_msg = strdup(create_error_msg.c_str());
return nullptr;
}
}
return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
#else
//...省略不相干的
#endif
}

以上代码中会根据 class_loadercaller_location 进行判断,是直接使用 dlopen 还是 android_dlopen_extOpenNativeLibraryInNamespace 来进行加载 so。

initarray和init加载分析

跟进android_dlopen_ext函数,该函数属于xref/bionic/libdl/libdl.cpp

1
2
3
4
void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) {
const void* caller_addr = __builtin_return_address(0);
return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}

跟进__loader_android_dlopen_ext函数,该函数属于 xref/bionic/linker/dlfcn.cpp

1
2
3
4
5
6
void* __loader_android_dlopen_ext(const char* filename,
int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
return dlopen_ext(filename, flags, extinfo, caller_addr);
}

继续跟进dlopen_ext函数,该函数属于 xref/bionic/linker/dlfcn.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
static void* dlopen_ext(const char* filename,
int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
ScopedPthreadMutexLocker locker(&g_dl_mutex);
g_linker_logger.ResetState();
void* result = do_dlopen(filename, flags, extinfo, caller_addr);
if (result == nullptr) {
__bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
return nullptr;
}
return result;
}

跟进do_dlopen函数,该函数属于xref/bionic/linker/linker.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {

// 省略...
ProtectedDataGuard guard;
// 已经完成了so的加载,返回了一个soinfo*的结构体指针
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();

if (si != nullptr) {
// 返回handle
void* handle = si->to_handle();
LD_LOG(kLogDlopen,
"... dlopen calling constructors: realpath=\"%s\", soname=\"%s\", handle=%p",
si->get_realpath(), si->get_soname(), handle);
// 调用构造函数,在call_constructors内部调用了initarray和init
si->call_constructors();
failure_guard.Disable();
LD_LOG(kLogDlopen,
"... dlopen successful: realpath=\"%s\", soname=\"%s\", handle=%p",
si->get_realpath(), si->get_soname(), handle);
return handle;
}

return nullptr;
}

所以initarrayinit是在dlopen内部被调用的,所以直接hook dlopen的话,不管是进入函数的时候hook还是返回的时候hook都是hook不到initarrayinit这两个函数的

call_constructors函数如下,函数位于xref/bionic/linker/linker_soinfo.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void soinfo::call_constructors() {
if (constructors_called) {
return;
}

// We set constructors_called before actually calling the constructors, otherwise it doesn't
// protect against recursive constructor calls. One simple example of constructor recursion
// is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
// 1. The program depends on libc, so libc's constructor is called here.
// 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
// 3. dlopen() calls the constructors on the newly created
// soinfo for libc_malloc_debug_leak.so.
// 4. The debug .so depends on libc, so CallConstructors is
// called again with the libc soinfo. If it doesn't trigger the early-
// out above, the libc constructor will be called again (recursively!).
constructors_called = true;

if (!is_main_executable() && preinit_array_ != nullptr) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT("\"%s\": ignoring DT_PREINIT_ARRAY in shared library!", get_realpath());
}

get_children().for_each([] (soinfo* si) {
si->call_constructors();
});

if (!is_linker()) {
bionic_trace_begin((std::string("calling constructors: ") + get_realpath()).c_str());
}

// DT_INIT should be called before DT_INIT_ARRAY if both are present.
// 在这里调用initarray和init
call_function("DT_INIT", init_func_, get_realpath());
call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());

if (!is_linker()) {
bionic_trace_end();
}
}

JNI_Onload加载分析

bool JavaVMExt::LoadNativeLibrary中有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   //查找JNI_OnLoad方法,所以我们在jni开发中动态注册就需要重写这个方法是有道理的
void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
// 其实sym有没有获取到值都不影响加载,JNI_OnLoad不是必须的
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 << "\"]";
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);
//...省略
}

FindSymbol函数如下,该函数位于xref/art/runtime/jni/java_vm_ext.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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, shorty)
: FindSymbolWithoutNativeBridge(symbol_name);
}

// 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());
}

JNI_OnLoad方法是在dlopen完全执行完毕之后调用的

要想hook JNI_OnLoad方法,直接在dlopen onleave的时候去hook

要想hook initarrayinit方法,需要等到soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);执行完毕,即so加载完毕之后再去hook,不然基址都获取不到