android虚拟机(一)

android虚拟机(一)

Dalvik虚拟机

Dalvik虚拟机的启动过程

https://blog.csdn.net/luoshengyang/article/details/8885792

Zygote进程在启动时会创建一个Dalvik实例,在它孵化app的进程时会复制一份到app的进程中,使每一个app进程都有一个独立的Dalvik虚拟机实例。

Dalvik虚拟机在Zygote进程中的启动过程,这个启动过程主要完成以下4个事情:

  1. 创建了一个Dalvik虚拟机实例;(这里只是对JavaVM结构的成员变量赋值,真正环境的创建在step.3)
  2. 为主线程的设置了一个JNI环境;
  3. 加载了Java核心类及其JNI方法;
  4. 注册了Android核心类的JNI方法。

AndroidRuntime类的start主要做了一下四件事:

  1. 调用startVm来创建一个Dalvik虚拟机实例,保存在成员变量mJavaVM。(JavaVM是Dalvik虚拟机在JNI中的表示)
  2. 调用成员函数startReg来注册一些Android核心类的JNI方法。
  3. 通过JNI接口,FindClass找到com.android.internal.os.ZygoteInit类,通过JNI接口,GetStaticMethodID找到com.android.internal.os.ZygoteInit类的静态成员函数main作为java层的入口。
  4. 在Zygote进程退出时,会调用JavaVM的成员函数DetachCurrentThread来使Dalvik虚拟机实例和Zygote进程的主线程脱离,再调用DestroyJavaVM来销毁Dalvik虚拟机实例。

关于JavaVM与JVM的区别:在java里,每一个process可以产生多个java vm对象,但是在android上,每一个process只有一个Dalvik虚拟机对象,也就是在android进程中是通过有且只有一个虚拟器对象来服务所有java和c/c++代码。

startVM调用JNI_CreateJavaVM来创建

JNI_CreateJavaVM主要完成以下四件事情。

  1. 为当前进程创建一个Dalvik虚拟机实例,即一个JavaVMExt对象。
  2. 为当前线程创建和初始化一个JNI环境,即一个JNIEnvExt对象,这是通过调用函数dvmCreateJNIEnv来完成的。
  3. 将参数vm_args所描述的Dalvik虚拟机启动选项拷贝到变量argv所描述的一个字符串数组中去,并且调用函数dvmStartup来初始化前面所创建的Dalvik虚拟机实例。
  4. 调用函数dvmChangeStatus将当前线程的状态设置为正在执行NATIVE代码,并且将面所创建和初始化好的JavaVMExt对象和JNIEnvExt对象通过输出参数p_vm和p_env返回给调用者。

在Java层调用C层的本地函数时,调用c本地函数的线程必然通过Dalvik虚拟机来调用c层的本地函数,此时,Dalvik虚拟机会为本地的C组件实例化一个JNIEnv指针,该指针指向Dalvik虚拟机的具体的函数列表。
当JNI的c组件调用Java层的方法或者属性时,需要通过JNIEnv指针来进行调用。
当本地c/c++想获得当前线程所要使用的JNIEnv时,可以使用Dalvik虚拟机对象的JavaVM jvm->GetEnv()返回当前线程所在的JNIEnv
(可以联系在native中,动态注册函数的流程)

JavaVMExt对象和JNIEnv对象都有一个函数列表。

1
2
3
4
5
6
7
struct JavaVMExt {
const struct JNIInvokeInterface* funcTable; /* must be first */
...

struct JNIEnvExt {
const struct JNINativeInterface* funcTable;
...

调用dvmCreateJNIEnv来创建JNIEnv

  1. 创建一个JNIEnvExt对象,用来描述一个JNI环境,并且设置这个JNIEnvExt对象的宿主Dalvik虚拟机,以及所使用的本地接口表,即设置这个JNIEnvExt对象的成员变量funcTable和vm。这里的宿主Dalvik虚拟机即为当前进程的Dalvik虚拟机,它保存在全局变量gDvm的成员变量vmList中。本地接口表由全局变量gNativeInterface来描述。
    1. 参数self描述的是前面创建的JNIEnvExt对象要关联的线程,可以通过调用函数dvmSetJniEnvThreadId来将它们关联起来。注意,当参数self的值等于NULL的时候,就表示前面的JNIEnvExt对象是要与主线程关联的,但是要等到后面再关联,因为现在用来描述主线程的Thread对象还没有准备好。通过将一个JNIEnvExt对象的成员变量envThreadId和self的值分别设置为0x77777775和0x77777779来表示它还没有与线程关联。
    2. 在一个Dalvik虚拟机里面,可以运行多个线程。所有关联有JNI环境的线程都有一个对应的JNIEnvExt对象,这些JNIEnvExt对象相互连接在一起保存在用来描述其宿主Dalvik虚拟机的一个JavaVMExt对象的成员变量envList中。因此,前面创建的JNIEnvExt对象需要连接到其宿主Dalvik虚拟机的JavaVMExt链表中去。

创建JavaVM和JNIEnv过程大致相同,绑定一个函数列表,然后将该对象的指针保存起来。

调用dvmStartup初始化JavaVM

子模块的初始化

最后一步的初始化:dvmDebuggerStartup->dvmInitZygote

调用了系统调用setpgid来设置当前进程,即Zygote进程的进程组ID,两个参数均为0,这意味着Zygote进程的进程组ID与进程ID是相同的,也就是说,Zygote进程运行在一个单独的进程组里面。

Dalvik虚拟机的运行过程

http://blog.csdn.net/luoshengyang/article/details/8914953

JNIEnv的CallStaticVoidMethod调用回调函数表中的CallStaticVoidMethodV,来执行参数clazz和methodID所描述的Java代码。

CallStaticVoidMethodV->CallStatic##_jname##MethodV->dvmCallMethodV

函数dvmCallMethodV首先检查参数method描述的函数是否是一个JNI方法。如果是的话,那么它所指向的一个Method对象的成员变量nativeFunc就指向该JNI方法的地址,因此就可以直接对它进行调用。否则的话,就说明参数method描述的是一个Java函数,这时候就需要继续调用函数dvmInterpret来执行它的代码.

dvmCallMethodV中会根据执行模式来选择不同的解释器入口。

进入解释器后,对需要执行的java的类所相关部分进行初始化,之后是循环读取、解释指令码,直到return为止。

其中讲到Dalvik虚拟机解释器的可移植版本实现中,解释器对指令流的解释时,在获取到return指令时截止,是否可以在return指令后添加junk code。

ART虚拟机

https://blog.csdn.net/luoshengyang/article/details/39256813

ART虚拟机实例的创建和Dalvik大相径庭。
ART虚拟机在Zygote进程中创建并启动,再由Zygote来fork给应用程序进程

###ART加载OAT文件的过程

https://blog.csdn.net/luoshengyang/article/details/39307813

讲解一下OAT文件的格式,以及加载OAT的过程,包括涉及到的函数

oat文件本质上就是一个ELF文件

启动
Runtime create
Runtime init
  Android 5.0中将option的解析放在了init中
    1⃣️ ART启动
    2⃣️ interpret DEX
  ParsedOptions::Create
    解析启动参数
  Heap
    space::ImageSpace::Create
  JavaVMExt
  Thread::Attach
  GetHeap

运行
OatFile::Open
  1⃣️portable&&executable:OpenDlopen
  new OatFile
  Dlopen
    dlopne、dlsym -> get begin/end (得到内存中oatdata段的内存区域)
  2⃣️OpenElfFile
  ElfFileOpen
    FindDynamicSymbolAddress->get begin/end (得到内存中oatdata段的内存区域)
    Setup
      GetOatHeader
      GetOatHeader().GetKeyValueStoreSize()
      GetOatHeader().GetDexFileCount()

oatdata-c500

获取到Dex文件的过程:先获取到OAT文件中的oatdata段起始地址,找到oatheader,跃过header,找到oat_dex_file,由偏移dex_file_offset(与oatdata起始的偏移)定位到dexfile。

获取方法的过程:同样以上方式找到oat_dex_file,在dex_file_offset后是一个数组,元素是指向oat_class的指针,每个oat_class与dex中的class一一对应。
oat_class:

Size Name
Int16 status
Int16 Type
Int32 Bitsize(when type==1)
Byte * bitsize Bitmap[bitsize](when type==1)
Int32 2 n OatMethodOffset[n]

OatMethodOffset:

size name
int32 code_offest
int gc_map_offset

根据需要的method所在类的编号(在dex中class_def_item数组的索引值),找到对应oat_class,再根据method的编号(class_def_item中方法对应的encodemethod数组的索引值),找到对应的OatMethodOffset,由begin+code_offset(begin为内存中oatdata起始地址),得到method的本地指令地址。