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

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. 漏洞复现-CVE-2018-15473-ssh用户枚举漏洞

          0x00 实验环境 攻击机:Win 10 0x01 影响版本 OpenSSH 7.7前存在一个用户名枚举漏洞,通过该漏洞,攻击者可以判断某个用户名是否存在于目标主机 0x02 漏洞复现 针 ...

  2. 优化自动化测试流程,使用 flask 开发一个 toy jenkins工具

    1.自动化 某一天你入职了一家高大上的科技公司,开心的做着软件测试的工作,每天点点点,下班就走,晚上陪女朋友玩王者,生活很惬意. 但是美好时光一般不长,这种生活很快被女主管打破.为了提升公司测试效率, ...

  3. springboot注解之@Configuration 和 @Bean

    1.包结构 2.主程序类 1 /** 2 * 主程序类 3 * @SpringBootApplication:这是一个springboot应用 4 * 5 * @SpringBootApplicati ...

  4. 动态规划-最长公共上升子序列-n^2解法

    1. 题目描述 给定两个数列\(A, B\),如果他们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列.求\(A\)和\(B\)的最长公共上升子序列. 输入格 ...

  5. P2261 [CQOI2007]余数求和 【整除分块】

    一.题面 P2261 [CQOI2007]余数求和 二.分析 参考文章:click here 对于整除分块,最重要的是弄清楚怎样求的分得的每个块的范围. 假设$ n = 10 ,k = 5 $ $$  ...

  6. Lombok 常用注解总结

    本文转载自知乎专栏 极乐科技.有所整理. 主要注解 @Data @Setter @Getter @Log4j @AllArgsConstructor @NoArgsConstructor @Equal ...

  7. 京东 vue3 组件库震撼升级,如约而至!

    京东零售开源项目 NutUI 是一套京东风格的轻量级移动端 Vue 组件库,是开发和服务于移动 Web 界面的企业级产品.经过长时间的开发与打磨,NutUI 3.0 终于和大家见面了!3.0 版本在技 ...

  8. Hadoop学习笔记—HDFS

    目录 搭建安装 三个核心组件 安装 配置环境变量 配置各上述三组件守护进程的相关属性 启停 监控和性能 Hadoop Rack Awareness yarn的NodeManagers监控 命令 hdf ...

  9. docker部署kafka集群

    利用docker可以很方便的在一台机子上搭建kafka集群并进行测试.为了简化配置流程,采用docker-compose进行进行搭建. kafka搭建过程如下: 编写docker-compose.ym ...

  10. CVE-2021-21402 Jellyfin任意文件读取

    CVE-2021-21402 Jellyfin任意文件读取 漏洞简介 jellyfin 是一个自由的软件媒体系统,用于控制和管理媒体和流媒体.它是 emby 和 plex 的替代品,它通过多个应用程序 ...