android load,android loadLibrary源码分析

前言

本文介绍android4与android8中调用.init .init_array段中函数的过程,找到关键函数方便我们hook它

Android4

public static void loadLibrary(String libName) {

Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());

}

void loadLibrary(String libraryName, ClassLoader loader) {

//这里基本不会为null

if (loader != null) {

...

}

String filename = System.mapLibraryName(libraryName);

List candidates = new ArrayList();

String lastError = null;

for (String directory : mLibPaths) {

String candidate = directory + filename;

candidates.add(candidate);

if (IoUtils.canOpenReadOnly(candidate)) {

// 调用doLoad函数加载so库文件

String error = doLoad(candidate, loader);

if (error == null) {

return; // We successfully loaded the library. Job done.

}

lastError = error;

}

}

if (lastError != null) {

throw new UnsatisfiedLinkError(lastError);

}

throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);

}

这里关键就是doLoad函数加载so

private String doLoad(String name, ClassLoader loader) {

String ldLibraryPath = null;

if (loader != null && loader instanceof BaseDexClassLoader) {

// so库文件的文件路径

ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();

}

synchronized (this) {

// 调用native方法nativeLoad加载so库文件

return nativeLoad(name, loader, ldLibraryPath);

}

}

private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);

这里得到so文件路径后调用了native方法nativeLoad去加载so

这个native的源码按照文件路径格式找到是在java_lang_Runtime.cpp中

/dalvik/vm/native/java_lang_Runtime.cpp

/*

* 参数args[0]保存的是一个Java层的String对象,这个String对象描述的就是要加载的so文件,

* 函数Dalvik_java_lang_Runtime_nativeLoad首先是调用函数dvmCreateCstrFromString来将它转换成一个C++层的字符串fileName,

* 然后再调用函数dvmLoadNativeCode来执行加载so文件的操作。

*/

static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,

JValue* pResult)

{

StringObject* fileNameObj = (StringObject*) args[0];

Object* classLoader = (Object*) args[1];

StringObject* ldLibraryPathObj = (StringObject*) args[2];

assert(fileNameObj != NULL);

char* fileName = dvmCreateCstrFromString(fileNameObj);

if (ldLibraryPathObj != NULL) {

char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);

void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");

if (sym != NULL) {

typedef void (*Fn)(const char*);

Fn android_update_LD_LIBRARY_PATH = reinterpret_cast(sym);

(*android_update_LD_LIBRARY_PATH)(ldLibraryPath);

} else {

ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");

}

free(ldLibraryPath);

}

StringObject* result = NULL;

char* reason = NULL;

// 调用dvmLoadNativeCode函数加载so库文件

bool success = dvmLoadNativeCode(fileName, classLoader, &reason);

if (!success) {

const char* msg = (reason != NULL) ? reason : "unknown failure";

result = dvmCreateStringFromCstr(msg);

dvmReleaseTrackedAlloc((Object*) result, NULL);

}

free(reason);

free(fileName);

RETURN_PTR(result);

}

这里就把路径java字符串转化为C++层的字符串,最后调用dvmLoadNativeCode(fileName, classLoader, &reason);

bool dvmLoadNativeCode(const char* pathName, Object* classLoader,

char** detail)

{

...

Thread* self = dvmThreadSelf();

ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);

// 先调用dlopen函数加载so库文件到内存中

handle = dlopen(pathName, RTLD_LAZY);

dvmChangeStatus(self, oldStatus);

...

/* create a new entry */

SharedLib* pNewEntry;

pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));

pNewEntry->pathName = strdup(pathName);

pNewEntry->handle = handle;

pNewEntry->classLoader = classLoader;

dvmInitMutex(&pNewEntry->onLoadLock);

pthread_cond_init(&pNewEntry->onLoadCond, NULL);

pNewEntry->onLoadThreadId = self->threadId;

/* try to add it to the list */

SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);

if (pNewEntry != pActualEntry) {

ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",

pathName, classLoader);

freeSharedLibEntry(pNewEntry);

return checkOnLoadResult(pActualEntry);

} else {

if (verbose)

ALOGD("Added shared lib %s %p", pathName, classLoader);

bool result = false;

void* vonLoad;

int version;

// 获取前面加载的so库文件中的导出函数JNI_OnLoad的调用地址

vonLoad = dlsym(handle, "JNI_OnLoad");

if (vonLoad == NULL) {

ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);

result = 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.)

*/

// 保存获取到的JNI_OnLoad函数的调用地址

OnLoadFunc func = (OnLoadFunc)vonLoad;

Object* prevOverride = self->classLoaderOverride;

self->classLoaderOverride = classLoader;

oldStatus = dvmChangeStatus(self, THREAD_NATIVE);

if (gDvm.verboseJni) {

//这里可以用户ida的查找

ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);

}

// 调用so库文件中的导出函数JNI_OnLoad

version = (*func)(gDvmJni.jniVm, NULL);

dvmChangeStatus(self, oldStatus);

self->classLoaderOverride = prevOverride;

if (version == JNI_ERR) {

*detail = strdup(StringPrintf("JNI_ERR returned from JNI_OnLoad in \"%s\"",

pathName).c_str());

} else if (dvmIsBadJniVersion(version)) {

*detail = strdup(StringPrintf("Bad JNI version returned from JNI_OnLoad in \"%s\": %d",

pathName, version).c_str());

} else {

result = true;

}

if (gDvm.verboseJni) {

ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]",

(result ? "successfully" : "failure"), pathName);

}

}

...

return result;

}

}

在该函数中先调用dlopen函数加载so库文件到内存中,然后调用dlsym函数获取so库文件中JNI_OnLoad函数的导出地址,然后调用JNI_OnLoad函数执行开发者自定义的代码和实现jni函数的注册。

这里我们就找到了JNI_OnLoad函数的执行处

我们在来看看dlopen函数的代码

这个函数在/bionic/linker/dlfcn.cpp

// dlopen函数调用do_dlopen函数实现so库文件的加载

void* dlopen(const char* filename, int flags) {

// 信号互斥量(锁)

ScopedPthreadMutexLocker locker(&gDlMutex);

// 调用do_dlopen()函数实现so库文件的加载

soinfo* result = do_dlopen(filename, flags);

// 判断so库文件是否加载成功

if (result == NULL) {

__bionic_format_dlerror("dlopen failed", linker_get_error_buffer());

return NULL;

}

// 返回加载后so库文件的文件句柄

return result;

}

可以看到他的关键代码是do_dlopen

/bionic/linker/linker.cpp

// 实现对so库文件的加载和执行构造函数

soinfo* do_dlopen(const char* name, int flags) {

// 判断加载so文件的flags是否符合要求

if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {

DL_ERR("invalid flags to dlopen: %x", flags);

return NULL;

}

// 修改内存属性为可读可写

set_soinfo_pool_protection(PROT_READ | PROT_WRITE);

// find_library会判断so是否已经加载,

// 如果没有加载,对so进行加载,完成一些初始化工作

soinfo* si = find_library(name);

// 判断so库问价是否加载成功

if (si != NULL) {

// ++++++ so加载成功,调用构造函数 ++++++++

si->CallConstructors();

// ++++++++++++++++++++++++++++++++++++++++

}

// 设置内存属性为可读

set_soinfo_pool_protection(PROT_READ);

// 返回so内存模块

return si;

}

so加载成功,调用构造函数 si->CallConstructors();

// so库文件加载完毕以后调用构造函数

void soinfo::CallConstructors() {

if (constructors_called) {

return;

}

constructors_called = true;

if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {

// The GNU dynamic linker silently ignores these, but we warn the developer.

PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",

name, preinit_array_count);

}

// 调用DT_NEEDED类型段的构造函数

if (dynamic != NULL) {

for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {

if (d->d_tag == DT_NEEDED) {

const char* library_name = strtab + d->d_un.d_val;

TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);

find_loaded_library(library_name)->CallConstructors();

}

}

}

TRACE("\"%s\": calling constructors", name);

// DT_INIT should be called before DT_INIT_ARRAY if both are present.

// 先调用.init段的构造函数

CallFunction("DT_INIT", init_func);

// 再调用.init_array段的构造函数

CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);

}

其实这里就看到.init段和.init_array段构造函数的调用点了。

其中init_func和init_array在soinfo_link_image函数中对其赋值

static bool soinfo_link_image(soinfo* si) {

...

case DT_INIT:

si->init_func = reinterpret_cast(base + d->d_un.d_ptr);

DEBUG(“%s constructors (DT_INIT) found at %p”, si->name, si->init_func);

break;

case DT_INIT_ARRAY:

si->init_array = reinterpret_cast(base + d->d_un.d_ptr);

DEBUG(“%s constructors (DT_INIT_ARRAY) found at %p”, si->name, si->init_array);

break;

...

}

我们现在来看看init是如何调用的吧

// 构造函数调用的实现

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {

// 判断构造函数的调用地址是否符合要求

if (function == NULL || reinterpret_cast(function) == static_cast(-1)) {

return;

}

// function_name被调用的函数名称,function为函数的调用地址

// [ Calling %s @ %p for '%s' ] 字符串为在 /system/bin/linker 中查找.init和.init_array段调用函数的关键

TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);

// 调用function函数

function();

TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);

// The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures

// are still writable. This happens with our debug malloc (see http://b/7941716).

set_soinfo_pool_protection(PROT_READ | PROT_WRITE);

其实最核心的一句话就是function();,我们可以通过上面的字符串信息在ida中找到这个位置

再来看看.init_array中的实现

void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {

if (functions == NULL) {

return;

}

TRACE("[ Calling %s (size %d) @ %p for '%s' ]", array_name, count, functions, name);

int begin = reverse ? (count - 1) : 0;

int end = reverse ? -1 : count;

int step = reverse ? -1 : 1;

// 循环遍历调用.init_arrayt段中每个函数

for (int i = begin; i != end; i += step) {

TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);

// .init_arrayt段中,每个函数指针的调用和上面的.init段的构造函数的实现是一样的

CallFunction("function", functions[i]);

}

TRACE("[ Done calling %s for '%s' ]", array_name, name);

}

在这里可以看到与CallFunction没有什么区别就是从这个函数数组中循环去执行CallFunction罢了。

经过上面的代码分析可以得出结论执行顺序init>init_array>JNI_OnLoad

Android8

在Android8中private static native String nativeLoad的调用现在在

/art/runtime/openjdkjvm/OpenjdkJvm.cc

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,

jstring javaFilename,

jobject javaLoader,

jstring javaLibrarySearchPath) {

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,

javaLibrarySearchPath,

&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());

}

LoadNativeLibrary的调用如下

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,

const std::string& path,

jobject class_loader,

jstring library_path,

std::string* error_msg) {

...

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,

&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 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;

//得到JNI_OnLoad函数地址

void* sym = library->FindSymbol("JNI_OnLoad", nullptr);

if (sym == nullptr) {

VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";

was_successful = true;

} else {

ScopedLocalRef old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));

self->SetClassLoaderOverride(class_loader);

VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";

//定义JNI_OnLoadFn

typedef int (*JNI_OnLoadFn)(JavaVM*, void*);

JNI_OnLoadFn jni_on_load = reinterpret_cast(sym);

//调用JNI_OnLoad

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

...

VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")

<< " from JNI_OnLoad in \"" << path << "\"]";

}

library->SetResult(was_successful);

return was_successful;

}

在android8中dlopen函数变化的非常多,但是总体的逻辑不变,我们来看看soinfo::CallConstructors()变成什么样了。

void soinfo::call_constructors() {

if (constructors_called) {

return;

}

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.

// 先调用.init段的构造函数

call_function("DT_INIT", init_func_, get_realpath());

// 再调用.init_array段的构造函数

call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());

if (!is_linker()) {

bionic_trace_end();

}

}

可以看到它的命名方式有了一些变化,再来看看call_array

template

static void call_array(const char* array_name __unused,

F* functions,

size_t count,

bool reverse,

const char* realpath) {

if (functions == nullptr) {

return;

}

TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);

int begin = reverse ? (count - 1) : 0;

int end = reverse ? -1 : count;

int step = reverse ? -1 : 1;

for (int i = begin; i != end; i += step) {

TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);

call_function("function", functions[i], realpath);

}

TRACE("[ Done calling %s for '%s' ]", array_name, realpath);

}

这个更加没啥变化

最后看看我们最关心的call_function

static void call_function(const char* function_name __unused,

linker_ctor_function_t function,

const char* realpath __unused) {

if (function == nullptr || reinterpret_cast(function) == static_cast(-1)) {

return;

}

TRACE("[ Calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);

function(g_argc, g_argv, g_envp);

TRACE("[ Done calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);

}

static void call_function(const char* function_name __unused,

linker_dtor_function_t function,

const char* realpath __unused) {

if (function == nullptr || reinterpret_cast(function) == static_cast(-1)) {

return;

}

TRACE("[ Calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);

function();

TRACE("[ Done calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);

}

可以看到到了Android8之后,call_function有了两个函数,它的主要区别在于第二个参数function的类型,这个是根据call_array的F来判断的。他们分别定义如下

typedef void (*linker_ctor_function_t)(int, char**, char**);

typedef void (*linker_dtor_function_t)();

其实在Androi8中调用.init .init_array走的函数基本都是

static void call_function(const char* function_name __unused, linker_ctor_function_t function, const char* realpath __unused);

所以我们在Android8中需要hook的就是这个函数。

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值