JNI原理 模仿System.loadLibrary和dlopen使用

前言

如下图所示,OpenNativeLibrary代码中使用了dlopen打开动态库,本文按照该源码中调用dlopen、dlsym来调用so里的方法,加强一下理解。
继上文梳理了LoadLibrary源码流程,本文就是模仿该流程,在Java层获取到要调用so路径后,传入c层调用dlopen打开,并通过dlsym调用方法后dlclose结束。image.png

函数说明

  • 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,错误为:
  • 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动态库并调用执行方法,对这块稍微熟悉了点。
若有错误请指正。

猜你喜欢

转载自blog.csdn.net/u014099894/article/details/111699084