胡琪

为今天工作,为明天投资,为未来孵化一些东西

【native层方法替换热修复原理】之虚拟机运行时数据结构ClassObject/Class和Method/ArtMethod

通过前面热修复与动态加载专栏热修复系列文章的讲解我们知道了以下2点重要信息

  • 在安卓java层,描述dex文件的数据结构是DexFile这个类,PathClassLoader对dex文件的加载在java代码中最终都是委托DexFile调用loadDex完成的
  • 在native层,在dalvik模式下描述dex文件中一个类的数据结构是ClassObject这个类

那么这里就有了另外一个问题,在dalvik虚拟机运行时环境中类和方法是如何描述的呢?Art模式下又是如何描述的呢?

根据前面dex中类的加载流程和unexpected DEX异常的分析类加载过程我们已经知道在java层类的加载最终是通过DexFile的loadClass函数完成的。因此我们只需要顺着该函数分析其对应的native函数在dalvik模式和art模式下的实现即可。首先回顾下该函数在java层的调用链:

可以看到在java层类的调用经过层层中转最终是委托给了DexFile的一个native函数defineClassNative来完成的。所以我们只需要分析defineClassNative这个函数在dalvik模式和art模式下分别是如何实现的即可。首先分析下dalvik模式。

dalvik模式下运行时类和方法的描述

dex中类的加载流程和unexpected DEX异常中已经提到过在dalvik模式下native层加载一个类最终调用的是findClassNoInit函数,该函数返回的是一个ClassObject指针,所以类加载机制的最终目标就是为目标类生成一个ClassObject数据结构,并将其存储在运行时环境中,为解释器的执行提供相应类方法的字节码。所以在运行时类的描述就是ClassObject这个数据结构。而方法在运行时的描述就是ClassObject中定义的一个成员Method数据结构。

弄清楚这2个数据结构对后面我们学习dalvik虚拟机JNI模块实现原理,hook框架Xposed以及阿里的DexPosed AOP框架以及AndFix热修复框架有非常重要的帮助。

注意:虽然现在是安卓默认使用Art运行时,但是对于开发者而言,开发出的应用程序经过编译和打包之后,仍然是一个包含dex字节码的APK文件。也就是仍然适合dalvik虚拟机的那一套指令,只不过Art运行时在apk安装时会将应用的dex字节码翻译成本地机器码,执行时直接执行本地机器码而不再是解释执行。关于dalvik虚拟机和Art运行时区别建议参看:https://blog.csdn.net/luoshengyang/article/details/18006645。

ClassObject数据结构

该数据结构位于dalvk虚拟机源码目录dalvik\vm\oo\Object.h头文件中。完整定义如下:

ClassObject结构体内包含了大量的其他数据成员,这些成员基本上包含了目标类在运行期间所需要的全部的数据描述,比如类的描述descriptor,类的访问权限等标记accessFlags,直接方法directMethods等。所以说该数据结构是运行时内存中非常重要的一个数据结构。下面对其中比较重要的一些成员做一些说明,

  • const char*     descriptor; 类的描述符,来自于dex文件中的常量池,格式与在JNI开发中使用的类描述符是同一个概念
  • DvmDex*         pDvmDex; DvmDex类型指针。DvmDex数据结构也是一个描述Dex文件的数据结构,但是与前面提到的DexFile的不同在于在DexFile数据结构的基础上增加了一些附加信息用于加快Dex文件解析效果,换而言之就是该数据结构内部是包含一个DexFile数据结构成员的。
  •  ClassObject*    super; ClassObject类型的指针,指向当前类的父类在内存中对应的ClassObject实例,从而能够访问父类的成员,例如父类的方法和字段。
  • Method*         directMethods;Method类型指针,指向该类的直接方法区,从注释上可以看到所谓直接方法就是指静态/私有/构造方法(/* static, private, and <init> methods */)
  • Method*         virtualMethods; 也是Method类型指针,不过指向的时虚方法区。

该类还包括很多其他成员,其余的未做说明的可以看上面源码的注释进行了解。在上面提到的这些成员中我们重点讲解下Method这个数据结构,因为对于一个类而言最重要的就是他所提供的功能,而功能是通过函数以API的形式提供的。

当虚拟机完成类加载工作之后,解释器会对已经装载的代码进行检查,验证符合虚拟机规范格式之后,最终调用dvmInterpret函数初始化解释器斌开始解释执行字节码,dvmInterpret函数是解释器的入口函数。该函数定义如下:

可以看到该函数的第二个参数即是Mehtod类型的指针,所以Method对象是dalvik虚拟机解释器的重要输入。引用自《Android Dalvik虚拟机结构及机制剖析》

Method数据结构

Method数据结构同样位于dalvk虚拟机源码目录dalvik\vm\oo\Object.h头文件中,定义如下:

同样的来看下该数据结构中的一些重要成员:

  • ClassObject*    clazz;ClassObject类型指针,用来表示该方法属于哪个类实例。
  • u4              accessFlags;方法访问标记符,如果某个方法被多个访问标记修饰,如public static function(),则是多个访问标记符进行与运算之后的结果。
  • const char*     name;函数名称
  •  const u2*       insns; 从注释可以看到是实际的代码,也就是指向函数的指令部分的起始地址。
  • DalvikBridgeFunc nativeFunc; DalvikBridgeFunc类型的函数指针,从注释上可以看到是JNI本地方法指针,可以是一个真正的native方法或者是一个JNI桥函数(native method ptr; could be actual function or a JNI bridge

从这里可以得到2点重要线索:

  1. dalvik虚拟机对于一个非native方法进行解释执行时,起关键作用的就是方法的指令部分,也就是Method数据结构的insns成员指向的地址的内容。
  2. 对于native方法,Method数据结构有一个nativeFunc成员作为描述执行native方法时的入口,该成员是一个函数指针,定义如下:

    为何要定义为函数指针,是因为这样在随着程序运行时可以动态更改该指针的指向即可调用函数声明相同的不同方法。在后面我们深入分析dalvik虚拟机JNI模块的实现原理时可以看到对于不是通过动态注册定义的native方法该函数指针初始指向dvmResolveNativeMethod这个函数。

art模式下类和方法运行时的描述

前面提到过我们只需要分析defineClassNative这个函数在art下是如何实现的即可。该函数在art下的实现函数是DexFile_defineClassNative,该函数位于安卓源码art/runtime/native/dalvik_system_DexFile.cc文件.

从这段代码我们可以看到art模式下类的加载是通过ClassLinker这个类调用DefineClass来完成的,加载成功之后返回一个mirror::Class的指针,换而言之就是在art模式下描述一个类运行时的数据结构是mirror::Class,也就是这个类的作用相当于dalvik模式下的ClassObject。

mirror::Class

先看下该类包含哪些重要成员,因为art下的这个类的定义在不同安卓版本下改动较大(这也是为何阿里的AndFix在art模式下的替换逻辑要分不同版本实现的原因),我们选取一个分析即可。这里以安卓源码4.4.4_r1为例(该数据结构定义位于安卓源码的art\runtime\mirror\class.h中):

这里仅仅只是展示了mirror::Class这个类定义的成员变量,未展示该类定义的成员函数。按照ClassObject的分析方式,对该类中的重要成员做如下说明:

  • ObjectArray<ArtMethod>* direct_methods_;存储该类所有直接方法的数组,从这里其实也可以看到在art模式下描述方法的运行时的数据结构是ArtMethod
  • ObjectArray<ArtMethod>* virtual_methods_; 存储该类所有虚方法的数组
  • uint32_t access_flags_; 描述类访问标志的字段

所以接下来看下ArtMethod成员的定义(该数据结构定义位于安卓源码的art\runtime\mirror\art_method.h)

ArtMethod


该数据结构中的几个重要的成员及其意义如下:

  • Class* declaring_class_; 该ArtMethod所属的类,也就是该方法位于哪个类对象中
  • uint32_t access_flags_; ArtMethod代表的方法的访问标志符
  • const void* entry_point_from_compiled_code_; art模式下执行编译过后的本地机器时的实际指令地址,类似前面dalvik模式下Method的insns字段
  • EntryPointFromInterpreter* entry_point_from_interpreter_;EntryPointFromInterpreter类型的函数指针,前面说过Art模式和dalvik模式的一个很重要的区别就是art模式会把字节码先优化未本地机器码然后直接执行,但是art模式下也可以像dalvik模式下解释执行,该函数指针即是用来表面使用哪种方式执行
  • const void* native_method_;java层方法对应的本地方法,作用类似前面dalvik模式下Method的nativeFunc字段

总结

到这里基本上就把安卓framework层Dex文件,类文件以及方法的描述彻底弄清楚了,这里做个小结:

  • 在安卓framework的java层,描述dex文件的数据结构是DexFile这个类,PathClassLoader对dex文件的加载在java代码中最终都是委托DexFile调用loadDex完成的。
  • 在framework的native层,描述dex文件对应的数据结构是c++层DexFile类,描述dex文件中一个类的数据结构在dalvik模式下是ClassObject结构体,在art模式下是mirror::Class;描述方法的数据结构在dalvik模式下是Method结构体,Mehotd类是dalvik虚拟机解释执行时的重要输入,在art模式下是ArtMethod
  • Method结构体中包含了一个insns成员,当该方法为非native方法时,该insns成员指向了该方法实际的指令。同时也包含一个nativeFunc成员,当该方法为native方法时,会执行nativeFunc指向的函数的代码。
  • java层的方法在运行时的表示为Method(dalvik模式下)和ArtMethod(art模式下)

对于热修复而言:

java层的Dex加载以及类加载机制衍生出了基于MultiDex改造的热修复理论,代表作为QQ空间热修复,Tinker以及Nuwa。虽然这他们解决类预校验标志的方法不尽相同,但是他们热修复的理论部分原理是一样的。

native层JNI方法和native方法的执行原理衍生出了native层方法替换的热修复方案,代表做包括Xposed hook框架,Dexposed以及AndFix。

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注