-
Notifications
You must be signed in to change notification settings - Fork 0
Description
本文即描述dalvik虚拟机是如何解释执行dalvik字节码的。
以AndroidRuntime中的start()中env->CallStaticVoidMethod(startClass, startMeth, strArray);进入ZygoteInit.main()为起点分析。 😆
函数调用如下4步,最终进入dvmCallMethodV
following:
1.AndroidRuntime.start():env->CallStaticVoidMethod()
2./dalvik/vm/Jni.cpp#3157: 全局变量,函数跳转表gNativeInterfaceCallStaticVoidMethod()
3./dalvik/vm/Jni.cpp#2050: 宏定义#define CALL_STATIC
4./dalvik/vm/interp/Stack.cpp#dvmCallMethodV
dvmCallMethodV分析
Dalvik Stack Frame结构
High address low address
BreakFrame register*4 RegularFrame
用于异常处理
dvmCallMethodV执行流程
1.
首先需要压栈
if (dvmIsNativeMethod(method)) {
/* native code calling native code the hard way */
if (!dvmPushJNIFrame(self, method)) {
return NULL;
}
} else {
/* native code calling interpreted code */
if (!dvmPushInterpFrame(self, method)) { //还需要压入method的参数
return NULL;
}
}2.
然后需要
if (dvmIsNativeMethod(method)) {
(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
method, self);
} else {
dvmInterpret(self, method, pResult);
}如果是native method, 直接调用DalvikBridgeFunc nativeFunc;
typedef void (*DalvikBridgeFunc)(const u4* args, JValue* pResult,
const Method* method, struct Thread* self);
// 传入给args的就是 Stack Frame的当前指针,当前指针指向的就是 regular frame。如果是java方法, 调用解释器。
3.
dvmInterpret()分析
1.设置thread状态,将解释器的pc指向method->insns即dalvik指令。
self->interpSave.method = method;
self->interpSave.curFrame = (u4*) self->interpSave.curFrame;
self->interpSave.pc = method->insns;2.判断解析器模式。
(1)Fast模式: kExecutionModeInterpFast
(2)JIT
以上会执行dvmMterpStd
(3)Portable模式
会执行dvmInterpretPortable;
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;
// Call the interpreter
(*stdInterp)(self);3.分析Portable模式。 Portable模式的入口在dvmInterpretPortable
(1)
/* copy state in */
curMethod = self->interpSave.method;//curMethod是当前正在解析的方法
pc = self->interpSave.pc; //程序计数器
fp = self->interpSave.curFrame; //Stack Frame
retval = self->interpSave.retval; //返回值(2)然后是一大段代码,对每一条指令分别处理。
跟老罗的博客不同,在android 4.4中,并没有出现switch语句,因为portable解析器采用了gcc的Threaded code (Lable as vlue)技术,增加了switch的效率。也因为这个技术,解析器需要去维护一个静态数组,里面存储指令的跳转地址。
处理指令依靠了大量的宏定义。
处理逻辑如下:
1.初始化完成之后,依靠宏FINISH(0)获取第一条指令。
2.在FNISH(offset)中,
2.1先调用宏ADJUST_PC(_offset); 判断偏移地址是否溢出并且调整pc寄存器(即移动pc,指向当前需要解析的指令)。
2.2然后利用FETCH(0)获取当前pc寄存器指向的指令。
2.3最后跳到静态数组指令标签处去执行指令操作。
# define FINISH(_offset) { \
ADJUST_PC(_offset); \
inst = FETCH(0); \
if (self->interpBreak.ctl.subMode) { \
dvmCheckBefore(pc, fp, self); \
} \
goto *handlerTable[INST_INST(inst)]; \
}
#define INST_INST(_inst) ((_inst) & 0xff) //用于获取指令的索引号总结:
这里虽然没有while(1),但是通过FINISH()宏的方式,同样起到了无限循环的作用。跳出循环全靠FINIFH宏中的ADJUST_PC宏。
举例
比如在执行FiNISH(0),虚拟机读到的一条指令是mul-int/2addr v1,v2:
1.
DEFINE_GOTO_TABLE(handlerTable);
goto *handlerTable[INST_INST(inst)]; 跳到了上文所说的静态数组。
2.静态数组中有这样一个项:
H(OP_MUL_INT_2ADDR)根据宏定义
# define H(_op) &&op_##_op所以变成了 &&op_OP_MUL_INT_2ADDR,编译完成之后,这里存放的就是执行这条指令的那段代码的地址。
此时,执行流在这
/* File: c/OP_MUL_INT_2ADDR.cpp */
HANDLE_OP_X_INT_2ADDR(OP_MUL_INT_2ADDR, "mul", *, 0)
OP_END这里又是一个宏,展开为
# define HANDLE_OPCODE(_op) op_##_op:
#define HANDLE_OP_X_INT_2ADDR(_opcode, _opname, _op, _chkdiv) \
HANDLE_OPCODE(_opcode /*vA, vB*/) \
vdst = INST_A(inst); \
vsrc1 = INST_B(inst); \
ILOGV("|%s-int-2addr v%d,v%d", (_opname), vdst, vsrc1); \
if (_chkdiv != 0) { \
s4 firstVal, secondVal, result; \
firstVal = GET_REGISTER(vdst); \
secondVal = GET_REGISTER(vsrc1); \
if (secondVal == 0) { \
EXPORT_PC(); \
dvmThrowArithmeticException("divide by zero"); \
GOTO_exceptionThrown(); \
} \
if ((u4)firstVal == 0x80000000 && secondVal == -1) { \
if (_chkdiv == 1) \
result = firstVal; /* division */ \
else \
result = 0; /* remainder */ \
} else { \
result = firstVal _op secondVal; \
} \
SET_REGISTER(vdst, result); \
} else { \
SET_REGISTER(vdst, \
(s4) GET_REGISTER(vdst) _op (s4) GET_REGISTER(vsrc1)); \
} \
FINISH(1);这里包含了处理四则运算的所有逻辑,会根据传入的flag=mul来判断执行的是乘法操作。
运行完毕,最后将SET_REGISTER(vdst, result)将结果存入寄存器。
以上。