dalvik VM的解释器分析

深入理解Dalvik虚拟机- 解释器的运行机制 Dalvik的指令执行是解释器+JIT的方式,解释器就是虚拟机来对Javac编译出来的字节码,做译码、执行,而不是转化成CPU的指令集,由CPU来做译码,执行。可想而知,解释器的效率是相对较低的,所以出现了JIT(Just In Time),JIT是将执行次数较多的函数,做即时编译,在运行时刻,编译成本地目标代码,JIT可以看成是解释器的一个补充优化。再之后又出现了Art虚拟机的AOT(Ahead 阅读详情

以KK的dalvik源码为基础来解析。

使用的源码基于https://github.com/AOKP/dalvik, 可以从https://github.com/AOKP/dalvik/archive/kitkat.zip 下载。

我是在linux下,使用vim + ctags做分析的。

由于ARM架构是使用最多也是最频繁的架构,所以我分析的重点是ARM的汇编如何实现解释器的。所以我在分析过程中会忽略掉与ARM无关的代码。

入口是什么?

从dvmCallMethod开始分析。这个函数是调用一个java method的主要入口函数。
dvmCallMethod->dvmCallMethodV->dvmInterpret->dvmMterpStd->dvmMterpStdRun

这一串调用,最终走到了dvmMterpStdRun函数。这个函数是用汇编写成的,在ARM架构中,实现的文件是vm/mterp/out/InterpAsm-armv7-a-neon.S。这个是Android KK ARM版本中使用的文件,也是我们分析的重点。

说了半天,dvmMterpStdRun是干什么的?其实就是用于解释实现各个dex 指令的,是整个解释的核心。不过,在正式了解这个函数之前,我们要分别了解下dvmCallMethodV,dvmInterpret,dvmMethodStd这3个函数的主要功能和涉及到的数据结构。
这些函数并不直接执行代码,但是却为dvmMterpStdRun准备了运行环境,只有理解了这些运行环境,才能更好的理解解释器的工作原理。

dvmCallMethodV

这个函数的核心作用是创建虚拟栈。
首先,我们了解下,一个普通的java method调用链条中的虚拟栈是怎么组织的。
dvmCallMethodV中完成栈构建的函数在callPrep->dvmPushInterpFrame

普通java method的虚拟栈

DVM为解释器分配的专门的栈,每个java线程有一个。这些信息保存在Thread结构体中。
//vm/interp/InterpState.h
struct InterpSaveState {
    const u2*       pc;         // Dalvik PC
    u4*             curFrame;   // Dalvik frame pointer
    const Method    *method;    // Method being executed
    DvmDex*         methodClassDex;
    JValue          retval;
    void*           bailPtr;
....
    struct InterpSaveState* prev;  // To follow nested activations
} __attribute__ ((__packed__));

//vm/Thread.h
/*
 * Our per-thread data.
 *
 * These are allocated on the system heap.
 */
struct Thread {
    /*
     * Interpreter state which must be preserved across nested
     * interpreter invocations (via JNI callbacks).  Must be the first
     * element in Thread.
     */
    InterpSaveState interpSave;
.....
    /* current limit of stack; flexes for StackOverflowError */
    const u1*   interpStackEnd;
......
    /* start (high addr) of interp stack (subtract size to get malloc addr) */
    u1*         interpStackStart;
......
};

Thread的interpStackStart和interpStackEnd表示虚拟栈的开始和结束范围。注意,栈的增长方向是由高地址向低地址曾长,因此inpterpStackStart >interpStackEnd。
inpterpSave的curFrame记录了当前虚拟栈(Frame)的位置。

我用下面的表来表示虚拟栈的组成部分:



  • StackSaveArea保存的是一个method 栈的信息,如当前的Method, 调用的PC地址,返回值地址等,还有prevFrame,指向Caller的frame地址;
  • curFrame保存当前函数的Frane地址。这个地址不包含StackSaveArea的指针。
  • method的registersSize指出全部寄存器的个数,包括用作参数传递的寄存器个数;
  • method的insSize指出作为参数的寄存器的个数;
  • method的outsSize指出该函数调用其他函数需要的寄存器个数
关于registerSize,insSize和outsSize的关系,以下几点:
  • 参数寄存器 (ins registers)是全部寄存器的一部分,所以,insSize < registerSize;
  • outsSize是调用其他函数需要的寄存器个数,这只是一个参考值。因为一个函数可以调用N个函数,这些被调用的函数的参数个数不一样,所以,outsSize总是取被调用函数中参数最多的那个值作为它的值;
  • 假设函数A调用函数B,A 向它的out register写入调用参数,当进入B函数后,A的out register就变成了B的ins register。这样,B就可以直接访问A传递过来的参数了。
  • ins register是register的一部分,在函数内部是可以参与运算的。因为需要和调用者共用,所以ins register总是位于虚拟栈的最高地址处。
关于参数,还需有注意一点:非静态函数,参数P0被隐含定义为this指针。

dvmCallMethodV的栈特点

dvmCallMethodV要插入一个BreakStackSaveArea对象,这也是个StackSaveArea,用于模拟一个栈,保证调用链条的连续性。

如下图:



dvmInterpret

该函数是解释模式的入口函数。在介绍这个函数之前,需要介绍两个重要的概念:InterpSaveState和ExecutionSubModes

InterpSaveState
InterpSaveState 结构体保存在Thread中,用于存储解释模式中重要的数据结构。它的定义如下: (vm/interp/InterpState.h)
struct InterpSaveState {
    const u2*       pc;         // Dalvik PC
    u4*             curFrame;   // Dalvik frame pointer
    const Method    *method;    // Method being executed
    DvmDex*         methodClassDex;
    JValue          retval;
    void*           bailPtr;
#if defined(WITH_TRACKREF_CHECKS)
    int             debugTrackedRefStart;
#else
    int             unused;        // Keep struct size constant
#endif
    struct InterpSaveState* prev;  // To follow nested activations
} __attribute__ ((__packed__));
  • pc: 保存的是当前正在执行的dalvik代码的地址
  • curFrame: 当前的frame地址,与StackSaveArea中的 curFrame是一致的
  • method: 当前调用的Method对象
  • methodClassDex: 当前method所属的DvmDex对象。这个对象包含了一个Dex文件对象和解析表(ResolvedClasses, ResolvedMethod, ResolvedString和ResolevedField等)
  • retval:返回值
  • bailPtr: 保存的是寄存器sp的地址,用于恢复解释器的堆栈
  • prev:指向上一个InterpSaveState对象。这个链表的建立就是在dvmIntepreter函数中实现的。
解释器要不断的更新这些值,保持这些值与运行状态一致。

ExecutionSubModes
它是一个枚举值,它的定义如下:
/*
 * Execution sub modes, e.g. debugging, profiling, etc.
 * Treated as bit flags for fast access.  These values are used directly
 * by assembly code in the mterp interpeter and may also be used by
 * code generated by the JIT.  Take care when changing.
 */
enum ExecutionSubModes {
    kSubModeNormal            = 0x0000,   /* No active subMode */
    kSubModeMethodTrace       = 0x0001,
    kSubModeEmulatorTrace     = 0x0002,
    kSubModeInstCounting      = 0x0004,
    kSubModeDebuggerActive    = 0x0008,
    kSubModeSuspendPending    = 0x0010,
    kSubModeCallbackPending   = 0x0020,
    kSubModeCountedStep       = 0x0040,
    kSubModeCheckAlways       = 0x0080,
    kSubModeSampleTrace       = 0x0100,
    kSubModeJitTraceBuild     = 0x4000,
    kSubModeJitSV             = 0x8000,
    kSubModeDebugProfile   = (kSubModeMethodTrace |
                              kSubModeEmulatorTrace |
                              kSubModeInstCounting |
                              kSubModeDebuggerActive)
};

整个mode可以分为两类,第一类是kSubModeNormal,这种模式下,解释器正常执行dalvik的字节码;其余可以归结为另一类,这类模式下,在执行dalvik字节码之前,先要调用一个dvmCheckBefore函数,这个函数会根据不同的submode,执行不同的操作,为debug,jit的trace code等功能,提供入口。
这个枚举值记录在Thread::InterpBreak.ctl.subMode中。

他通过调用 dvmDisableSubMode/dvmEnableSubMode -> updateInterpBreak函数来实现对模式的切换。
/*
 * Update interpBreak for a single thread.
 */
void updateInterpBreak(Thread* thread, ExecutionSubModes subMode, bool enable)
{
    InterpBreak oldValue, newValue;
    do {
.....
#ifndef DVM_NO_ASM_INTERP
        newValue.ctl.curHandlerTable = (newValue.ctl.breakFlags) ?
            thread->altHandlerTable : thread->mainHandlerTable;
#endif
    } while (dvmQuasiAtomicCas64(oldValue.all, newValue.all,
             &thread->interpBreak.all) != 0);
};
#endif
    } while (dvmQuasiAtomicCas64(oldValue.all, newValue.all,
             &thread->interpBreak.all) != 0);
}
循环的目的只是为了做原子同步。

其中,newValue.ctl.curHandlerTable取mainHandlerTable与altHandlerTable中的一个。mainHandlerTable的对应kSubModeNormal,表示不加dvmCheckBefore调用的实现;altHandlerTable就是加dvmCheckBefore的调用。

而curHandleTable就是解释器的入口函数地址。

mainHandlerTable与altHandlerTable的值来自./vm/Thread.cpp的allocThread函数:
#ifndef DVM_NO_ASM_INTERP
     thread->mainHandlerTable = dvmAsmInstructionStart;                                                                                                                                                         
     thread->altHandlerTable = dvmAsmAltInstructionStart;
     thread->interpBreak.ctl.curHandlerTable = thread->mainHandlerTable;
 #endif
dvmAsmInstructionStart和dvmAsmAltInstructionStart的定义在(vm/mterp/out/InterpAsm-armv7-a-neon.S 这是主流设备的架构)
  368     .global dvmAsmInstructionStart
  369     .type   dvmAsmInstructionStart, %function
  370 dvmAsmInstructionStart = .L_OP_NOP
.....
 9684     .global dvmAsmAltInstructionStart
 9685     .type   dvmAsmAltInstructionStart, %function
 9686     .text
 9687     
 9688 dvmAsmAltInstructionStart = .L_ALT_OP_NOP 


当完成这些操作后,dvm就会调用到函数dvmMterpStd。

这里还需要解释下,handleTable是什么样的概念,以及与dvmMterpStd有何不同。
dvmMterpStd是解释器的入口函数,而handleTable则是一个表,与dalvik字节码对应的一个表。dalvik字节码由两部分组成:操作代码(opcode)和操作数(operand)组成。
opcode由1个字节组成,operand则可以是0~5个更多。所以opcode最大是255。比如:
mov v1, v2
该字节码就是令v1 = v2。mov是opcode的助记符,v1, v2就是operand。

所以,一个opcode对应一段汇编代码,然后将这些汇编代码按照opcode的值顺序排列起来,就成了handleTable了。当我们执行时,只需要取出opcode对应的汇编码地址,就可以直接跳转并执行了。

详细的实现,厚文中介绍。

dvmMterpStd

dvmMterpStd函数通过调用dvmMterpStdRun以真正进入到解释器入口中。

如何读懂VM 解释器的汇编码?

你需要了解基本的ARM汇编。如果不了解可以从网上了解下,只需要一些入门的知识即可。

我们所阅读的汇编代码,主要在vm/mterp/out/InterpAsm-armv7-a-neon.S文件中。

HandlerTable

mainHandlerTable

VM汇编解释器中的核心代码是每个dalvik opcode对应的汇编代码段组成的handle表。这个表上面我们已经初步介绍了,现在我们介绍下其具体的实现。大家看一个例子:

/* ------------------------------ */
    .balign 64
.L_OP_MOVE: /* 0x01 */
/* File: armv6t2/OP_MOVE.S */
    /* for move, move-object, long-to-int */
    /* op vA, vB */
    mov     r1, rINST, lsr #12          @ r1<- B from 15:12
    ubfx    r0, rINST, #8, #4           @ r0<- A from 11:8
    FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST
    GET_VREG(r2, r1)                    @ r2<- fp[B]
    GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
    SET_VREG(r2, r0)                    @ fp[A]<- r2
    GOTO_OPCODE(ip)                     @ execute next instruction

  • 第一行,".balign 64" 表示代码从64字节处对齐。这条伪指令表形成的关键。所有的opcode对应的汇编码都是从64字节处对齐的,也就是说,opcode对应的汇编码,其最大大小是64字节。由此,handleTable就是一个64*opcode_count的数组,只要用 handleTableStart + opcode*64就可以得到opcode对应的地址了。
  • .L_OP_MOVE: 这个是标签,标签的取名是用 .L_OP_<opcode助记符大写> 组成的
  • GOTO_OPCODE(ip):该宏的作用,是跳转到下条指令

我们知道,mainHandlerTable对应的dvmAsmInstructionStart是从.L_OP_NONE开始的,汇编代码按照opcode的值顺序排列下来,每个标签间隔64字节,这样,就形成了一个完整的表。


altHandlerTable的小技巧

altHandlerTable指向的dvmAsmAltInstructionStart,是一组以.L_ALT_OP_<opcode助记符大写> 的标签组成,这些标签也是以64字节对齐的。 altHandlerTable实际上只是实现了一个跳转,比如.L_ALT_OP_MOVE在调用完dvmCheckBefore后,在转而调用.L_OP_MOV的。我们还是以mov指令为例:

/* ------------------------------ */
    .balign 64
.L_ALT_OP_MOVE: /* 0x01 */
/* File: armv5te/alt_stub.S */
/*
 * Inter-instruction transfer stub.  Call out to dvmCheckBefore to handle
 * any interesting requests and then jump to the real instruction
 * handler.    Note that the call to dvmCheckBefore is done as a tail call.
 * rIBASE updates won't be seen until a refresh, and we can tell we have a
 * stale rIBASE if breakFlags==0.  Always refresh rIBASE here, and then
 * bail to the real handler if breakFlags==0.
 */
    ldrb   r3, [rSELF, #offThread_breakFlags]
    adrl   lr, dvmAsmInstructionStart + (1 * 64)
    ldr    rIBASE, [rSELF, #offThread_curHandlerTable]
    cmp    r3, #0
    bxeq   lr                   @ nothing to do - jump to real handler
    EXPORT_PC()
    mov    r0, rPC              @ arg0
    mov    r1, rFP              @ arg1
    mov    r2, rSELF            @ arg2
    b      dvmCheckBefore       @ (dPC,dFP,self) tail call
重点看
adrl   lr, dvmAsmInstructionStart + (1 * 64)
这句话的意思是lr = dvmAsmInstructionStart + 1 * 64,其结果,就是lr = .L_OP_MOV的地址。

lr是link register的缩小,代表函数返回地址。一般情况下,我们调用一个函数,用bl <func>的形式。bl是branch link的缩写,即跳转到<func>函数入口,然后把返回地址写入lr寄存器。当<func>函数返回时,从lr中读取返回地址。

大家请注意,上面代码最后一句: b dvmCheckBefore,用b指令(branch),则只跳转,不改写lr寄存器的值,那么这个时候,寄存器的值就是.L_OP_MOV的地址,那就是说,当 dvmCheckBefore函数返回后,将直接返回到.L_OP_MOV处继续执行。

这就是dvm的小伎俩。所有的altHandle都是这么处理的。这样,在正式执行指令之前,我们就可以调用dvmCheckBefore做些处理了。


寄存器的使用情况

我们看到有如下定义:

/* single-purpose registers, given names for clarity */
#define rPC     r4
#define rFP     r5
#define rSELF   r6
#define rINST   r7
#define rIBASE  r8

  • rPC: 是当前正在执行的字节码的地址
  • rIBASE: 这个是curHandlerTable的地址
  • rSELF: 当前Thread对象指针
  • rFP : curFrame的地址
  • rINST: 当前的字节码值,只有低16位被用到,因为字节码以16位为一个单位。
  • r0~r3:是用作临时寄存器的
  • r10 : 作为一个通用寄存器来使用
  • r12(ip) 用作指令跳转的寄存器
  • 其余的被操作系统使用

几个重要的宏说明

FETCH_INST系列

这些包括FETCH_INST,FETCH_ADVANCE_INST, PREFETCH_ADVANCE_INST,FETCH_ADVANCE_INST_RB这些
他们的作用是获取inst到寄存器rINST或者其他寄存器。
以其中一个为例:
#define FETCH_ADVANCE_INST(_count) ldrh    rINST, [rPC, #((_count)*2)]!
该宏的等价表达式是 rINST = *(short*)(rPC += count*2)。
最后的一个"!"表示rPC在取值后要加上_count*2。

FETCH数据系列

包括FETCH, FETCH_S, FETCH_B宏,"S"表示signed,"B"表示byte。
如
#define FETCH(_reg, _count)     ldrh    _reg, [rPC, #((_count)*2)]

GOTO_OPCODE系列

包括下面几个宏
#define GOTO_OPCODE(_reg)       add     pc, rIBASE, _reg, lsl #6
#define GOTO_OPCODE_BASE(_base,_reg)  add     pc, _base, _reg, lsl #6
#define GOTO_OPCODE_IFEQ(_reg)  addeq   pc, rIBASE, _reg, lsl #6
#define GOTO_OPCODE_IFNE(_reg)  addne   pc, rIBASE, _reg, lsl #6

GOTO_OPCODE宏,就是 pc = rIBASE + (_reg << 6)
_reg << 6 相当于 _reg *64。rIBASE是curHandleTable的值,_reg的值,必须是opcode的值,这个就是直接跳转到指定的opcode上去。

addeq表示标志寄存器比较标志为0时跳转,addne则表示不为零时跳转。

VREG操作系列

#define GET_VREG(_reg, _vreg)   ldr     _reg, [rFP, _vreg, lsl #2]
#define SET_VREG(_reg, _vreg)   str     _reg, [rFP, _vreg, lsl #2]
这两个是用来获取虚拟寄存器中的值到指定寄存器的函数。


Thread管理相关的宏

/* save/restore the PC and/or FP from the thread struct */
#define LOAD_PC_FROM_SELF()     ldr     rPC, [rSELF, #offThread_pc]
#define SAVE_PC_TO_SELF()       str     rPC, [rSELF, #offThread_pc]
#define LOAD_FP_FROM_SELF()     ldr     rFP, [rSELF, #offThread_curFrame]
#define SAVE_FP_TO_SELF()       str     rFP, [rSELF, #offThread_curFrame]
#define LOAD_PC_FP_FROM_SELF()  ldmia   rSELF, {rPC, rFP}
#define SAVE_PC_FP_TO_SELF()    stmia   rSELF, {rPC, rFP}

/*
 * "export" the PC to the stack frame, f/b/o future exception objects.  Must
 * be done *before* something throws.
 *
 * In C this is "SAVEAREA_FROM_FP(fp)->xtra.currentPc = pc", i.e.
 * fp - sizeof(StackSaveArea) + offsetof(SaveArea, xtra.currentPc)
 *
 * It's okay to do this more than once.
 */
#define EXPORT_PC() \
    str     rPC, [rFP, #(-sizeofStackSaveArea + offStackSaveArea_currentPc)]

/*
 * Given a frame pointer, find the stack save area.
 *
 * In C this is "((StackSaveArea*)(_fp) -1)".
 */
#define SAVEAREA_FROM_FP(_reg, _fpreg) \
    sub     _reg, _fpreg, #sizeofStackSaveArea
上面这些宏都是为了读写Thread对象的成员而准备的。

一些常见指令的实现分析

const指令

const指令是对所有立即数的操作,其中包括3类:数字类(整数、浮点数)、字符串类和class操作。
const数字
const指令包括const/4, const/16, const, const/high16, const_wide, const_wide/32, const_wide/high16这些指令。这些指令的区别只在于数字宽度不一样。
我以const指令为例
/* ------------------------------ */
    .balign 64
.L_OP_CONST: /* 0x14 */
/* File: armv5te/OP_CONST.S */
    /* const vAA, #+BBBBbbbb */
    mov     r3, rINST, lsr #8           @ r3<- AA
    FETCH(r0, 1)                        @ r0<- bbbb (low)
    FETCH(r1, 2)                        @ r1<- BBBB (high)
    FETCH_ADVANCE_INST(3)               @ advance rPC, load rINST
    orr     r0, r0, r1, lsl #16         @ r0<- BBBBbbbb
    GET_INST_OPCODE(ip)                 @ extract opcode from rINST
    SET_VREG(r0, r3)                    @ vAA<- r0
    GOTO_OPCODE(ip)                     @ jump to next instruction

const指令的助记符形式是
/* const vAA, #+BBBBbbbb */
要让vAA = +BBBBbbbb值。
按照小端字节的排列顺序,这些值保存在  op|AA  BBBB bbbb。除了"op"外,一个字母表示4位。
可以看到,上面的代码含义是:
r3 = rINST >> 8;
r0 = *(++rPC)
r0 |= *(++rPC) << 16
ip = rINST & 255;
pc = rIBASE + ip * 64

const/string
string是一个java.lang.String对象,需要从解析表中获取,代码如下:
    .balign 64
.L_OP_CONST_STRING: /* 0x1a */
/* File: armv5te/OP_CONST_STRING.S */
    /* const/string vAA, String@BBBB */
    FETCH(r1, 1)                        @ r1<- BBBB
    ldr     r2, [rSELF, #offThread_methodClassDex]  @ r2<- self->methodClassDex
    mov     r9, rINST, lsr #8           @ r9<- AA
    ldr     r2, [r2, #offDvmDex_pResStrings]   @ r2<- dvmDex->pResStrings
    ldr     r0, [r2, r1, lsl #2]        @ r0<- pResStrings[BBBB]
    cmp     r0, #0                      @ not yet resolved?
    beq     .LOP_CONST_STRING_resolve
    FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
    SET_VREG(r0, r9)                    @ vAA<- r0
    GET_INST_OPCODE(ip)                 @ extract opcode from rINST
    GOTO_OPCODE(ip)                     @ jump to next instruction
如果DvmDex的pResStrings已经有解析好的stirng对象,直接取出,否则就调用dvmResolveString来解析。dvmResolveString函数会解析出字符串后再把他们添加到DvmDex的pResStrings中去,下次使用时,就不必再次解析了。

在这里,指令里面包含了字符串的string ID,通过该ID来解析字符串。
const/class
该指令是获取一个class对象,然后写入到指定的虚拟寄存器中
/* ------------------------------ */
    .balign 64
.L_OP_CONST_CLASS: /* 0x1c */
/* File: armv5te/OP_CONST_CLASS.S */
    /* const/class vAA, Class@BBBB */
    FETCH(r1, 1)                        @ r1<- BBBB
    ldr     r2, [rSELF, #offThread_methodClassDex]  @ r2<- self->methodClassDex
    mov     r9, rINST, lsr #8           @ r9<- AA
    ldr     r2, [r2, #offDvmDex_pResClasses]   @ r2<- dvmDex->pResClasses
    ldr     r0, [r2, r1, lsl #2]        @ r0<- pResClasses[BBBB]
    cmp     r0, #0                      @ not yet resolved?
    beq     .LOP_CONST_CLASS_resolve
    FETCH_ADVANCE_INST(2)               @ advance rPC, load rINST
    SET_VREG(r0, r9)                    @ vAA<- r0
    GET_INST_OPCODE(ip)                 @ extract opcode from rINST
    GOTO_OPCODE(ip)                     @ jump to next instruction

实现同string非常相似,主要不同在于它调用了dvmResolveClass来解析。

mov指令

mov指令按照不同字宽,包括mov, mov/from16, mov/16, mov/wide, mov/wide16,mov/object, mov/from_object, mov/result, mov/result_wide,mov/exception等。
根据名字,我们就大概知道其功能了。其中mov/result, mov/result_wide,是从Thread.retal中读取对应的值。当return时,解释器会将函数结果存储在Thread::retval变量中。

还是看看mov的例子吧
/* ------------------------------ */
    .balign 64
.L_OP_MOVE: /* 0x01 */
/* File: armv6t2/OP_MOVE.S */
    /* for move, move-object, long-to-int */
    /* op vA, vB */
    mov     r1, rINST, lsr #12          @ r1<- B from 15:12
    ubfx    r0, rINST, #8, #4           @ r0<- A from 11:8
    FETCH_ADVANCE_INST(1)               @ advance rPC, load rINST
    GET_VREG(r2, r1)                    @ r2<- fp[B]
    GET_INST_OPCODE(ip)                 @ ip<- opcode from rINST
    SET_VREG(r2, r0)                    @ fp[A]<- r2
    GOTO_OPCODE(ip)                     @ execute next instruction
实际要达到的效果是 fp[A] = fp[B],fp的地址放在rFP寄存器中。

get/put指令

java中对field的读,对应的是get指令,写,对应的是put指令。get/put分sget/sput和iget/iput。's'表示static即对静态field的读写,'i'表示instance,对对象field的读写。按照数据类型不同,分为iget, iget/wide,iget/object,iget/byte, iget/short,等等。每种get和put都是这样。
我们只要看看iget就知道他们的大致写法了
/* ------------------------------ */
    .balign 64
.L_OP_IGET: /* 0x52 */
/* File: armv6t2/OP_IGET.S */
    /*
     * General 32-bit instance field get.
     *
     * for: iget, iget-object, iget-boolean, iget-byte, iget-char, iget-short
     */
    /* op vA, vB, field@CCCC */
    mov     r0, rINST, lsr #12          @ r0<- B
    ldr     r3, [rSELF, #offThread_methodClassDex]    @ r3<- DvmDex
    FETCH(r1, 1)                        @ r1<- field ref CCCC
    ldr     r2, [r3, #offDvmDex_pResFields] @ r2<- pDvmDex->pResFields
    GET_VREG(r9, r0)                    @ r9<- fp[B], the object pointer
    ldr     r0, [r2, r1, lsl #2]        @ r0<- resolved InstField ptr
    cmp     r0, #0                      @ is resolved entry null?
    bne     .LOP_IGET_finish          @ no, already resolved
8:  ldr     r2, [rSELF, #offThread_method]    @ r2<- current method
    EXPORT_PC()                         @ resolve() could throw
    ldr     r0, [r2, #offMethod_clazz]  @ r0<- method->clazz
    bl      dvmResolveInstField         @ r0<- resolved InstField ptr
    cmp     r0, #0
    bne     .LOP_IGET_finish
    b       common_exceptionThrown

field的索引保存在CCCC部分,一个字母表示4位。CCCC最大值是65535。
如果field没有解析,还需有调用dvmResolveInstField先解析他,然后在继续运行。由于iget指令需要的代码数超出了64字节,因此用.LOP_IGET_FINISH完成剩余的工作。

INVOKE指令的分析

INVOKE指令是其中比较复杂的指令,它按照参数,可以分成RANGE和不带RANGE的,区别在于用于传递参数的寄存器是否是一个范围;按照类型可以分为: INVOKE_STATIC/DIRECT/VIRTUAL/INTERFACE/SUPER这几种。
其中static, direct, native函数都是非常接近的。它们的主要不同,在于获取被调用method的方法不同。

对于DVM来说,要invoke一个方法,需要经历3个步骤:
  1. 取得method
  2. 准备参数
  3. 准备栈结构

最后跳转到第一条指令并执行

取得method

按照不同的method类型,获取method的方法也不一样

direct/static
这3类结构是一样的,即在dalvik字节码中,保存的都是被调用函数的索引
字节码的形式是
    /* op vB, {vD, vE, vF, vG, vA}, class@CCCC */
    /* op {vCCCC..v(CCCC+AA-1)}, meth@BBBB */
其中class@CCCC或者meth@BBBB表示的就是method的索引。
用伪码表示就是
if (!self->pDvmDex->pResMethods[BBBB]) {
    dvmResolveMethod(class, BBBB, MEHOD_DIRECT);
}
....
这是最简单的形式
virtual/super
根据不同的this的类型,获取的method对象也不一样,所以,它必须先取得method对象的methodIdx,然后再从对象的class的vtable中取得真正的method,才能得到正确的method对象。用伪代码表示就是
    /* op vB, {vD, vE, vF, vG, vA}, class@CCCC */

mth = self->pDvmDex->pResMethods[CCCC];
if (mth == NULL) {
    mth = dvmResoleMethod(self->inpterState.method->clazz, BBBB, METHOD_VIRTUAL);
}
int mthidx = mth->methodIndex;
mth = this->clazz->vtable[mthidx];

super的实现与virtual很类似,所不同的是,对于super,需要从this->clazz->super->vtable中获取method。
interface
interface method的获取与其他method有很大的不同。在dalvik指令中,给出的method@BBBB这样的id,实际上指向的是interface的method,而我们执行时,需要获取的,却是this所属的class的对应的method。完成这个过程,是通过函数dvmFindInterfaceMethodInCache来实现的。

dvmFindInterfaceMethodInCache->dvmInterpFindInterfaceMethod,他们将解析出的真正method缓存起来,用hash表将interface method,class与对应的真实method关联起来。

首先,我们看看,如何从ineterface的method找到implements class的method。

从ineterface的method找到implements class的method
该功能由函数dvmInterpFindInterfaceMethod来实现的。 这个算法的核心是 InterfaceEntry结构。每个class都有这样一个结构,叫做iftable。iftable是一个InterfaceEntry结构的数组。这个结构如下:
/*
 * Used for iftable in ClassObject.
 */
struct InterfaceEntry {
    /* pointer to interface class */
    ClassObject*    clazz;

    /*
     * Index into array of vtable offsets.  This points into the ifviPool,
     * which holds the vtables for all interfaces declared by this class.
     */
    int*            methodIndexArray;
};
  • clazz:成员指定了当前一个class implememts其中的一个interface的clazz对象
  • methodIndexArray:interface的method在implements类中的对应的implement method在vtable中的索引
下面的源代码给出了基本的实现:

/*
 * Find the concrete method that corresponds to "methodIdx".  The code in
 * "method" is executing invoke-method with "thisClass" as its first argument.
 *
 * Returns NULL with an exception raised on failure.
 */
Method* dvmInterpFindInterfaceMethod(ClassObject* thisClass, u4 methodIdx,
    const Method* method, DvmDex* methodClassDex)
{
    Method* absMethod;
    Method* methodToCall;
    int i, vtableIndex;

    /*
     * Resolve the method.  This gives us the abstract method from the
     * interface class declaration.
     */
    absMethod = dvmDexGetResolvedMethod(methodClassDex, methodIdx);
....
    /* 找出来对应的iftable */
    for (i = 0; i < thisClass->iftableCount; i++) {
        if (thisClass->iftable[i].clazz == absMethod->clazz)
            break;
    }
.....
    /*用methodIndex得到vtableIndex */
    vtableIndex =
        thisClass->iftable[i].methodIndexArray[absMethod->methodIndex];
    assert(vtableIndex >= 0 && vtableIndex < thisClass->vtableCount);
    methodToCall = thisClass->vtable[vtableIndex];

......
    return methodToCall;
}


cache的结构和存储方法
每次都需要resolve interface method和一个for循环来查找InterfaceEntry结构,效率很低,于是,dvm引入了一个hash表来解决这个问题。
cache的定义通过宏ATOMIC_CACHE_LOOKUP来实现。

hash表的定义在Thread::pDevDex::pInterfaceCache。这个数据是AtomicCache结构,这个结构是一个带有线程同步的cache结构。其中thisClass和methodId作为hash key,用于快速查找数据的实现。进一步的信息可以查看宏 ATOMIC_CACHE_LOOKUP

准备参数

按照是否是rang,分为common_invokeMethodNoRange和common_invokeMethodRange两个标签。这两个标签的功能都是为了拷贝参数,只是实现方法上有不同。要拷贝参数,都分成两个步骤
  • 分配"outs"空间
  • 执行参数拷贝
看下分配"outs"空间部分
    @ prepare to copy args to "outs" area of current frame
    movs    r2, rINST, lsr #12          @ r2<- B (arg count) -- test for zero
    SAVEAREA_FROM_FP(r10, rFP)          @ r10<- stack save area
    FETCH(r1, 2)                        @ r1<- GFED (load here to hide latency)
    beq     .LinvokeArgsDone
首先,令r2 = arg_count, 然后取得r10 = rFP - sizeof(StackSaveArea)。然后将参数依次填充到r10之前的内存中去。这里,r10每次要-4. 
我将汇编与伪代码对照起来,就是
.LinvokeNonRange:
    rsb     r2, r2, #5                  @ r2<- 5-r2                         |         r2 = 5-r2
    add     pc, pc, r2, lsl #4          @ computed goto, 4 instrs each      |         switch(r2) {
    bl      common_abort                @ (skipped due to ARM prefetch)     |         
5:  and     ip, rINST, #0x0f00          @ isolate A                         |         case 0:
    ldr     r2, [rFP, ip, lsr #6]       @ r2<- vA (shift right 8, left 2)   |            ip = rINST & 0x0f00; //vA
    mov     r0, r0                      @ nop                               |            r2 = *(rFP + (ip >> 6))
    str     r2, [r10, #-4]!             @ *--outs = vA                      |            *(r10 - 4) = r2; r10 -= 4;
4:  and     ip, r1, #0xf000             @ isolate G                         |         case 1:
    ldr     r2, [rFP, ip, lsr #10]      @ r2<- vG (shift right 12, left 2)  |            ip = r1 & 0xf0000; //vG
    mov     r0, r0                      @ nop                               |            r2 = *(rFP + (ip >> 10));
    str     r2, [r10, #-4]!             @ *--outs = vG                      |            *(r10 - 4) = r2; r10 -= 4;
3:  and     ip, r1, #0x0f00             @ isolate F                         |         case 2:
    ldr     r2, [rFP, ip, lsr #6]       @ r2<- vF                           |            ip = r1 & 0x0f00
    mov     r0, r0                      @ nop                               |            r2 = *(rFP + (ip >> 6));
    str     r2, [r10, #-4]!             @ *--outs = vF                      |            *(r10 - 4) = r2; r10 -= 4;
2:  and     ip, r1, #0x00f0             @ isolate E                         |         case 3:
    ldr     r2, [rFP, ip, lsr #2]       @ r2<- vE                           |            ip = r1 & 0x00f0;
    mov     r0, r0                      @ nop                               |            r2 = *(rFP + (ip >> 2));
    str     r2, [r10, #-4]!             @ *--outs = vE                      |            *(r10 - 4) = r2; r10 -= 4;
1:  and     ip, r1, #0x000f             @ isolate D                         |         case 4:
    ldr     r2, [rFP, ip, lsl #2]       @ r2<- vD                           |            ip = r1 & 0x000f;
    mov     r0, r0                      @ nop                               |            r2 = *(rFP + (ip << 2));
    str     r2, [r10, #-4]!             @ *--outs = vD                      |            *(r10 - 4) = r2; r10 -= 4; }

dvm用了一个小技巧:
add     pc, pc, r2, lsl #4  
巧妙的跳转到指定的位置,以便隔去那些不需要处理的分支。

准备栈结构


.LinvokeArgsDone标签做最后的步骤,即准备栈并调用函数。

准备栈部分的代码如下: (删除部分关系不大的代码)
.LinvokeArgsDone: @ r0=methodToCall
    ldrh    r9, [r0, #offMethod_registersSize]  @ r9<- methodToCall->regsSize
    ldrh    r3, [r0, #offMethod_outsSize]  @ r3<- methodToCall->outsSize
    ldr     r2, [r0, #offMethod_insns]  @ r2<- method->insns
    ldr     rINST, [r0, #offMethod_clazz]  @ rINST<- method->clazz
    @ find space for the new stack frame, check for overflow
    SAVEAREA_FROM_FP(r1, rFP)           @ r1<- stack save area
    sub     r1, r1, r9, lsl #2          @ r1<- newFp (old savearea - regsSize)
    SAVEAREA_FROM_FP(r10, r1)           @ r10<- newSaveArea
@    bl      common_dumpRegs
    ldr     r9, [rSELF, #offThread_interpStackEnd]    @ r9<- interpStackEnd
    sub     r3, r10, r3, lsl #2         @ r3<- bottom (newsave - outsSize)
    cmp     r3, r9                      @ bottom < interpStackEnd?
    ldrh    lr, [rSELF, #offThread_subMode]
    ldr     r3, [r0, #offMethod_accessFlags] @ r3<- methodToCall->accessFlags
    blo     .LstackOverflow             @ yes, this frame will overflow stack

    @ set up newSaveArea
....
    str     rFP, [r10, #offStackSaveArea_prevFrame]
    str     rPC, [r10, #offStackSaveArea_savedPc]
<pre name="code" class="cpp" style="font-size: 13.3333339691162px;">1:
    tst     r3, #ACC_NATIVE
    bne     .LinvokeNative

......

    ldrh    r9, [r2]                        @ r9 <- load INST from new PC
    ldr     r3, [rINST, #offClassObject_pDvmDex] @ r3<- method->clazz->pDvmDex
    mov     rPC, r2                         @ publish new rPC


    @ Update state values for the new method
    @ r0=methodToCall, r1=newFp, r3=newMethodClass, r9=newINST
    str     r0, [rSELF, #offThread_method]    @ self->method = methodToCall
    str     r3, [rSELF, #offThread_methodClassDex] @ self->methodClassDex = ...
.....
    mov     rFP, r1                         @ fp = newFp
    GET_PREFETCHED_OPCODE(ip, r9)           @ extract prefetched opcode from r9
    mov     rINST, r9                       @ publish new rINST
    str     r1, [rSELF, #offThread_curFrame]   @ curFrame = newFp
    GOTO_OPCODE(ip)                         @ jump to next instruction
....

 还是用r10来做临时变量,可以看到,伪代码是这样的
 
r9 = method->registerSize;
r2 = method->insns; //code的入口地址
r1 = rFP - sizeof(StackSaveArea);
r1 = r1 - r9 * 4;
r10 = r1 - sizeof(StackSaveArea);

r10->prevFrame = rFP;
r10->savedPc = rPC;

r9 = *r2;
rPc = r9; //即 *method->insns; 第一条指令
rSELF->method = r0; //method to call
rSELF->pDvmDex = method_to_call->clazz->pDvmDex; //需要切换pDvmDex为当前调用函数的pDvmDex,否则很多解析表获取会出错

rFP = r1; //新的frame地址
ip = r9 & 0xff; //取得第一条指令的opcode
rINST = r9;
pc = curHandleTable[ip *64]; //完成跳转

对于native函数的调用,我们将在另外文章中详细描述。


异常处理

抛出异常

异常分为两种,一种是系统运行中,由系统产生的异常,一种是throw指令产生的异常。对于系统产生的异常,比如解析错误等,由内部函数dvmThrowException或者其包装函数产生。

dvmThrowException->dvmThrowChainedException,该函数的基本功能很简单,即创建一个exception对象,然后赋值给Thread::exception成员。这就完成了异常的抛出工作。

对于用户用throw指令抛出的异常,则也是将异常赋值给Thread::exception成员。异常的创建则是由new-instance指令来完成的,而throw指令只负责把对应的exception放入到Thread::exception变量中。

处理异常

异常的处理是紧随者异常抛出的。通常,throw指令在设置完self->exception后,就会调用common_exceptionThrown这个标签,而对于系统异常,如dvmResolveClass函数调用完成后,只要判断返回值是否成功,如果失败,就直接跳转到common_exceptionThrown,去处理异常。

当异常发生时,当前程序流程要立即中断,然后进入到catch和finally块进行处理,这些都是由common_excetptionThrown完成的。

为了方便理解,我直接将其转为伪代码,有兴趣的同学可以看源码(common_exceptionThrown标签的内容)

<span style="font-family:microsoft yahei;"><span style="white-space: pre-wrap;">exception = self->exception;
dvmAddTrackedAlloc(self, exception); //防止GC回收正在处理的exception对象
self->exception = NULL; //情况异常

void *newFP;
ret = dvmFindCatchBlock(self, rPc - self->method->insns, exception, 0, &newFP);
if (self->stackOverflowed) {
   dvmCleanupStackOverflow(self, exception);
}

rFP = newFP;
if (ret != 0) //catch到了
{
</span></span>   method = (rFP - sizeof(StackSaveArea))->method;
   self->method = method;
   rPC = method->insns + ret * 2; //指令入口
   dvmReleaseTrackedAlloc(exception, self);
   rIBASE = self->curHandlerTable;
   rINST = *rPC & 0xffff;
   ip = rINST & 0xff; //取得opcode
   pc = rIBASE + ip * 64;
<span style="font-family: 'microsoft yahei'; white-space: pre-wrap;">} else { //没有catch到
     //进一步抛出异常
}</span>
dvmFindCatchBlock是整个处理过程的核心,它返回catch(包括finally的处理地址和所在frame的地址。有了这两个信息后,dvm就能重建frame并跳转了。

我们知道,frame上有StackSaveArea,该结构保存了prevFrame和savedPc两个重要成员。prevFrame能让我们遍历整个数据栈,一直遇到一个BreakSaveArea。因为异常的处理不能跨越native函数,即如果A->nativeB->C->D,如果D发生异常,那么遍历到nativeB后就要停止,返回到nativeB函数,让nativeB函数去处理异常,或者继续抛出给A函数处理。

每个method的代码中都包含了catch信息,catch信息由(excetpion, startAddr,code-length, handle addr)组成,只要判断savedPc是否在startAddr和code-length中间,就能知道catch是否找到了。这个由函数dvmFindCatchBlock实现。




[pytorch]FixMatch代码详解(超详细) FixMatch代码详解-训练过程参数数据产生构建模型训练参数设置weight decay(权值衰减)学习率衰减(learning rate decay)指数移动平均(EMA)model训练过程运行结果 上一篇大概讲了数据加载的过程,这一篇更进一步,分析一下训练是怎样进行的 上一篇链接: [pytorch]FixMatch代码详解-数据加载 参数 所有的参数我都默认使用作者给出的例子: python train.py --dataset cifar10 --num-labeled 4000 --arch w 阅读详情

相关推荐

智能座舱-语音交互系统

从什么是智能座舱、智能座舱的发展驱动因素、智能座舱的构成要素三个方面梳理我对智能座舱的基础认识之后,为了加深“智能座舱产品入门”课程中语音交互部分知识的理解,我从什么是语音交互、语音交互的底层技术、智能座舱的语音交互等方面,对智能座舱语音交互系统相关的知识进行了梳理与总结。

GUOSA0928的博客 1440

Android培训班(77)Dalvik虚拟机的dvmInterpretStd函数

通上面的学习,先初始化解释器的状态,然后调用解释器来执行方法。解释器又分为两种,一种是使用汇编写成性能优化的解释器,一种是使用标准C语言写成的解释器,可以很通用,理解起来也容易一些。接着下来,就先来理解C语言写成的解释器,函数dvmInterpretStd的代码如下:boolINTERP_FUNC_NAME(Thread* self,InterpState* interpState){这...

anjichan4261的博客 211

freeswitch之呼叫中心acd模块开发

一、定义: ACD(Automatic Call Distributor) 自动呼叫分配,也叫智能选择座席。它是呼叫中心整个前台接入系统逻辑功能的描述:把接入的呼叫转接到正确的座席员桌前。ACD是现代呼叫中心有别于一般的热线电话系统和自动应答系统的重要标志,其性能的优劣直接影响到呼叫中心的效率和顾客的满意度。 二、ACD包含的功能点: 1 、程控交换功能 ACD在本质上也是交换机的一种类型,必须具有程控交换最基本的话务交换功能。 2 、排队功能 ACD必须具有话务排队的功能,所谓排队是指在内线都

飞鸟的博客 1870

【分析】dalvik虚拟机解释执行字节码

参考源码版本:Android-4.4.4_r2 提示:大部分分析直接注释在代码内。 dvmInterpret函数中调用了dvmInterpretPortable函数对方法的字节码进行解释执行,dvmInterpret在dalvik/vm/interp/Interp.cpp文件中。 dvmInterpretPortable函数在dalvik/vm/mterp/out/Int

不歪的专栏 7096

IATF16949核心职位全解析

TM / Top MgmtMR / QMRProcess OwnerQMQAQCSQECQEPQEPETEMEIEPM / PJMFMEA ModeratorMSA CoordinatorSPC CoordinatorCM / CCB内审员CP / CPK

weixin_49938552的博客 972

VS2022+Qt6保姆级配置指南:从插件安装到项目互通全流程(附常见错误解决)

本文提供了一份详细的Visual Studio 2022与Qt6集成开发环境配置指南。从插件安装、环境搭建到项目创建与互通,涵盖了全流程操作步骤,并针对常见的配置错误和调试问题提供了实用的解决方案,帮助开发者高效构建企业级桌面应用。

nginx7reverse的博客 1204

【ARM64 常见汇编指令学习 14 -- ARM 汇编 .balign,.balignw,.balign 伪指令学习】

balignl是一个伪操作符,伪操作符的意思就是机器码里,并没有一个汇编指令与其对应,是编译器来实现其功能的。balignl是.balign的变体。.balignl, fill } {, max }第一个参数alignment为一个正整数,以alignment的值的整数倍为结束地址进行对齐,以当前地址为起始地址,进行字节填充,比如当前地址为20,而alignment的值我们设定为16,那么字节填充自20开始,结束于20后第一个16的倍数地址处,即32处。第二个参数fill即我们选定的,用来填充的数值。

CodingCos的博客 1193

Mcgs 屏幕Modbus RTU通讯调试

昆仑通态屏幕作为Modbus Master 与 Modbus Slave通过RS485接口实现通信的调试记录

madao1234的博客 5569

Dalvik VM介绍

Dalvik VM介绍

hffyyg的博客 1708

OPTI Toolbox v2.28 安装与 3 个求解器补全:SCIP、SeDuMi、MOSEK 配置详解

本文详细介绍了OPTI Toolbox v2.28在MATLAB环境下的完整安装指南,包括SCIP、SeDuMi和MOSEK三个关键求解器的配置与优化技巧。通过分步指导和实战案例,帮助用户高效搭建强大的优化工具包,提升混合整数规划、二次约束二次规划等复杂问题的求解效率。

weixin_29039773的博客 350

windows 下AD9361的 no-os master 工程搭建

参考网站: https://wiki.analog.com/resources/fpga/docs/build#windows_environment_setup https://wiki.analog.com/resources/eval/user-guides/ad-fmcomms2-ebz/software/no-os-setup 参考博客: https://blog.csdn.net...

Bronceyang的博客 7292

Dalvik 和 ART虚拟机

1. Dalvik 虚拟机 Dalvik 虚拟机(Dalvik Virtual Machine),简称 Dalvik VM 或者 DVM。它是 Google 专门为 Android 平台开发的虚拟机,运行在 Android 运行时库中。DVM 并不是一个 Java 虚拟机,原因如下。 1.1 DVM 与 JVM 的区别 DVM 没有遵循 JVM 规范来实现,与 JVM 主要区别如下: 1. 基于的架构不同 JVM 的执行的指令是基于栈结构,这就意味着需要去栈中读写数据,所需的指令会很多,会导致速度

wangbuji的博客 1719

2026华为OD面试题029:勇攀数字高峰

在给定的数字地形图里寻找登山路径,数字代表当前位置的海拔高度。要求从最低海拔出发,不断攀登,最终到达最高山峰。地图保证最低海拔和最高山峰都只有一个。 登山规则: - 路径上的海拔必须严格递增 - 只能向上下左右 4 个方向移动 - 路径必须从最低海拔开始,到最高海拔结束 - 每个地点只能走一次 - 每一步的攀登高度差必须大于 0,且不超过指定值 `K` 输出满足条件的登山路径的数量。

琅嬛福地 116

android dalvik vm interp x86

解释器汇编代码运行原理<br /> <br />一.解释器汇编代码的重要性以及生成方法<br />       Dalvik的vm目录是虚拟机最重要的部分,而解释更是重中之重。所有的java字节码都经过解释器解释执行,因此解释器的准确性和性能直接影响程序的执行。<br />       在Dalvik里,有解释器的c语言实现的版本位于vm的interp目录下。其主要的代码在interpcore.h文件中。<br />       正因为解释器对代码的执行影响太大了,所以就有了解释器的汇编语言的版本。

flyren的专栏 1380

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

揭秘Dalvik解释器的核心运作机制

摘要: 本文介绍了Dalvik虚拟机解释器的核心机制及其与Method Tracing的关系。Dalvik解释器负责解释执行.dex字节码,包含switch-based和mterp两种实现方式。文章分析了方法调用(invoke)和返回(return)的处理流程,重点指出解释器会在这些关键位置插入Method Tracing的钩子(如dvmTraceMethodEnter/Exit),从而确保完整记录方法调用链。虽然这种插桩机制保证了trace数据的准确性,但也带来显著的性能开销。源码分析表明,相关逻辑主要集

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 544

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Android ART运行时无缝替换Dalvik虚拟机的过程分析

Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机,希望籍此解决饱受诟病的性能问题。老罗不打算分析ART的实现原理,只是很有兴趣知道ART是如何无缝替换掉原来的Dalvik虚拟机的。毕竟在原来的系统中,大量的代码都是运行在Dalvik虚拟机里面的。开始觉得这个替换工作是挺复杂的,但是分析了相关代码之后,发现思路是很清晰的。本来就详细分析这个无缝的替换过程。

老罗的Android之旅 9万+

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Dalvik虚拟机JNI方法的注册过程分析

在前面一文中,我们分析了Dalvik虚拟机的运行过程。从中可以知道,Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个JNI方法,那么就会直接跳到它的地址去执行。也就是说,JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行。由此也可看出,JNI方法是Android应用程序与本地操作系统直接进行通信的一个手段。在本文中,我们就详细分析JNI方法的注册过程。

老罗的Android之旅 6万+

DALVIK JIT 入口分析

dvmCompileTrace函数 如何进入到JIT模式? 如何获取需要JIT的代码? jit出的代码放在哪里? jit出代码与bytecode的对应关系是什么? dvmCompileTrace过程分析 BasicBlock MIR LIR Assemble 寄存器分配规则是什么?

doon的专栏 2766

深入理解Dalvik虚拟机- 解释器的执行机制

Dalvik的指令运行是解释器+JIT的方式,解释器就是虚拟机来对Javac编译出来的字节码,做译码、运行,而不是转化成CPU的指令集。由CPU来做译码,运行。可想而知。解释器的效率是相对较低的,所以出现了JIT(Just In Time),JIT是将运行次数较多的函数,做即时...

aoe41606的博客 415

一文让你搞懂各种虚拟机、解释器、JIT和AOT编译器

问题提出什么是字节码?为啥要有字节码的存在?不同VM的字节码一样吗?VM对字节码是怎么执行的?Dalvik和ART发展历程一些概念的解释hot spotHotSpot VM自适应编译(adaptive compilation)JIT编译动态编译(dynamic compilation)JRockit VM前端编译JIT编译和AOT编译比较ART VM中AOT/JIT混合编译Dalvik VMDVM(Dalvik VM)与JVM的区别基于的架构不同执行的字节码不同DVM允许在有限的内存中同时运行多个进程DV.

“点点”专栏 3333

JVM、DVM(Dalvik VM)和ART虚拟机对比

本文在于帮助大家快速的有一定深度的了解Android虚拟机。如果读者期望更加深入的了解相关的内容,可以根据文末给出的参考资料继续往下学习。如果觉得文中内容有什么错误,欢迎读者朋友指正,同时如需要转载请注明出处http://blog.csdn.net/evan_man/article/details/52414390,谢谢! Android系统使用Dalvik Virtual Machine

evan_man的专栏 1万+

Dalvik虚拟机的运行过程分析

在前面一篇文章中,我们分析了Dalvik虚拟机在Zygote进程中的启动过程。Dalvik虚拟机启动完成之后,也就是在各个子模块初始化完成以及加载了相应的Java核心类库之后,就是可以执行Java代码了。当然,Dalvik虚拟机除了可以执行Java代码之外,还可以执行Native代码,也就是C和C++代码。在本文中,我们就将继续以Zygote进程的启动过程为例,来分析Dalvik虚拟机的运行过程。

老罗的Android之旅 7万+

Dalvik控制VM详解

1.扩展的JNI检测      JNI(Java native Interface)是Java本地接口,提供了Java语言程序调用本地(C/C++)代码的方法。为了能触发CheckJNI标志位,第二种特性是dalvik.vm.checkjni,它的值覆盖了ro.kernel.Android.checkjni的值。如果这个特性没有被定义,dalvik.vm.checkjni也没有设置成false,

CodeMyDream的专栏 2315

Dalvik VM和JVM

(1) Dalvik VM和JVM 的第一个区别是 Dalvik VM是基于寄存器的架构(reg based),而JVM是栈机(stack based)。reg based VM的好处是可以做到更好的提前优化(ahead-of-time optimization)。 另外reg based的VM执行起来更快,但是代价是更大的代码长度。 (2) 另外一个区别是Dalvik可以允许多个instanc

A2268320026的专栏 688

揭秘Dalvik解释器核心执行流程

Dalvik解释器的核心是一个"fetch-decode-execute"主循环,位于InterpC-portable.cpp等源文件中。该循环不断从方法的字节码中取出指令,解析操作码并通过switch-case执行对应的C代码实现。典型流程包括:取指令(inst = *pc++)、解析操作码、执行指令(如加法、数据移动或方法调用)、更新程序计数器并循环。每条指令(如add-int、move、invoke-virtual)都有对应的处理代码段,通过操作寄存器和栈帧实现功能。这种机制保证了

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 302
上一篇: G1 算法论文
下一篇: android N编译遇到的问题
漂流的代码
博客等级 码龄23年 188粉丝 99原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值