android虚拟机(一)
Dalvik虚拟机
Dalvik虚拟机的启动过程
https://blog.csdn.net/luoshengyang/article/details/8885792
Zygote进程在启动时会创建一个Dalvik实例,在它孵化app的进程时会复制一份到app的进程中,使每一个app进程都有一个独立的Dalvik虚拟机实例。
Dalvik虚拟机在Zygote进程中的启动过程,这个启动过程主要完成以下4个事情:
- 创建了一个Dalvik虚拟机实例;(这里只是对JavaVM结构的成员变量赋值,真正环境的创建在step.3)
- 为主线程的设置了一个JNI环境;
- 加载了Java核心类及其JNI方法;
- 注册了Android核心类的JNI方法。
AndroidRuntime类的start主要做了一下四件事:
- 调用startVm来创建一个Dalvik虚拟机实例,保存在成员变量mJavaVM。(JavaVM是Dalvik虚拟机在JNI中的表示)
- 调用成员函数startReg来注册一些Android核心类的JNI方法。
- 通过JNI接口,FindClass找到com.android.internal.os.ZygoteInit类,通过JNI接口,GetStaticMethodID找到com.android.internal.os.ZygoteInit类的静态成员函数main作为java层的入口。
- 在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主要完成以下四件事情。
- 为当前进程创建一个Dalvik虚拟机实例,即一个JavaVMExt对象。
- 为当前线程创建和初始化一个JNI环境,即一个JNIEnvExt对象,这是通过调用函数dvmCreateJNIEnv来完成的。
- 将参数vm_args所描述的Dalvik虚拟机启动选项拷贝到变量argv所描述的一个字符串数组中去,并且调用函数dvmStartup来初始化前面所创建的Dalvik虚拟机实例。
- 调用函数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 | struct JavaVMExt { |
调用dvmCreateJNIEnv来创建JNIEnv
- 创建一个JNIEnvExt对象,用来描述一个JNI环境,并且设置这个JNIEnvExt对象的宿主Dalvik虚拟机,以及所使用的本地接口表,即设置这个JNIEnvExt对象的成员变量funcTable和vm。这里的宿主Dalvik虚拟机即为当前进程的Dalvik虚拟机,它保存在全局变量gDvm的成员变量vmList中。本地接口表由全局变量gNativeInterface来描述。
- 参数self描述的是前面创建的JNIEnvExt对象要关联的线程,可以通过调用函数dvmSetJniEnvThreadId来将它们关联起来。注意,当参数self的值等于NULL的时候,就表示前面的JNIEnvExt对象是要与主线程关联的,但是要等到后面再关联,因为现在用来描述主线程的Thread对象还没有准备好。通过将一个JNIEnvExt对象的成员变量envThreadId和self的值分别设置为0x77777775和0x77777779来表示它还没有与线程关联。
- 在一个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()
获取到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中方法对应的encode_method数组的索引值),找到对应的OatMethodOffset,由begin_+code_offset(begin为内存中oatdata起始地址),得到method的本地指令地址。