• 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库;看我 android 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时?

        在dex文件里的class—>method的accessflag属性:定义在/external/emma/core/java12/com/vladium/jcd/cls/IAccessFlags.java

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

        看后面elf格式、so加载文章

     

      参考资料:

      1 老罗的android之旅

     

     

  • 相关阅读:
    高并发系统设计(二十):分布式架构如何跟踪排查慢请求问题?
    Git将多个commit合并成一个commit
    高并发系统设计(十九)【注册中心】:微服务架构结合RPC框架如何做到分布式系统寻址?
    高并发系统设计(十八):【RPC框架】10万QPS下如何实现毫秒级的服务调用?
    AfxSocketInit()
    TEXTMETRIC 结构详解
    OnInitialUpdate函数
    SetForegroundWindow
    GetSafeHwnd()函数
    MFC之CCommandLineInfo
  • 原文地址:https://www.cnblogs.com/vendanner/p/4816911.html
Copyright © 2020-2023  润新知