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, bool fromJni, JValue* pResult, va_list args)

{

    //取出方法的简要声明

    const char* desc = &(method->shorty[1]); // [0is 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 = (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

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

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

{

    //节选

    v107 = stackinfo;

    proto = (*dexInfo

           + *(*dexInfo

             + *(dexInfo[4+ 60)

             + 4 * *(*dexInfo + *(dexInfo[4+ 76+ 12 * *(*dexInfo + *(dexInfo[4+ 92+ 8 * *(method_info + 4+ 2))));

    do

      v63 = proto++;                            // 方法的简单声明: VL

    while *v63 < 0 );

    v64 = j_j_strlen(proto);

    v99 = v64;

    v65 = *v61 - v61[1];                        // 函数内部使用寄存器个数:DexCode.registerSize - DexCode.insSize

    v105 = v65 - 1;

    v66 = v95 + 1;

    v67 = v107;

    if ( !*(method_info + 8) )

    {

      v105 = v65;

      v68 = 4 * v65;

      v69 = *(*(v107 + 20+ v68);              // 函数的第一个参数 thisReg

      v96 = *v95;

      //节选

      v111 = v68;

      v73 = (*(v67 + 20+ v68);

      v74 = *v73;

      if *v73 && !*(v74 + 4) )

      {

        *(v74 + 4= 1;

        v78 = v111;

        v77 = v96;

        **(*(v67 + 20+ v111) = v96;

      }

      else

      {

        v75 = v64;

        v76 = malloc_1(8u);                     // 重新创建了一个MainActivity object,并放入第二个register space位置。

        *v76 = 0;

        *(v76 + 4= 0;

        v77 = v96;

        *v76 = v96;

        *(v76 + 4= 1;

        *v73 = v76;

        v67 = v107;

        v64 = v75;

        v78 = v111;

      }

      *(*(v67 + 16+ v78) = v77;               // 直接将Activity实例,放入第一个register space的相应位置、

    }

    v108 = v67;

    if ( v64 >= 2 )                             // 处理除了this之外的其他参数。

    {

      v79 = 1;

      v112 = 1;

      do

      {

        v80 = v63[v79++ + 1];

        if ( v80 > 89 )

        {

          if ( v80 == 90 )

          {

            *(*(v67 + 16+ 4 * (v112++ + v105)) = *v66;

            ++v66;

          }

        }

        else

        {

          v81 = v80 - 66;

          if ( v81 <= 0x11 )

            JUMPOUT(__CS__, *(&off_E308 + v81) + 58120);// 调用jni->newGlobalRef,构造将传递给onCreate的参数

        }

      }

      while ( v79 < v64 );

    }

 

}

这里是从dex文件中提取出函数的简要声明,onCreate的简单声明为VL,然后根据声明,放入参数。首先放入this参数,然后根据后续参数的类型,将参数放入相应位置。和dalvik虚拟机流程类似。

sub_3FE5C

sub_D930完成了虚拟寄存器栈的构建并放入参数后,调用了sub_3FE5C。sub_3FE5C主要负责初始化解释器的一些状态,主要是InterpState结构。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

struct InterpState {

    void *pc;

    char key;

    DexInfo *dex_info;

};

 

LOAD:0003FF18                 BL              malloc_1                  ; 创建InterpState结构

LOAD:0003FF1C                 PUSH            {R4}

LOAD:0003FF1E                 POP             {R1}

LOAD:0003FF20                 PUSH            {R0}

LOAD:0003FF22                 POP             {R4}

LOAD:0003FF24                 STR             R4, [SP,#0x74+var_24]

LOAD:0003FF26                 LDRB            R0, [R1,#0x1C]

LOAD:0003FF28                 ADDS            R6, #0x10

LOAD:0003FF2A                 STR             R6, [SP,#0x74+var_2C]

LOAD:0003FF2C                 STR             R6, [R4]                  ; InterpState->pc

LOAD:0003FF2E                 STRB            R0, [R4,#4]               ; InterpState->key

LOAD:0003FF30                 STR             R5, [R4,#8]               ; InterpState->dex_info

数字壳解释器模型

sub_D930和sub_3FE5C完成了解释器的准备工作。sub_3FF5C负责解释执行。数字壳的解释器模型就是上面提到的那种最直观的模型,循环取指令,然后判断指令类型,去相应分支执行,执行完成后,再返回到switch执行下条指令。

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

//R4寄存器存放的是InterpState结构

LOAD:0003FF5C                 LDRB            R1, [R4,#4]   ; 取出key,InterpState->key

LOAD:0003FF5E                 MOVS            R6, #0x31 ; '1'

LOAD:0003FF60                 PUSH            {R1}

LOAD:0003FF62                 POP             {R2}

LOAD:0003FF64                 ANDS            R2, R6

LOAD:0003FF66                 LDR             R0, loc_402C8

LOAD:0003FF68                 PUSH            {R0}

LOAD:0003FF6A                 POP             {R3}

LOAD:0003FF6C                 BICS            R3, R1

LOAD:0003FF6E                 ORRS            R3, R2

LOAD:0003FF70                 MOVS            R2, #0xEF00

LOAD:0003FF74                 LSLS            R1, R1, #8

LOAD:0003FF76                 ANDS            R2, R1

LOAD:0003FF78                 BICS            R0, R1

LOAD:0003FF7A                 ORRS            R0, R2

LOAD:0003FF7C                 EORS            R0, R3

LOAD:0003FF7E                 LDR             R1, [R4]    ; 取出pc, Interpstate->pc

LOAD:0003FF80                 LDRH            R4, [R1]    ; 取出指令

 

//执行完invoke-super

LOAD:000463EC loc_463EC                             

LOAD:000463EC                                    

LOAD:000463EC                 LDR             R0, [R4]

LOAD:000463EE                 ADDS            R0, #6 

LOAD:000463F0                 STR             R0, [R4]  ; InterpState->pc = InterpState->pc + 6

LOAD:000463F2                 BL              loc_3FF5C ; 跳转到解释器开头,执行下一条指令。

数字壳invoke-super指令分析

invoke-super指令包含了函数调用的过程,可以看到dalvik解释器虚拟寄存器栈是比较复杂的,设计很多数据结构。目前为止数字壳的相关结构中,并未发现类似的结构。所以想分析下数字壳的解释器是如何处理函数调用的。调试分析后发现,数字壳的解释器其实并未实现真正的函数调用,它是通过调用jni中的CallVoidMethod方法来实现函数调用。

 

处理invoke-super的handler为sub_4878C,流程如下:

  1. 提取invoke-super指令中包含的MethodID,从dex中获取到DexMethodId结构,获取函数名。
  2. 从DexMethodId中获取ClassId
  3. 利用获取的ClassId可以获取到类名,利用jni->FindClass获取类
  4. 利用jni->GetMethod获取函数
  5. 准备函数参数
  6. 利用jni->CallVoidMehtod调用函数,实现invoke-method

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

int  sub_4878C(int a1, bool methodCallRange, JNIEnv *a3, DexInfo *a4, StackInfo *a5, InterpState *a6, int opNumber, void *a8)

{

  //节选

  a1a = v8;

  dex_base = *v8;

  dex_header = *(a1a + 16);

  key = *(a6 + 4);                              // 取出key

  v11 = (key << 8) | key;

  DexMethodId = dex_base + *(dex_header + 92+ 8 * (*(*a6 + 2) ^ v11);

  v13 = dex_base + *(dex_header + 60);

  methodName = (dex_base + *(v13 + 4 * *(DexMethodId + 4)));   //获取函数名

  do

    v15 = *methodName++ 0;

  while ( v15 );

  DexMethodId2 = (dex_base + *(dex_header + 92+ 8 * (*(*a6 + 2) ^ ((key << 8) | key)));

  proto = (dex_base

         + *(v13

           + 4 * *(dex_base + *(dex_header + 68+ 4 * *(dex_base + *(dex_header + 76+ 12 * *(DexMethodId + 2+ 4))));

  do

    v17 = *proto++;                        //获取函数简单声明

  while ( v17 < 0 );

  v18 = *(*a6 + 4);

  if ( v52 == 1 )

    thisReg = v18 ^ ((key << 8) | key);     //this参数

  else

    thisReg = (v18 ^ key) & 0xF;

  v20 = 0;

  if ( v51 )

  {

    v21 = *(*(a5 + 20+ 4 * thisReg);

    if ( !v21 || (v20 = *v21) == 0 )

    {

      v30 = ((*v53)->FindClass)(v53, "java/lang/NullPointerException");

      .....

    }

  }

  arg0 = v20;                                   // this object

  v62 = v53;

  v63 = 0;

  if ( ((*v53)->ExceptionCheck)(v53) )

    v63 = 0;

  if ( v51 != 2 && v51 != 4 )

  {

    sub_610BC(a1a, v53, *DexMethodId2);         // 获取到classid所指定的类

    return sub_48AE2(v32, v33, v34, v35, a5);   // 利用jni->CallVoidMethod调用父类方法

  }

 

 

//48EAA: 构建invoke-super除this外的参数

//49770:调用jni->CallVoidMethod方法

数字壳比较复杂,通过分析可以学到很多东西,比如各种反调试,linker,适配很多版本的动态加载,解释器等,感谢数字公司提供的免费加固。笔记如果有错误,欢迎指正,也希望大佬们可以交流下其他vmp的实现思路。

https://bbs.pediy.com/thread-226214.htm

Android虚拟机Dalvik完整源码,宝贵资源,欢迎下载! This directory contains the Dalvik virtual machine and core class library, as well as related tools, libraries, and tests. A note about the licenses and header comments --------------------------------------------- Much of the code under this directory originally came from the Apache Harmony project, and as such contains the standard Apache header comment. Some of the code was written originally for the Android project, and as such contains the standard Android header comment. Some files contain code from both projects. In these cases, the header comment is a combination of the other two, and the portions of the code from Harmony are identified as indicated in the comment. Here is the combined header comment: /* * Copyright (C) The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ---------- * * Portions of the code surrounded by "// BEGIN Harmony code" and * "// END Harmony code" are copyrighted and licensed separately, as * follows: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Native SH call bridge --------------------- Native SH call bridge is written by Shin-ichiro KAWASAKI and Contributed to Android by Hitachi, Ltd. and Renesas Solutions Corp.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值