dalvik浅析二:jni、so

  android大多使用java来开发,java中有个概念叫jni。当然说到jni,必然是少不了native code。在android中就是so库。我们来分析下jni在android dalvik的使用,以下篇幅是我对Dalvik虚拟机JNI方法的注册过程分析文章的学习和注解。在这之前先说几个概念:

  JavaVM:虚拟机实例,也可以通过全局变量gDvm所描述的一个DvmGlobals结构体的成员变量vmList来描述的;

  JNIEnv:用来描述当前线程的Java环境,利用此结构可以调用在Zygote中注册(看Zygote的启动过程)到dalvik里的jni方法

  jobject:来描述当前正在执行JNI方法的Java对象

  下图取自老罗的博客(下文就是围绕此图展开)

  

  我们在java函数在load so库:

System.loadLibrary("nanosleep");    

  so库的编写:

static jint shy_luo_jni_ClassWithJni_nanosleep(JNIEnv* env, jobject clazz, jlong seconds, jlong nanoseconds)
{
    struct timespec req;
    req.tv_sec  = seconds;
    req.tv_nsec = nanoseconds;

    return nanosleep(&req, NULL);
}

static const JNINativeMethod method_table[] = {
    {"nanosleep", "(JJ)I", (void*)shy_luo_jni_ClassWithJni_nanosleep},
};

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
      JNIEnv* env = NULL;
    jint result = -1;

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

    jniRegisterNativeMethods(env, "shy/luo/jni/ClassWithJni", method_table, NELEM(method_table));

    return JNI_VERSION_1_4;
}

  java层在loadLibrary so库时,系统其实做了这么几件事(上图step 4):

  1 调用dlopen在进程加载so库;

  2 调用dlsym获得so库中名称为“JNI_OnLoad”的函数的地址并保存在保存在函数指针func中:func= dlsym(handle, "JNI_OnLoad");

  3 执行so库中JNI_OnLoad函数: version = (*func)(gDvm.vmList, NULL);

  这个时候我们的视线转移到C++层:JNI_OnLoad(在这里注册jni方法)。看代码,实际是调用jniRegisterNativeMethods函数。但是看上图我们知道实际之前几个函数实质突破,还是靠dvmRegisterJNIMethod来执行:

static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
    const char* signature, void* fnPtr)
{  // 解释下参数:  // clazz:类名"shy/luo/jni/ClassWithJni";  // methodName:需要注册的jni方法名 nanosleep;  // signature:方法的签名 实质是方法的参数和返回值,区别不同参数的函数  // fnPtr: jni方法函数地址 即shy_luo_jni_ClassWithJni_nanosleep函数;dalvik执行的就是这个函数,很重要哎
    Method* method;
    ......
    method = dvmFindDirectMethodByDescriptor(clazz, methodName, signature);
    ......
    dvmUseJNIBridge(method, fnPtr);
    ......
}

  在这里我要补充的是注意dvmFindDirectMethodByDescriptor函数。jni方法在java层对应的函数就是个空的,而jni注册就是要把jni方法绑定到对应的java层函数体中。那我们怎么找到让他们对应起来呢?dvmFindDirectMethodByDescriptor利用methodName和signature参数来达到上述目的。在dvmFindDirectMethodByDescriptor中,得到class类的函数列表methods;循坏比较methods[index]的args、returnType和signature是否相等,若相等则为jni方法找到了在java层的函数(jni:我在上层也是有人滴^_^)。ok,找到method了,赶快绑定啊也可别让她逃走了啊。

void dvmUseJNIBridge(Method* method, void* func)
{
    DalvikBridgeFunc bridge = shouldTrace(method)
        ? dvmTraceCallJNIMethod
        : dvmSelectJNIBridge(method);
    dvmSetNativeFunc(method, bridge, func);
}  
 这里有个bridge的东东,我们这里先不看后面会提及(详情看老罗的文章吧)。直接看dvmSetNativeFunc
void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func,
    const u2* insns)
{
    ......
    // 参数func  = bridge   // 参数 insns = func(dvmSetNativeFunc(method, bridge, func)); 即func = (void*)shy_luo_jni_ClassWithJni_nanosleep
if (insns != NULL) {
        /* update both, ensuring that "insns" is observed first */
        method->insns = insns;
        android_atomic_release_store((int32_t) func,
            (void*) &method->nativeFunc);
    } else {
        /* only update nativeFunc */
        method->nativeFunc = func;
    }  

    ......
}
 在dvmSetNativeFunc函数,既然是把bridge赋值给method->nativeFunc,shy_luo_jni_ClassWithJni_nanosleep赋值给method->insns,那什么时候才会执行到shy_luo_jni_ClassWithJni_nanosleep啊(在dalvik中,若method为native则会执行method->nativeFunc)!带着这个疑问,我们回头看dvmSelectJNIBridge:
/*
 * Returns the appropriate JNI bridge for ‘method‘, also taking into account
 * the -Xcheck:jni setting.
 */
static DalvikBridgeFunc dvmSelectJNIBridge(const Method* method)
{
    enum {
        kJNIGeneral = 0,
        kJNISync = 1,
        kJNIVirtualNoRef = 2,
        kJNIStaticNoRef = 3,
    } kind;
    static const DalvikBridgeFunc stdFunc[] = {
        dvmCallJNIMethod_general,
        dvmCallJNIMethod_synchronized,
        dvmCallJNIMethod_virtualNoRef,
        dvmCallJNIMethod_staticNoRef
    };
    static const DalvikBridgeFunc checkFunc[] = {
        dvmCheckCallJNIMethod_general,
        dvmCheckCallJNIMethod_synchronized,
        dvmCheckCallJNIMethod_virtualNoRef,
        dvmCheckCallJNIMethod_staticNoRef
    };  

    bool hasRefArg = false;  

    if (dvmIsSynchronizedMethod(method)) {
        /* use version with synchronization; calls into general handler */
        kind = kJNISync;
   .....if (hasRefArg) {
            /* use general handler to slurp up reference args */
            kind = kJNIGeneral;
        } else {
            /* virtual methods have a ref in args[0] (not in signature) */
            if (dvmIsStaticMethod(method))
                kind = kJNIStaticNoRef;
            else
                kind = kJNIVirtualNoRef;
        }
    }  

    return dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];
}  
直接看最后返回dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];假设返回stdFunc[kind]。看上面stdFunc定义,可知bridge其实是函数。我们再假定是最普通dvmCallJNIMethod_general,那么在dvmSetNativeFunc里method->nativeFunc = dvmCallJNIMethod_general。ok,那我们就看看dvmCallJNIMethod_general是在哪里执行我们的shy_luo_jni_ClassWithJni_nanosleep。
void dvmCallJNIMethod_general(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
    ......
    dvmPlatformInvoke(env, staticMethodClass,
        method->jniArgInfo, method->insSize, modArgs, method->shorty,
        (void*)method->insns, pResult);
    ......
}

  接着看dvmPlatformInvoke:  

void dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo, int argc,
    const u4* argv, const char* shorty, void* func, JValue* pReturn)
{
    ......
    ffi_call(&cif, FFI_FN(func), pReturn, values);
}

  wow,看到没有最终还是调用了method->insns(在java函数中,dalvik中的method->insns存的是函数体的dex代码)即shy_luo_jni_ClassWithJni_nanosleep。

  ok,上图中的步骤已全部走完。发现jni注册实质就是把native函数体绑定到对应的java层函数体,让dalvik发现函数是native时有native代码可以执行。

  思考:

  1 method是native时,dalvik才会调用method->nativeFunc来执行;那这个native标志是在什么时候被设置呢?dex被载入dalvik时?

  2 so库加载过程时dlopen载入,然后执行调用其JNI_OnLoad函数。那具体的执行流程是?so库的加固是否在这里做文章呢?

  参考资料:

  1 老罗的android之旅

时间: 2024-10-14 03:40:20

dalvik浅析二:jni、so的相关文章

IOS RunLoop浅析 二

上一篇我们说了runloop 的几种模式,那么我们在模式中又要做些什么呢??? 模式中有三个模块: 事件源(输入源) Source Source: 按照官方文档分类 Port-Based Custom Input Cocoa Perform Selector 按照函数调用栈,Source的分类 Source0:非基于Port的 Source1:基于Port的,通过内核和其他线程通信,接受,分发系统事件. (这里没什么太大用,剩下的Source概念我就不介绍了有兴趣可以去别处查查) 观察者 Obs

iOS-静态库,动态库,framework浅析(二)

创建.a静态库 第一步,新建工程.     一般使用工程名就使用库的名称,比如我这里用FMDB来创建静态库,我的工程名就取名为FMDB,创建的.a静态库就是libFMDB.a. 使用静态库模板新建工程.png 创建的工程.png 第二步,删除系统默认创建的[FMDB.h]和[FMDB.m]文件,导入需要打包的源文件. 导入源文件后.png 第三步(方式一),修改项目配置 修改配置.png 点击上图中的[3],弹出的列表中选择[New Headers Phase],打开[Headers (0 it

Android之Activity生命周期的浅析(二)

??上一篇文章,我们主要分析了Activity的正常情况下生命周期及其方法,本篇主要涉及内容为Activity的异常情况下的生命周期. Activity异常生命周期 ??异常的生命周期是指Activity被系统回收或者当前设备的Configuration发生变化(一般指横竖屏切换)从而导致Activity被销毁重建.异常的生命周期主要分以下两种情况: 1.相关的系统配置发生改变导致Activity被杀死并重新创建(一般指横竖屏切换) 2.内存不足导致低优先级的Activity被杀死 1.相关的系

云端高性能技术架构浅析二

本文主要对缓存中的Memcached技术进行介绍. 1 Memcached 1.1 Memcached简介 Memcached是一个高性能的分布式对象缓存系统,用于动态Web应用,以减轻数据库负载[1].它通过在内存中缓存数据和对象来减少应用程序读取数据库的次数,从而提高网站的性能.如图1是Memcached在网站中的位置示意图. 图1 Memcached位置示意图 Memcached以键值对的形式将数据(或对象)缓存在内存中,虽然使用到了多个服务节点,但是和一般分布式缓存系统不同的是,每一份数

linux 多任务浅析(二)

在一中我们说到了多任务即不同进程都有自己的独立的代码段,数据段,堆栈段.看似利用这个原理能将多任务隔离,但是他们各个段的起始地址又都是0,这就是很无语了,不过没关系,分页的时候会接着讲.这篇文章说一下多任务的切换. x86体系从硬件上支持任务间的切换,也就说实际上linux同一时间只是在运行一个任务,但是由于他可以在很短的时间在不同的任务间来回切换执行,我们感觉上他是多个任务一起执行的.既然要在任务间来回切换那么势必就要记录每一个进程被切换时的状态,以便切换回来的时候恢复.所有每一个进程都有属于

【MySQL】MySQL锁和隔离级别浅析二 之 INSERT

最近在整理线上性能时,发现一台线上DB出现两个insert产生的死锁问题 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 150119 10:55:08 *** (1) TRANSACTION: TRANSACTION 578E79C8, ACTIVE 0 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 7 lock struct(

CoInitialize浅析二

最近工作比较忙,在粗略分析了CoInitialize之后我们一直没有再深入研究,下面言归正传.前面我们初步了解到了CoInitialize其实是通过调用CoInitializeEx来实现功能的,而后者最终调用了wCoInitializeEx函数,如果能进一步了解这个函数的内部实现,那么我们对COM环境的初始化过程就比较清晰了.好,我们下面继续看wCoInitializeEx的汇编代码,这次我们分段来看. 769AF092 arg_0           = dword ptr  8769AF09

Android Launcher浅析(二)

1,如何修改主菜单图标的位置? [DESCRIPTION] 默认主菜单图标在中间,如何修改它的位置? Launcher3: DynamicGrid.java文件 hotseatAllAppsRank = (int) (numColumns/2); //默认是列数除以2取整,可以设置为需要的值 Launcher2: 1. 请修改packages/apps/Laucher2/res/values/config.xml 中hotseat_all_apps_index的值,例如修改为1 2. 默认在ho

Android 4.4 Kitkat Phone工作流程浅析(十二)__4.4小结与5.0概览

前置文章: <Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划> <Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析> <Android 4.4 Kitkat Phone工作流程浅析(三)__MO(去电)流程分析> <Android 4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程简析> <Android 4.4 Kitkat Phone工作流程浅析(五)__M