之前曾经简单跟踪过代码,知道dalvik的字节码是可以支持解释执行的,所谓的解释执行,其实就是c/c++编写的用于解释并执行dalvik字节码的程序,说白了就是dalvik字节码到cpu字节码的转换。
之前的理解算是囫囵吞枣,最近有时间,好好跟了一遍dalvik的代码,算是弄明白了细节。
我们从dvmCallMethod
开始来跟一遍dalvik执行方法的过程:
void dvmCallMethod(Thread* self, const Method* method, Object* obj,
JValue* pResult, ...)
{
va_list args;
va_start(args, pResult);
dvmCallMethodV(self, method, obj, false, pResult, args);
va_end(args);
}
直接调用dvmCallMethodV
:
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);
//ALOGD(" FP is %p, INs live at >= %p", self->interpSave.curFrame, ins);
/* put "this" pointer into in0 if appropriate */
if (!dvmIsStaticMethod(method)) {
#ifdef WITH_EXTRA_OBJECT_VALIDATION
assert(obj != NULL && dvmIsHeapAddress(obj));
#endif
*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;
}
}
}
#ifndef NDEBUG
if (verifyCount != method->insSize) {
ALOGE("Got vfycount=%d insSize=%d for %s.%s", verifyCount,
method->insSize, clazz->descriptor, method->name);
assert(false);
goto bail;
}
#endif
//dvmDumpThreadStack(dvmThreadSelf());
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);
}
#ifndef NDEBUG
bail:
#endif
dvmPopFrame(self);
}
解释一下这个函数:
- 先分配了栈帧,通过
callPrep
- 参数入栈
- 执行方法 通过
dvmInterpret
(暂时只分析普通的method,不考虑jni method) - 弹出栈帧
看一下栈帧分配:
static ClassObject* callPrep(Thread* self, const Method* method, Object* obj,
bool checkAccess)
{
ClassObject* clazz;
#ifndef NDEBUG
if (self->status != THREAD_RUNNING) {
ALOGW("threadid=%d: status=%d on call to %s.%s -",
self->threadId, self->status,
method->clazz->descriptor, method->name);
}
#endif
assert(self != NULL);
assert(method != NULL);
if (obj != NULL)
clazz = obj->clazz;
else
clazz = method->clazz;
IF_LOGVV() {
char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
LOGVV("thread=%d native code calling %s.%s %s", self->threadId,
clazz->descriptor, method->name, desc);
free(desc);
}
if (checkAccess) {
/* needed for java.lang.reflect.Method.invoke */
if (!dvmCheckMethodAccess(dvmGetCaller2Class(self->interpSave.curFrame),
method))
{
/* note this throws IAException, not IAError */
dvmThrowIllegalAccessException("access to method denied");
return NULL;
}
}
/*
* Push a call frame on. If there isn't enough room for ins, locals,
* outs, and the saved state, it will throw an exception.
*
* This updates self->interpSave.curFrame.
*/
if (dvmIsNativeMethod(method)) {
/* native code calling native code the hard way */
if (!dvmPushJNIFrame(self, method)) {
assert(dvmCheckException(self));
return NULL;
}
} else {
/* native code calling interpreted code */
if (!dvmPushInterpFrame(self, method)) {
assert(dvmCheckException(self));
return NULL;
}
}
return clazz;
}
核心是dvmPushInterpFrame
:
static bool dvmPushInterpFrame(Thread* self, const Method* method)
{
StackSaveArea* saveBlock;
StackSaveArea* breakSaveBlock;
int stackReq;
u1* stackPtr;
assert(!dvmIsNativeMethod(method));
assert(!dvmIsAbstractMethod(method));
stackReq = method->registersSize * 4 // params + locals
+ sizeof(StackSaveArea) * 2 // break frame + regular frame
+ method->outsSize * 4; // args to other methods
if (self->interpSave.curFrame != NULL)
stackPtr = (u1*) SAVEAREA_FROM_FP(self->interpSave.curFrame);
else
stackPtr = self->interpStackStart;
if (stackPtr - stackReq < self->interpStackEnd) {
/* not enough space */
ALOGW("Stack overflow on call to interp "
"(req=%d top=%p cur=%p size=%d %s.%s)",
stackReq, self->interpStackStart, self->interpSave.curFrame,
self->interpStackSize, method->clazz->descriptor, method->name);
dvmHandleStackOverflow(self, method);
assert(dvmCheckException(self));
return false;
}
/*
* Shift the stack pointer down, leaving space for the function's
* args/registers and save area.
*/
stackPtr -= sizeof(StackSaveArea);
breakSaveBlock = (StackSaveArea*)stackPtr;
stackPtr -= method->registersSize * 4 + sizeof(StackSaveArea);
saveBlock = (StackSaveArea*) stackPtr;
#if !defined(NDEBUG) && !defined(PAD_SAVE_AREA)
/* debug -- memset the new stack, unless we want valgrind's help */
memset(stackPtr - (method->outsSize*4), 0xaf, stackReq);
#endif
#ifdef EASY_GDB
breakSaveBlock->prevSave =
(StackSaveArea*)FP_FROM_SAVEAREA(self->interpSave.curFrame);
saveBlock->prevSave = breakSaveBlock;
#endif
breakSaveBlock->prevFrame = self->interpSave.curFrame;
breakSaveBlock->savedPc = NULL; // not required
breakSaveBlock->xtra.localRefCookie = 0; // not required
breakSaveBlock->method = NULL;
saveBlock->prevFrame = FP_FROM_SAVEAREA(breakSaveBlock);
saveBlock->savedPc = NULL; // not required
saveBlock->xtra.currentPc = NULL; // not required?
saveBlock->method = method;
LOGVV("PUSH frame: old=%p new=%p (size=%d)",
self->interpSave.curFrame, FP_FROM_SAVEAREA(saveBlock),
(u1*)self->interpSave.curFrame - (u1*)FP_FROM_SAVEAREA(saveBlock));
self->interpSave.curFrame = FP_FROM_SAVEAREA(saveBlock);
return true;
}
这段代码比较长,我分析了一下,绘制了一张图帮助理解:
即分配一个栈帧时,会从栈顶方向向下依次分配breakSaveBlock、registers(其中又依次是params和本地变量)、saveBlock,然后将
curFrame
指向saveBlock的高边缘地址。同时还建立一些prevFrame的索引关系,参见图。
当栈帧分配后,回到dvmCallMethodV
,依次将参数压入刚刚分配的栈帧的registers中的params处,参数序号和地址依次升高。
当压入参数后,调用dvmInterpret
开启对方法的解释执行,执行完后结果通过pResult
获取,最后调用dvmPopFrame
弹出之前分配的栈帧。
从dvmInterpret
开始就是重点了,通过跟一遍代码,我们可以知道字节码的格式,以及dalvik的解释执行过程。
内容有点多,分几篇来写。
网友评论