Skip to content

Dalvik虚拟机执行过程——Portable解释器 #8

@NazcaLines

Description

@NazcaLines

本文即描述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

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)将结果存入寄存器。
以上。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions