Dalvik虛擬機是如何加載Dex

0x00 Dalvik虛擬機是如何執行程序Dex的?

這裏寫圖片描述

解析完dex結構之後,我就比較好奇dalvik虛擬機是如何加載並執行dex的?

davlik是基於寄存器的虛擬機,其從源代碼到可執行文件中的與java編譯有所不同,多了一步使用dx工具將class文件壓縮成Dalvik字節碼

對比jar和apk文件格式的區別:

0x01 dalvik相對於java虛擬機的優點:

代碼密度小,運行效率高,節省資源。
常量池只使用32位的索引
有內存限制
默認棧大小是12KB(3個頁,每頁4KB)
堆默認啓動大小爲2MB,默認最大值爲16MB
堆支持的最小啓動大小爲1MB,支持的最大值爲1024MB
堆和棧參數可以通過-Xms-Xmx修改

0x02 dalvik如何加載並執行dex

通過mmap函數將類加載到內存,然後通過讀寫操作訪問dex,然後解析dex文件內容並加載其中的類到哈希表中。並通過dexFileParse函數對其進行分析

映射dex到內存--》加載class--》Dalvik解釋器解釋執行

2.1 映射dex到內存

總的來說,dex文件可以抽象爲三個部分:頭部、索引、數據。通過頭部可以知道索引的位置和數目,以及數據區的起始位置。將dex文件映射到內存後,Dalvik會調用dexFileParse函數對其進行分析,分析的結果放到DexFile數據結構中。DexFile中的baseAddr指向映射區的起始位置,pClassDefs指向class索引的起始位置。爲了加快class的查找速度,還創建一個哈希表,對class名字進行哈希並生成索引。

2.2加載dex

在對文件解析完成後就要加載Class的具體內容了!在Dalvik中,由ClassObject 這個數據結構負責存放加載的信息。還包含一個Lock對象。如果其它線程想要獲取它的鎖,只有等這個線程釋放。

typedef struct Object {
    ClassObject* clazz;  // ClassObject類型對象
    Lock lock;           // 鎖對象
} Object;

如下圖所示,加載過程會在內存中alloc幾個區域,分別存放directMethods, virtualMethods, sfields, ifields。這些信息正是從dex 文件的數據區中讀取。

struct Field {
    ClassObject* clazz;    //所屬類型
    const char* name;      // 變量名稱
    const char* signature; // 如“Landroid/os/Debug;”
    u4 accessFlags;        // 訪問標記

    #ifdef PROFILE_FIELD_ACCESS
        u4 gets;
        u4 puts;
    #endif
};

首先會讀取Class的詳細信息,從中獲知directMethod, virtualMethod, sfield, ifield等的信息,然後再讀取。下圖爲加載完成後的示意。 這裏並未介紹加載的每個細節,感興趣的同學可通過此二圖自行分析。

還請大家注意的是在ClassObject結構中有個名爲super的成員。通過super成員來指向它的超類。

而dex加載整體流程如下:

待得到class索引後,實際的加載由loadClassFromDex來完成。首先它會讀取class的具體數據,分別加載directMethod, virtualMethod, ifield和sfield,然後爲ClassObject數據結構分配內存,並讀取dex文件的相關信息。加載完成後,將加載的class通過dvmAddClassToHash函數放入哈希表,以方便下次查找;最後,通過dvmLinkClass查找該類的超類,如果有接口類則加載相應的接口類。

2.3 dalvik解釋器分析

1.dalvik解釋器解釋指令前的準備工作
2.dalvik解釋器的模型
3.invoke-super指令實例分析

dalvik解釋器解釋指令前的準備工作
從外部進入解釋器的調用鏈如下:
dvmCallMethod -> dvmCallMethodV -> dvmInterpret

這三個函數是在解釋器取指令,選分支之前被調用,主要負責一些準備工作,包括分配虛擬寄存器,放入參數,初始化解釋器參數等。其中dvmCallMethod,直接調用了dvmCallMethodV.下面分析下後兩個函數。

dvmCallMethodV
dalvik虛擬機是基於寄存器架構的,可想而知,在具體執行函數之前,首先要做的就是分配好虛擬寄存器空間,並且將函數所需的參數,放入虛擬寄存器中。主要流程:

1.取出函數的簡單聲明,如onCreate函數的簡單聲明爲:VL
2.分配虛擬寄存器棧
3.放入this參數,根據參數類型放入申明中的參數
4.如果方法是native方法,直接跳轉到method->nativeFunc執行
5.如果方法是java方法,進入dvmInterpret解釋執行

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);

    //放入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 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;
            }
        }
    }
    //如果是本地方法,就直接跳轉到本地方法,若是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.判斷將要執行的方法是否合法(是否初始化或者error)
3.JIT環境的設置
4.根據系統參數選擇解釋器(Fast解釋器或者Portable解釋器)

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;//設置爲Portable解釋器

    // 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執行下條指令。

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)開始執行指令

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

#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這個標籤定義如下:

//invoke-super位描述符如下:A|G|op BBBB F|E|D|C
//methodCallRange depending on whether this is a "/range" instruction.
GOTO_TARGET(invokeSuper, bool methodCallRange)
    {
        Method* baseMethod;
        u2 thisReg;

        EXPORT_PC();
      //7010 0400 0000  opcode 對應的 A|G|OP BBBB CDEF
        //取出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)執行函數

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
        }

參考

http://blog.csdn.net/VirtualPower/article/details/5715277
https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system/DexFile.java
https://www.jianshu.com/p/14147171a599

解釋執行參考:
https://bbs.pediy.com/thread-226214.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章