Android JNI_OnLoad()函数

本文介绍了在Android中,当VM执行到C组件里的System.loadLibrary()函数时,如何通过JNI_OnLoad()函数告知VM所使用的JNI版本,并进行初始化设置。同时,文章还探讨了如何利用JNIEnv指针在多线程环境下保证本地函数的安全执行。

JVM加载完本地库会调用JNI_OnLoad()函数


当Android的 VM(VirtualMachine)执行到C组件(即*so文件)里的System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。它的用途有二:

1.       告诉VM此C组件使用那一个JNI版本。如果你的*.so文件没有提供JNI_OnLoad()函数,VM会默认该*.so檔是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的 java.nio.ByteBuffer, 就必须藉由JNI_OnLoad()函数来告知VM。

2.       由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization)。

 

例如,在Android的/system/lib/libmedia_jni.so档案里,就提供了JNI_OnLoad()函数,其程序代码片段为:

 

//#define LOG_NDEBUG 0

#define LOG_TAG "MediaPlayer-JNI"

………

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

    JNIEnv* env = NULL;

    jint result = -1;

 

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        LOGE("ERROR: GetEnv failed\n");

        goto bail;

    }

    assert(env != NULL);

    if (register_android_media_MediaPlayer(env) < 0) {

        LOGE("ERROR: MediaPlayer native registration failed\n");

        goto bail;

    }

    if (register_android_media_MediaRecorder(env) < 0) {

        LOGE("ERROR: MediaRecorder native registration failed\n");

        goto bail;

    }

    if (register_android_media_MediaScanner(env) < 0) {

        LOGE("ERROR: MediaScanner native registration failed\n");

        goto bail;

    }

    if (register_android_media_MediaMetadataRetriever(env) < 0) {

        LOGE("ERROR: MediaMetadataRetriever native registration failed\n");

        goto bail;

    }

    /* success -- return valid version number */

    result = JNI_VERSION_1_4;

bail:

    return result;

}

// KTHXBYE

 

    此函数回传JNI_VERSION_1_4值给VM,于是VM知道了其所使用的JNI版本了。此外,它也做了一些初期的动作(可呼叫任何本地函数),例如指令:

 

   if (register_android_media_MediaPlayer(env) < 0) {

        LOGE("ERROR: MediaPlayer native registration failed\n");

        goto bail;

    }

 

就将此组件提供的各个本地函数(NativeFunction)登记到VM里,以便能加快后续呼叫本地函数之效率。

    JNI_OnUnload()函数与JNI_OnLoad()相对应的。在加载C组件时会立即呼叫JNI_OnLoad()来进行组件内的初期动作;而当VM释放该C组件时,则会呼叫JNI_OnUnload()函数来进行善后清除动作。当VM呼叫JNI_OnLoad()或JNI_Unload()函数时,都会将VM的指标(Pointer)传递给它们,其参数如下:

 

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

      ………

}

 

jint JNI_OnUnload(JavaVM* vm, void* reserved)

{

      ………

}

 

在JNI_OnLoad()函数里,就透过VM之指标而取得JNIEnv之指标值,并存入env指针变量里,如下述指令:

 

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

    JNIEnv* env = NULL;

    jint result = -1;

 

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        LOGE("ERROR: GetEnv failed\n");

        goto bail;

    }

 

   由于VM通常是多执行绪(Multi-threading)的执行环境。每一个执行绪在呼叫JNI_OnLoad()时,所传递进来的JNIEnv指标值都是不同的。为了配合这种多执行绪的环境,C组件开发者在撰写本地函数时,可藉由JNIEnv指标值之不同而避免执行绪的数据冲突问题,才能确保所写的本地函数能安全地在Android的多执行绪VM 里安全地执行。基于这个理由,当在呼叫C组件的函数时,都会将JNIEnv指标值传递给它,如下:

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

JNIEnv* env = NULL;

  ……….

    if (register_android_media_MediaPlayer(env) < 0) {

       …….

    }

     }

这JNI_OnLoad()呼叫register_android_media_MediaPlayer(env)函数时,就将env指标值传递过去。如此,在register_android_media_MediaPlayer()函数就能藉由该指标值而区别不同的执行绪,以便化解数据冲突的问题。

      例如,在register_android_media_MediaPlayer()函数里,可撰写下述指令:

       if ((*env)->MonitorEnter(env, obj) != JNI_OK) {

            ………

 }

 

    查看是否已经有其它执行绪进入此对象,如果没有,此执行绪就进入该对象里执行了。还有,也可撰写下述指令:

 

        if ((*env)->MonitorExit(env, obj) != JNI_OK) {

             ………

         }

 

查看是否此执行绪正在此对象内执行,如果是,此执行绪就会立即离开。

### 如何在 IDA 中处理 JNI_OnLoad 函数 #### 定位 JNI_OnLoad 函数 为了定位 `JNI_OnLoad` 函数,可以利用已知的偏移量来计算其实际地址。对于给定的例子,如果 `libcrackme.so` 文件中的 `JNI_OnLoad` 函数相对于起始位置有固定的偏移,则可以通过简单的加法运算找到该函数的具体地址[^3]。 例如,假设 SO 文件加载基址为 AA238000H 并且 `JNI_OnLoad` 的 RVA (Relative Virtual Address) 是 00001B9CH, 那么真实的内存地址将是: ```assembly AA238000H + 00001B9CH = AA239B9CH ``` 一旦获得了这个确切的位置,在 IDA Pro 中就可以通过按 G 键并输入上述地址来进行导航;接着使用 F2 设置断点以便于后续调试操作。 #### 动态调试准备 为了让动态调试更加顺利,建议先让应用处于等待状态直到连接上外部调试工具再继续运行。这通常涉及到修改 Android 应用启动参数使其支持远程调试模式[^4]。具体做法是在命令行中执行如下指令: ```bash adb shell am start -D -n com.example.app/.MainActivity ``` 这里的 `-D` 参数指示系统暂停应用程序直至有一个合适的调试器被挂载上去为止。 #### 调试过程中注意事项 值得注意的是,尽管 `JNI_OnLoad` 常用于初始化 Java Native Interface 扩展功能,但它并非总是最先被执行的部分。实际上,在某些情况下,操作系统可能会优先调用 ELF 文件内的 `.init` 或者 `.init_array` 段里的其他代码片段[^2]。这意味着仅依赖于 `JNI_OnLoad` 来部署保护机制可能不够安全可靠,开发者应当考虑更全面的安全策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值