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库:

  1. System.loadLibrary("nanosleep");

  so库的编写:

  1. static jint shy_luo_jni_ClassWithJni_nanosleep(JNIEnv* env, jobject clazz, jlong seconds, jlong nanoseconds)
  2. {
  3. struct timespec req;
  4. req.tv_sec = seconds;
  5. req.tv_nsec = nanoseconds;
  6.  
  7. return nanosleep(&req, NULL);
  8. }
  9.  
  10. static const JNINativeMethod method_table[] = {
  11. {"nanosleep", "(JJ)I", (void*)shy_luo_jni_ClassWithJni_nanosleep},
  12. };
  13.  
  14. extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
  15. {
  16. JNIEnv* env = NULL;
  17. jint result = -1;
  18.  
  19. if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
  20. return result;
  21. }
  22.  
  23. jniRegisterNativeMethods(env, "shy/luo/jni/ClassWithJni", method_table, NELEM(method_table));
  24.  
  25. return JNI_VERSION_1_4;
  26. }

  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来执行:

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

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

  1. void dvmUseJNIBridge(Method* method, void* func)
  2. {
  3. DalvikBridgeFunc bridge = shouldTrace(method)
  4. ? dvmTraceCallJNIMethod
  5. : dvmSelectJNIBridge(method);
  6. dvmSetNativeFunc(method, bridge, func);
  7. }
  1. 这里有个bridge的东东,我们这里先不看后面会提及(详情看老罗的文章吧)。直接看dvmSetNativeFunc
  1. void dvmSetNativeFunc(Method* method, DalvikBridgeFunc func,
  2. const u2* insns)
  3. {
  4. ......
  5.   // 参数func = bridge
       // 参数 insns = func(dvmSetNativeFunc(method, bridge, func)); 即func = (void*)shy_luo_jni_ClassWithJni_nanosleep
  1. if (insns != NULL) {
  2. /* update both, ensuring that "insns" is observed first */
  3. method->insns = insns;
  4. android_atomic_release_store((int32_t) func,
  5. (void*) &method->nativeFunc);
  6. } else {
  7. /* only update nativeFunc */
  8. method->nativeFunc = func;
  9. }
  10.  
  11. ......
  12. }
  1.  dvmSetNativeFunc函数,既然是把bridge赋值给method->nativeFuncshy_luo_jni_ClassWithJni_nanosleep赋值给method->insns,那什么时候才会执行到shy_luo_jni_ClassWithJni_nanosleep啊(在dalvik中,若methodnative则会执行method->nativeFunc)!带着这个疑问,我们回头看dvmSelectJNIBridge:
  1. /*
  2. * Returns the appropriate JNI bridge for 'method', also taking into account
  3. * the -Xcheck:jni setting.
  4. */
  5. static DalvikBridgeFunc dvmSelectJNIBridge(const Method* method)
  6. {
  7. enum {
  8. kJNIGeneral = 0,
  9. kJNISync = 1,
  10. kJNIVirtualNoRef = 2,
  11. kJNIStaticNoRef = 3,
  12. } kind;
  13. static const DalvikBridgeFunc stdFunc[] = {
  14. dvmCallJNIMethod_general,
  15. dvmCallJNIMethod_synchronized,
  16. dvmCallJNIMethod_virtualNoRef,
  17. dvmCallJNIMethod_staticNoRef
  18. };
  19. static const DalvikBridgeFunc checkFunc[] = {
  20. dvmCheckCallJNIMethod_general,
  21. dvmCheckCallJNIMethod_synchronized,
  22. dvmCheckCallJNIMethod_virtualNoRef,
  23. dvmCheckCallJNIMethod_staticNoRef
  24. };
  25.  
  26. bool hasRefArg = false;
  27.  
  28. if (dvmIsSynchronizedMethod(method)) {
  29. /* use version with synchronization; calls into general handler */
  30. kind = kJNISync;
  31.   .....if (hasRefArg) {
  32. /* use general handler to slurp up reference args */
  33. kind = kJNIGeneral;
  34. } else {
  35. /* virtual methods have a ref in args[0] (not in signature) */
  36. if (dvmIsStaticMethod(method))
  37. kind = kJNIStaticNoRef;
  38. else
  39. kind = kJNIVirtualNoRef;
  40. }
  41. }
  42.  
  43. return dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];
  44. }
  1. 直接看最后返回dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];假设返回stdFunc[kind]。看上面stdFunc定义,可知bridge其实是函数。我们再假定是最普通dvmCallJNIMethod_general,那么在dvmSetNativeFuncmethod->nativeFunc = dvmCallJNIMethod_generalok,那我们就看看dvmCallJNIMethod_general是在哪里执行我们的shy_luo_jni_ClassWithJni_nanosleep
  1. void dvmCallJNIMethod_general(const u4* args, JValue* pResult,
  2. const Method* method, Thread* self)
  3. ......
  4. dvmPlatformInvoke(env, staticMethodClass,
  5. method->jniArgInfo, method->insSize, modArgs, method->shorty,
  6. (void*)method->insns, pResult);
  7. ......
  8. }

  接着看dvmPlatformInvoke:  

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

  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之旅

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

  1. InnoDB的锁机制浅析(二)—探索InnoDB中的锁(Record锁/Gap锁/Next-key锁/插入意向锁)

    Record锁/Gap锁/Next-key锁/插入意向锁 文章总共分为五个部分: InnoDB的锁机制浅析(一)-基本概念/兼容矩阵 InnoDB的锁机制浅析(二)-探索InnoDB中的锁(Recor ...

  2. EM算法浅析(二)-算法初探

    EM算法浅析,我准备写一个系列的文章: EM算法浅析(一)-问题引出 EM算法浅析(二)-算法初探 一.EM算法简介 在EM算法之一--问题引出中我们介绍了硬币的问题,给出了模型的目标函数,提到了这种 ...

  3. dalvik浅析三:类加载

    android的安装包是个apk文件,其中包含dex.资源及签名文件.其中dex是包含程序运行的类代码,而android是运行在dalvik(5.0之前)上的.本篇我们就来看下dalvik是如何把de ...

  4. Java Native Interface 二 JNI中对Java基本类型和引用类型的处理

    本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 Java编程里会使用到两种类型:基本类型(如 ...

  5. ReentrantLock和condition源码浅析(二)

    转载请注明出处... 接着上一篇的ReentrantLock和condition源码浅析(一),这篇围绕着condition 一.condition的介绍 在这里为了作对比,引入Object类的两个方 ...

  6. 以太网驱动的流程浅析(二)-Ifconfig的详细代码流程【原创】

    以太网驱动流程浅析(二)-ifconfig的详细代码流程 Author:张昺华 Email:920052390@qq.com Time:2019年3月23日星期六 此文也在我的个人公众号以及<L ...

  7. IOS RunLoop浅析 二

    上一篇我们说了runloop 的几种模式,那么我们在模式中又要做些什么呢??? 模式中有三个模块: 事件源(输入源) Source Source: 按照官方文档分类 Port-Based Custom ...

  8. JDK8 BigDecimal API-创建BigDecimal源码浅析二

    第二篇,慢慢来 根据指数调整有效小数位数 // 上一篇由字符串创建BigDecimal代码中,有部分代码没有给出,这次补上 // 这个是当解析字符数组时存在有效指数时调整有小小数位数方法 privat ...

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

    创建.a静态库 第一步,新建工程.     一般使用工程名就使用库的名称,比如我这里用FMDB来创建静态库,我的工程名就取名为FMDB,创建的.a静态库就是libFMDB.a.             ...

随机推荐

  1. dubbo实战之一:准备和初体验

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. 通过kubeadm快速部署K8S集群

    kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具. 这个工具能通过两条指令完成一个kubernetes集群的部署: # 创建一个 Master 节点 $ kubeadm i ...

  3. C# webapi跨域

    C# webapi跨域   第一种在Web.config中<system.webServer>节点中配置(不支持多个域名跨域) 1 <httpProtocol> 2 <c ...

  4. 每日一题20201112(922. 按奇偶排序数组 II)

    题目链接: 922. 按奇偶排序数组 II 思路 很简单,搞懂问题的核心就行,假设现在有奇数在偶数位上,偶数在奇数位上. 那么我们要做的就是,找到分别在对方位置上的数字,然后交换他们就行. class ...

  5. springboot源码解析-管中窥豹系列之BeanFactoryPostProcessor(十一)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  6. 用实战玩转pandas数据分析(一)——用户消费行为分析(python)

      CD商品订单数据的分析总结.根据订单数据(用户的消费记录),从时间维度和用户维度,分析该网站用户的消费行为.通过此案例,总结订单数据的一些共性,能通过用户的消费记录挖掘出对业务有用的信息.对其他产 ...

  7. [笔记] 扩展Lucas定理

    [笔记] 扩展\(Lucas\)定理 \(Lucas\)定理:\(\binom{n}{m} \equiv \binom{n/P}{m/P} \binom{n \% P}{m \% P}\pmod{P} ...

  8. E - Recursive sequence HDU - 5950 (矩阵快速幂)

    题目链接:https://vjudge.net/problem/HDU-5950 思路: 构造矩阵,然后利用矩阵快速幂. 1 #include <bits/stdc++.h> 2 #inc ...

  9. RepVGG

    RepVGG: Making VGG-style ConvNets Great Again 作者:elfin   资料来源:RepVGG论文解析 目录 1.摘要 2.背景介绍 3.相关工作 3.1 单 ...

  10. JS复制文本到粘贴板,前端H5移动端点击按钮复制文本

    <span id="codeNum">FTYHDSDW</span> <span class=" code-btn" id=&qu ...