前言
如下图所示,OpenNativeLibrary代码中使用了dlopen
打开动态库,本文按照该源码中调用dlopen、dlsym来调用so里的方法,加强一下理解。
继上文梳理了LoadLibrary源码流程,本文就是模仿该流程,在Java层获取到要调用so路径后,传入c层调用dlopen
打开,并通过dlsym
调用方法后dlclose
结束。
函数说明
-
dlopen()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。
- RTLD_LAZY
Each external function reference is bound the first time the function is called.
在dlopen返回前,对于动态库中存在的未定义的变量(如外部变量extern,也可以是函数)不执行解析,就是不解析这个变量的地址,直到首次调用; - RTLD_NOW
All external function references are bound immediately during the call to dlopen().
在dlopen返回前,解析出每个未定义变量的地址,如果解析不出来,在dlopen会返回NULL,错误为:
- RTLD_LAZY
-
dlerror()返回出现的错误。
-
使用dlsym()通过动态链接库操作句柄与符号,返回符号对应的地址。
-
使用dlclose()来卸载打开的库。
示例
第一步:文件test.cpp打成libtest-lib.so
// 文件1 test.cpp:
int fibonacci(int n) {
if (n == 1) return 1;
if (n == 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
第二步:文件testdlopen.cpp打出libdlopen-lib.so
// 文件2:testdlopen.cpp
extern "C"
JNIEXPORT void JNICALL
Java_com_baiiu_jnitest_dlopen_DLOpenFragment_testdlopen(JNIEnv *env, jobject thiz, jstring jstr) {
const char *soPath = env->GetStringUTFChars(jstr, JNI_FALSE);
LOGD("native--> soPath: %s", soPath);
//
void *handle = dlopen(soPath, RTLD_NOW);
char *error_msg = nullptr;
if (handle == nullptr) {
error_msg = strdup(dlerror());
LOGD("%s", error_msg);
}
LOGD("handle is %p", handle);
// void *sym = dlsym(handle, "fibonacci"); // 注意函数名称,这里是个坑点,readelf so发现名称变化了
void *sym = dlsym(handle, "_Z9fibonaccii");
if (sym == nullptr) {
LOGD("can not find the function");
return;
}
using FIBONACCI = int (*)(int);
FIBONACCI fibonacci = reinterpret_cast<FIBONACCI>(sym);
int result = (*fibonacci)(5);
LOGD("fibonacci result is: %d", result);
dlclose(handle);
env->ReleaseStringUTFChars(jstr, soPath);
}
第三步:模仿System#loadLibrary
Java代码中找到libtest-lib.so
路径,本来是hook DexPathList进行查找的,代码也实现了,突然发现BaseDexClassLoader里的findLibrary竟然是public的,那不就简单了么,直接调用,喜滋滋。
private static final String LIB_NAME = "test-lib";
private void test() {
try {
// 直接调用BaseDexClassLoader#findLibrary找到test-lib路径,传到c层进行dlopen,模仿System#loadLibrary操作
BaseDexClassLoader dexPathClassLoader = (BaseDexClassLoader) Binder.class.getClassLoader();
String targetSoAbsolutePath = dexPathClassLoader.findLibrary(LIB_NAME);
android.util.Log.e("mLogU", "ClassLoader#findLibrary: " + targetSoAbsolutePath);
testdlopen(targetSoAbsolutePath +"\lib" + LIB_NAME + ".so");
} catch (Exception e) {
android.util.Log.e("mLogU", e.toString());
}
}
private native void testdlopen(String targetSoAbsolutePath);
结语
本文模仿System#loadLibrary流程,并在c层直接打开so动态库并调用执行方法,对这块稍微熟悉了点。
若有错误请指正。