0x00 Dalvik虚拟机是如何执行程序Dex的?
解析完dex结构之后,我就比较好奇dalvik虚拟机是如何加载并执行dex的?
davlik是基于寄存器的虚拟机,其从源代码到可执行文件中的与java编译有所不同,多了一步使用dx工具将class文件压缩成Dalvik字节码
对比jar和apk文件格式的区别:
0x01 dalvik相对于java虚拟机的优点:
代码密度小,运行效率高,节省资源。
常量池只使用32位的索引
有内存限制
默认栈大小是12KB(3个页,每页4KB)
堆默认启动大小为2MB,默认最大值为16MB
堆支持的最小启动大小为1MB,支持的最大值为1024MB
堆和栈参数可以通过-Xms和-Xmx修改
0x02 dalvik如何加载并执行dex
通过mmap函数将类加载到内存,然后通过读写操作访问dex,然后解析dex文件内容并加载其中的类到哈希表中。并通过dexFileParse函数对其进行分析
映射dex到内存--》加载class--》Dalvik解释器解释执行
2.1 映射dex到内存
总的来说,dex文件可以抽象为三个部分:头部、索引、数据。通过头部可以知道索引的位置和数目,以及数据区的起始位置。将dex文件映射到内存后,Dalvik会调用dexFileParse函数对其进行分析,分析的结果放到DexFile数据结构中。DexFile中的baseAddr指向映射区的起始位置,pClassDefs指向class索引的起始位置。为了加快class的查找速度,还创建一个哈希表,对class名字进行哈希并生成索引。
2.2加载dex
在对文件解析完成后就要加载Class的具体内容了!在Dalvik中,由ClassObject 这个数据结构负责存放加载的信息。还包含一个Lock对象。如果其它线程想要获取它的锁,只有等这个线程释放。
typedef struct Object {
ClassObject* clazz; // ClassObject类型对象
Lock lock; // 锁对象
} Object;
如下图所示,加载过程会在内存中alloc几个区域,分别存放directMethods, virtualMethods, sfields, ifields。这些信息正是从dex 文件的数据区中读取。
struct Field {
ClassObject* clazz; //所属类型
const char* name; // 变量名称
const char* signature; // 如“Landroid/os/Debug;”
u4 accessFlags; // 访问标记
#ifdef PROFILE_FIELD_ACCESS
u4 gets;
u4 puts;
#endif
};
首先会读取Class的详细信息,从中获知directMethod, virtualMethod, sfield, ifield等的信息,然后再读取。下图为加载完成后的示意。 这里并未介绍加载的每个细节,感兴趣的同学可通过此二图自行分析。
还请大家注意的是在ClassObject结构中有个名为super的成员。通过super成员来指向它的超类。
而dex加载整体流程如下:
待得到class索引后,实际的加载由loadClassFromDex来完成。首先它会读取class的具体数据,分别加载directMethod, virtualMethod, ifield和sfield,然后为ClassObject数据结构分配内存,并读取dex文件的相关信息。加载完成后,将加载的class通过dvmAddClassToHash函数放入哈希表,以方便下次查找;最后,通过dvmLinkClass查找该类的超类,如果有接口类则加载相应的接口类。
2.3 dalvik解释器分析
1.dalvik解释器解释指令前的准备工作
2.dalvik解释器的模型
3.invoke-super指令实例分析
dalvik解释器解释指令前的准备工作
从外部进入解释器的调用链如下:
dvmCallMethod -> dvmCallMethodV -> dvmInterpret
这三个函数是在解释器取指令,选分支之前被调用,主要负责一些准备工作,包括分配虚拟寄存器,放入参数,初始化解释器参数等。其中dvmCallMethod,直接调用了dvmCallMethodV.下面分析下后两个函数。
dvmCallMethodV
dalvik虚拟机是基于寄存器架构的,可想而知,在具体执行函数之前,首先要做的就是分配好虚拟寄存器空间,并且将函数所需的参数,放入虚拟寄存器中。主要流程:
1.取出函数的简单声明,如onCreate函数的简单声明为:VL
2.分配虚拟寄存器栈
3.放入this参数,根据参数类型放入申明中的参数
4.如果方法是native方法,直接跳转到method->nativeFunc执行
5.如果方法是java方法,进入dvmInterpret解释执行
void dvmCallMethodV(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, va_list args)
{
//取出方法的简要声明
const char* desc = &(method->shorty[1]); // [0] is the return type.
int verifyCount = 0;
ClassObject* clazz;
u4* ins;
//访问权限检查,以及分配函数调用栈,在栈中维护了一份虚拟寄存器列表。
clazz = callPrep(self, method, obj, false);
if (clazz == NULL)
return;
/* "ins" for new frame start at frame pointer plus locals */
//指向第一个参数
ins = ((u4*)self->interpSave.curFrame) + (method->registersSize - method->insSize);
//放入this指针,到第一个参数。
/* put "this" pointer into in0 if appropriate */
if (!dvmIsStaticMethod(method)) {
*ins++ = (u4) obj;
verifyCount++;
}
//根据后续参数的类型,放入后续参数
while (*desc != '\0') {
switch (*(desc++)) {
case 'D': case 'J': {
u8 val = va_arg(args, u8);
memcpy(ins, &val, 8); // EABI prevents direct store
ins += 2;
verifyCount += 2;
break;
}
case 'F': {
/* floats were normalized to doubles; convert back */
float f = (float) va_arg(args, double);
*ins++ = dvmFloatToU4(f);
verifyCount++;
break;
}
case 'L': { /* 'shorty' descr uses L for all refs, incl array */
void* arg = va_arg(args, void*);
assert(obj == NULL || dvmIsHeapAddress(obj));
jobject argObj = reinterpret_cast<jobject>(arg);
if (fromJni)
*ins++ = (u4) dvmDecodeIndirectRef(self, argObj);
else
*ins++ = (u4) argObj;
verifyCount++;
break;
}
default: {
/* Z B C S I -- all passed as 32-bit integers */
*ins++ = va_arg(args, u4);
verifyCount++;
break;
}
}
}
//如果是本地方法,就直接跳转到本地方法,若是java方法,进入解释器,解释执行。
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);//解释器的入口
}
dvmPopFrame(self);
}
dvmInterpret
dvmInterpret作为虚拟机的入口,主要做了如下工作:
1.初始化解释器的执行环境。主要是对解释器的变量进行初始化,如将要执行方法的指针,当前函数栈的指针,程序计数器等。
2.判断将要执行的方法是否合法(是否初始化或者error)
3.JIT环境的设置
4.根据系统参数选择解释器(Fast解释器或者Portable解释器)
void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
{
//解释器的状态
InterpSaveState interpSaveState;
ExecutionSubModes savedSubModes;
#if defined(WITH_JIT)
double calleeSave[JIT_CALLEE_SAVE_DOUBLE_COUNT];
#endif
//保存之前的解释器状态,并将新的状态和之前的状态连接起来(链表)
interpSaveState = self->interpSave;
self->interpSave.prev = &interpSaveState;
/*
* Strip out and save any flags that should not be inherited by
* nested interpreter activation.
*/
savedSubModes = (ExecutionSubModes)(
self->interpBreak.ctl.subMode & LOCAL_SUBMODE);
if (savedSubModes != kSubModeNormal) {
dvmDisableSubMode(self, savedSubModes);
}
#if defined(WITH_JIT)
dvmJitCalleeSave(calleeSave);
#endif
#if defined(WITH_TRACKREF_CHECKS)
self->interpSave.debugTrackedRefStart =
dvmReferenceTableEntries(&self->internalLocalRefTable);
#endif
self->debugIsMethodEntry = true;
#if defined(WITH_JIT)
/* Initialize the state to kJitNot */
self->jitState = kJitNot;
#endif
/初始化解释器的执行环境
self->interpSave.method = method; //初始化执行的方法
self->interpSave.curFrame = (u4*) self->interpSave.curFrame; //初始化函数调用栈
self->interpSave.pc = method->insns; //初始化程序计数器
//检查方法是否为本地方法
assert(!dvmIsNativeMethod(method));
//方法的类是否初始化
if (method->clazz->status < CLASS_INITIALIZING || method->clazz->status == CLASS_ERROR)
{
ALOGE("ERROR: tried to execute code in unprepared class '%s' (%d)",
method->clazz->descriptor, method->clazz->status);
dvmDumpThread(self, false);
dvmAbort();
}
// 选择解释器
typedef void (*Interpreter)(Thread*);
Interpreter stdInterp;
if (gDvm.executionMode == kExecutionModeInterpFast)
stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
else if (gDvm.executionMode == kExecutionModeJit ||
gDvm.executionMode == kExecutionModeNcgO0 ||
gDvm.executionMode == kExecutionModeNcgO1)
stdInterp = dvmMterpStd;
#endif
else
stdInterp = dvmInterpretPortable;//设置为Portable解释器
// Call the interpreter
(*stdInterp)(self);
*pResult = self->interpSave.retval;
/* Restore interpreter state from previous activation */
self->interpSave = interpSaveState;
#if defined(WITH_JIT)
dvmJitCalleeRestore(calleeSave);
#endif
if (savedSubModes != kSubModeNormal) {
dvmEnableSubMode(self, savedSubModes);
}
}
dalvik解释器流程分析
dalvik解释器有两种:Fast解释器,Portable解释器。选择分析Portable解释器,因为Portable解释器的可读性更好。在分析前,先看下Portable解释器的模型。
Thread Code技术
实现解释器的一个常见思路如下代码,循环取指令,然后判断指令类型,去相应分支执行,执行完成后,再返回到switch执行下条指令。
while (*ins) {
switch (*ins) {
case NOP:
break;
case MOV:
break;
......
}
}
但是当每次执行一条指令,都需要重新判断下条指令类型,然后选择switch分支,这是个昂贵的开销。Dalvik为了解决这个问题,引入了Thread Code技术。简单的说就是在执行函数之前,建立一个分发表GOTO_TABLE,每条指令在表中有一个对应条目,条目里存放的就是处理该条指令的handler地址。比如invoke-super指令,它的opcode为6f,那么处理该条指令的handler地址就是:GOTO_TABLE[6f].那么在每条指令的解释程序末尾,都可以加上取指动作,然后goto到下条指令的handler。
dvmInterpretPortable源码分析
dvmInterpretPortable是Portable型虚拟机的具体实现,流程如下
1.初始化一些关于虚拟机执行环境的变量
2.初始化分发表
3.FINISH(0)开始执行指令
void dvmInterpretPortable(Thread* self)
{
DvmDex* methodClassDex; // curMethod->clazz->pDvmDex
JValue retval;
//一些核心的状态
const Method* curMethod; // 要执行的方法
const u2* pc; // 指令计数器
u4* fp; // 函数栈指针
u2 inst; // 当前指令
/* instruction decoding */
u4 ref; // 用来表示类的引用
u2 vsrc1, vsrc2, vdst; // 寄存器索引
/* method call setup */
const Method* methodToCall;
bool methodCallRange;
//建立分发表
DEFINE_GOTO_TABLE(handlerTable);
//初始化上面定义的变量
curMethod = self->interpSave.method;
pc = self->interpSave.pc;
fp = self->interpSave.curFrame;
retval = self->interpSave.retval; /* only need for kInterpEntryReturn? */
methodClassDex = curMethod->clazz->pDvmDex;
if (self->interpBreak.ctl.subMode != 0) {
TRACE_METHOD_ENTER(self, curMethod);
self->debugIsMethodEntry = true; // Always true on startup
}
methodToCall = (const Method*) -1;
//取出第一条指令,并且执行
FINISH(0); /* fetch and execute first instruction */
//下面就是定义了每条指令的处理分支。
//NOP指令的处理程序:什么都不做,然后处理下条指令
HANDLE_OPCODE(OP_NOP)
FINISH(1);
OP_END
.....
invoke-super指令实例分析
invoke-super这条指令的handler如下:
#define GOTO_invoke(_target, _methodCallRange) \
do { \
methodCallRange = _methodCallRange; \
goto _target; \
} while(false)
HANDLE_OPCODE(OP_INVOKE_SUPER /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
GOTO_invoke(invokeSuper, false);
OP_END
invokeSuper这个标签定义如下:
//invoke-super位描述符如下:A|G|op BBBB F|E|D|C
//methodCallRange depending on whether this is a "/range" instruction.
GOTO_TARGET(invokeSuper, bool methodCallRange)
{
Method* baseMethod;
u2 thisReg;
EXPORT_PC();
//7010 0400 0000 opcode 对应的 A|G|OP BBBB CDEF
//取出AG的值
vsrc1 = INST_AA(inst);
//要调用的method索引
ref = FETCH(1);
//要作为参数的寄存器的索引
vdst = FETCH(2);
//取出this寄存器的索引,比如thisReg为3的话,表示第三个寄存器,放的是this参数。
if (methodCallRange) {
ILOGV("|invoke-super-range args=%d @0x%04x {regs=v%d-v%d}",
vsrc1, ref, vdst, vdst+vsrc1-1);
thisReg = vdst;
} else {
ILOGV("|invoke-super args=%d @0x%04x {regs=0x%04x %x}",
vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f);
thisReg = vdst & 0x0f;
}
//检查this 是否为空
if (!checkForNull((Object*) GET_REGISTER(thisReg)))
GOTO_exceptionThrown();
//解析要调用的方法
baseMethod = dvmDexGetResolvedMethod(methodClassDex, ref);
if (baseMethod == NULL) {
baseMethod = dvmResolveMethod(curMethod->clazz, ref,METHOD_VIRTUAL);
if (baseMethod == NULL) {
ILOGV("+ unknown method or access denied");
GOTO_exceptionThrown();
}
}
if (baseMethod->methodIndex >= curMethod->clazz->super->vtableCount) {
/*
* Method does not exist in the superclass. Could happen if
* superclass gets updated.
*/
dvmThrowNoSuchMethodError(baseMethod->name);
GOTO_exceptionThrown();
}
methodToCall = curMethod->clazz->super->vtable[baseMethod->methodIndex];
#if 0
if (dvmIsAbstractMethod(methodToCall)) {
dvmThrowAbstractMethodError("abstract method not implemented");
GOTO_exceptionThrown();
}
#else
assert(!dvmIsAbstractMethod(methodToCall) ||
methodToCall->nativeFunc != NULL);
#endif
LOGVV("+++ base=%s.%s super-virtual=%s.%s",
baseMethod->clazz->descriptor, baseMethod->name,
methodToCall->clazz->descriptor, methodToCall->name);
assert(methodToCall != NULL);
//调用方法
GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst);
}
GOTO_TARGET_END
解析完要调用的方法后,跳转到invokeMethod结构来执行函数调用,invokeMethod为要调用的函数创建虚拟寄存器栈,新的寄存器栈和之前的栈是由重叠的。然后重新设置解释器执行环境的参数,调用FINISH(0)执行函数
GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall, u2 count, u2 regs)
{
//节选
if (!dvmIsNativeMethod(methodToCall)) {
/*
* "Call" interpreted code. Reposition the PC, update the
* frame pointer and other local state, and continue.
*/
curMethod = methodToCall; //设置要调用的方法
self->interpSave.method = curMethod;
methodClassDex = curMethod->clazz->pDvmDex;
pc = methodToCall->insns; //重置pc到要调用的方法
fp = newFp;
self->interpSave.curFrame = fp;
#ifdef EASY_GDB
debugSaveArea = SAVEAREA_FROM_FP(newFp);
#endif
self->debugIsMethodEntry = true; // profiling, debugging
ILOGD("> pc <-- %s.%s %s", curMethod->clazz->descriptor,
curMethod->name, curMethod->shorty);
DUMP_REGS(curMethod, fp, true); // show input args
FINISH(0); // jump to method start
}
参考
http://blog.csdn.net/VirtualPower/article/details/5715277
https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system/DexFile.java
https://www.jianshu.com/p/14147171a599