资讯详情

Dalvik解释器源码到VMP分析

前言

学习这一块的主要目的是想知道vmp如何实现,如何与系统本身的虚拟机合作,所以简单地学习Dalvik对数字公司的解释器进行了比较和分析。笔记结构如下:

  1. dalvik解释器分析
    1. dalvik解释器解释指令前的准备工作
    2. dalvik解释器模型
    3. invoke-super指令实例分析
  2. 数字壳解释器分析
    1. 解释器解释指令前的准备工作
    2. 解释器模型
    3. invoke-super指令实例分析

dalvik解释器分析

dalvik解释器解释指令前的准备工作

从外部进入解释器的调用链如下: dvmCallMethod -> dvmCallMethodV -> dvmInterpret

这三个函数在解释器获取指令和选择分支之前被调用,主要负责一些准备工作,包括分配虚拟寄存器、放入参数、初始化解释器参数等。dvmCallMethod,直接调用了dvmCallMethodV.下面分析下两个函数。

dvmCallMethodV

dalvik虚拟机是基于寄存器架构的。可以想象,在具体执行函数之前,首先要做的是分配虚拟寄存器空间,并将函数所需的参数放入虚拟寄存器中。主要流程:

  1. 取出函数的简单声明,如onCreate函数的简单声明如下:VL
  2. 虚拟寄存器存器栈
  3. 放入this参数,根据参数类型放入申报中的参数
  4. 如果方法是native方法,直接跳转method->nativeFunc执行
  5. 如果方法是java方法,进入dvmInterpret解释执行

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

void dvmCallMethodV(Thread*self, const Method*method,Object*obj,boolfromJni, JValue*pResult, va_list args)

{

//简要说明取出方法

const char*desc=&(method->shorty[1]);//[0]isthereturntype.

intverifyCount=0;

ClassObject*clazz;

u4*ins;

//检查访问权限,分配函数调用栈,在栈中维护虚拟寄存器列表。

clazz=callPrep(self, method, obj, false);

if(clazz==NULL)

return;

/*"ins"fornew frame start at frame pointer pluslocals*/

//指向第一个参数

ins=((u4*)self->interpSave.curFrame) (method->registersSize-method->insSize);

//放入this指针,到第一个参数。

/*put"this"pointer into in0ifappropriate*/

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 = (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. 判断将要执行的方法是否合法
  3. JIT环境的设置
  4. 根据系统参数选择解释器(Fast解释器或者Portable解释器)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

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;

 

    // 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执行下条指令。

1

2

3

4

5

6

7

8

9

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)开始执行指令

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

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如下:

1

2

3

4

5

6

7

8

9

#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这个标签定义如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

//invoke-super位描述符如下:A|G|op BBBB F|E|D|C

//methodCallRange depending on whether this is "/range" instruction.

GOTO_TARGET(invokeSuper, bool methodCallRange)

    {

        Method* baseMethod;

        u2 thisReg;

 

        EXPORT_PC();

        //取出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)执行函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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

        }

数字壳解释器分析

数字壳解释执行前的准备工作

进入解释器的流程为onCreate->sub_D930->sub_3FE5C->sub_3FF5C。sub_3FF5C真正的解释器入口,sub_D930和sub_3FE5C负责执行前的准备工作。这部分准备工作和dalvik解释器的准备工作类似。

sub_D930

sub_D930分为两部分,调用sub_66BD4之前为第一部分,之后为第二部分。这两部分主要做的事情如下:

  • 第一部分
    1. jni的一些初始化工作,FindClass,GetMethodID之类的工作
    2. 利用java.lang.Thread.getStackTrace获取到调用当前方法的类的类名以及函数名
  • 第二部分
    1. 调用sub_66BD4获取一些全局信息,以及待解释函数的信息
    2. 构建解释器的虚拟寄存器栈
    3. 解析待解释函数的简单声明,将函数参数放入虚拟寄存器

主要分析第二部分,首先引入一些数据结构,这类数据结构是动态分析出来的,有些字段的含义还不清楚标记为unkonw。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

struct GlobalInfo {

    DexInfo *dex_info;

    vector<MethodInfo*> method_list;

};

 

struct DexInfo {

    void *dexBase; //指向内存中dex的基址

    int unknow[12];

    void *dexHeader; //指向dex header,用来解析dex的。

    .....

    optable[];

};

 

struct MethodInfo {

    int dex_type_id;     //该方法在dex文件中DexTypeId列表的索引

    int dex_class_def_id;  // 该方法所在类在dex文件中DexClassDef列表索引

    int unknow;

    int codeoff;  //该方法的DexCode结构距离dex头的偏移。

};

 

struct StackInfo {

    JNIEnv * jni_env;

    int registerSize; //函数所需寄存器数量

    DexCode *dexCode;    //指向DexCode结构

    int unkonw;      

    void *registerSpace;   // 放入原始参数, malloc(4 * registerSize)

    void *registerSpace2;  // 新建一个object引用上面的参数, malloc(4 * registerSize)

    int unkonw2;       

    char key;             //解密指令的key 

    char origin_key;      //计算解密指令key所需的一个数据

};

sub_66BD4返回的是指向GlobalInfo结构的指针。这个全局信息里面包含了dex有关的信息和待解释函数的信息。有了这个信息就可以构建解释器所需的虚拟寄存器栈,完成准备工作。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

int __fastcall sub_D930(unsigned int a1, JNIEnv *a2, _DWORD *a3)

{

  //节选

  //获取全局信息

  global_info = sub_66BD4(v109, &v114);

  if ( v114 & 1 )

    j_j_j__ZdlPv(*(v47 + 5));

  if ( global_info && (v52 = *(global_info + 4), (*(global_info + 8- v52) >> 2 > v4) )

  {

    v53 = v52 + 4 * v4;

    a2c = v3;

    method_info = *v53;

    dexInfo = *global_info;

    DexCode = (**global_info + *(*v53 + 12));   // DexCode

    ((*v3)->PushLocalFrame)();

    //创建并初始化StackInfo结构

    stackinfo = malloc_1(0x20u);                // 创建StackInfo结构

    v56 = *DexCode;

    *stackinfo = a2c;                           // stackinfo->jni_env

    *(stackinfo + 4= v56;                     // stackinfo->registerSize

    *(stackinfo + 8= DexCode;                 // stackinfo->dexCode

    *(stackinfo + 12= 0;

    v57 = 4 * v56;

    v58 = malloc_0(4 * v56);                    // 创建虚拟寄存器栈

    *(stackinfo + 16= v58;

    memset(v58, v57, 0);

    v59 = malloc_0(v57);                        // 创建虚拟寄存器栈

    *(stackinfo + 20= v59;

    memset(v59, v57, 0);

    *(stackinfo + 29= 0;

    *(stackinfo + 28= 0;

    v60 = sub_3E268();

    v61 = DexCode;

    *(stackinfo + 28= *DexCode ^ *(v60 + 24) ^ *(method_info + 4) ^ *method_info;  // stackinfo->key

    *(stackinfo + 29= *(v60 + 24);

}

通过创建并初始化StackInfo结构,就完成了虚拟寄存器栈的创建,可以看到这里分配了两个虚拟寄存器栈。后面调试发现主要使用的是第二个虚拟寄存器栈。猜测这两个虚拟寄存器栈和dalvik拥有两个虚拟寄存器栈一样的原因一样,是一个用来执行native方法,一个执行java方法。

 

创建完虚拟寄存器栈的下一步工作就是放入函数参数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

标签: 1v69连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台