android dalvik vm interp x86

来源:互联网 发布:网络民族虚无主义文章 编辑:程序博客网 时间:2024/05/21 00:15

解释器汇编代码运行原理

 

一.解释器汇编代码的重要性以及生成方法

       Dalvikvm目录是虚拟机最重要的部分,而解释更是重中之重。所有的java字节码都经过解释器解释执行,因此解释器的准确性和性能直接影响程序的执行。

       Dalvik里,有解释器的c语言实现的版本位于vminterp目录下。其主要的代码在interpcore.h文件中。

       正因为解释器对代码的执行影响太大了,所以就有了解释器的汇编语言的版本。对应不同的硬件cpu就有不同的汇编语言实现。它们都位于vmmterp目录下,每个cpu都有自己的子目录,里面是源代码。通过python工具可以生成目标代码,然后参与编译。在mterp目录下有各个cpu编译的配置文件。

       x86为例,配置文件名为config-x86。使用python根据这个配置文件最后会生成两个文件InterpAsm-x86.SInterpC-x86.c。同样道理,如果是armv5te,就是InterpAsm-armv5te.SInterpC-armv5te.c。所以想编译什么版本,就使对应的两个文件参与编译。

 

二.Python工具的使用

       Python工具的使用方法非常简单。在命令行中进入mterp目录,输入:

>gen-mterp.py target-arch output-dir

       比如要生成x86的汇编代码,生成的目录设为out,则输入:

> gen-mterp.py x86 out

 

三.解释器的入口代码

       解释器执行入口的代码位于interp目录下的Interp.c

 

void dvmInterpret(Thread* self, const Method* method, JValue* pResult)

{

……

    typedef bool (*Interpreter)(Thread*, InterpState*);

Interpreter stdInterp;

if (gDvm.executionMode == kExecutionModeInterpFast)

        stdInterp = dvmMterpStd;

    else

        stdInterp = dvmInterpretStd;

……

}

 

       可以看到,如果解释器函数为dvmInterpretStd,则走的就是纯c实现的路线。如果解释器函数为dvmInterpretStd,则走的就是参与编译的那个cpu的汇编语言实现的路线。

      

       线程与解释的关系可以通过下面的代码得到:

 

VMThread_createà

dvmCreateInterpThreadà

interpThreadStartà

dvmCallMethodà

dvmCallMethod*à

dvmInterpret

 

       由此可以看出,每个线程都最终调用了解释器代码。因此可以说线程与解释器是一一对应的关系。     

 

四.c代码与汇编代码的胶合

       解释器代码的组织不论是c语言实现的还是汇编语言实现的都是从某个入口进入,然后根据当前的字节码跳入对应的段执行执行完毕再取下一条指令执行,如此循环直至方法返回。

       在真正进入解释器代码前要定义一个c代码跳转到解释器的glue(胶合)结构。此结构定义如下:

 

typedef struct InterpState {

    const u2*   pc;                       //程序计数器

    u4*         fp;                     //帧指针

    JValue      retval;                      //返回值

    const Method* method;                  //将要执行的方法

    DvmDex*         methodClassDex;        //dex文件生成的结构

    Thread*         self;                              //所属线程

    void*           bailPtr;                          //出错以后跳转的目标

    const u1*       interpStackEnd;                     //为加速复制到这里

    volatile int*   pSelfSuspendCount;            //为加速复制到这里

    InterpEntry entryPoint;                     //下一步该做什么

    int         nextMode;                  //INTERP_STD还是INTERP_DBG

} InterpState;

 

       这些都是在解释器执行时需要用到的数据结构和值。当构造完这个胶合结构以后就跳入解释器的汇编代码执行了。这个函数是dvmMterpStdRun

       x86为例,对应的汇编代码为:

 

_dvmMterpStdRun:

    push    %ebp             /*保存进入时ebp(堆栈的指针)*/

    movl    %esp,%ebp    /*将当前堆栈指针移入ebp*/

    push    %edi                     /*edi寄存器压入栈*/

    push    %esi               /*esi寄存器压入栈*/

    push    %ebx             /*ebx寄存器压入栈*/

 

    subl   $60,%esp          /*空出60bytes,稍后会解释*/

 

    movl    IN_ARG0(%ebp),%ecx /*第一个参数即glue结构的指针*/

    movl    %ecx,rGLUE_SPILL(%ebp)  /*将它保存到指定位置*/

    LOAD_PC_FROM_GLUE(%ecx)        /*加载当前该执行的第一条指令pc*/

    LOAD_FP_FROM_GLUE(%ecx)         /*加载当前第一帧存放的位置*/

    movl    $_dvmAsmInstructionStart,rIBASE      /*保存第一条字节码指令(NOP)的地址供以后使用*/

 

    movl    %esp,offGlue_bailPtr(%ecx) /*保存错误时使用的指针*/

 

    movb    offGlue_entryPoint(%ecx),%al    /*获得入口执行指令的策略*/

 

    cmpb    $kInterpEntryInstr,%al         /*比较下是否为kInterpEntryInstr */

    jne     .Lnot_instr                            /*不是的话出错了*/

 

    FETCH_INST()                                 /*取第一条字节码指令*/

GOTO_NEXT                                          /*跳转到这条指令执行*/

 

这样就开始执行了

GOTO_NEXT是一个很重要的宏,它的定义如下:

.macro GOTO_NEXT

    /* For computed next version */

     movzx    rOPCODE,%eax

     sall     $6,%eax

     addl     rIBASE,%eax

     jmp      *%eax

.endm

其含义就是根据取得的下一条指令的字节码,计算从第一条字节码开始的第几条字节码,然后跳转到那里。字节码指令的汇编执行体是64字节对齐的。

这一点在虚拟机初始化时是要检验的dvmStartup函数(vm/init.c),具体就是dvmCheckAsmConstants函数(mterp.c),因此偏移就很好计算。

/*获得指令字节码的编码值*/

movzx    rOPCODE,%eax

/*乘以64*/

sall     $6,%eax

/*加上第一条指令(NOP)的地址*/

addl     rIBASE,%eax

/*跳转过去*/

jmp      *%eax

 

关于之前的

subl   $60,%esp          /*空出60bytes,其实就是从rPC_SPILL 一直到OUT_ARG0*/

是因为以下的结构

 

#define IN_ARG0        (  8)

#define CALLER_RP      (  4)

#define PREV_FP        (  0) /* <- dvmMterpStdRun ebp */

/* Spill offsets relative to %ebp */

#define EDI_SPILL      ( -4)

#define ESI_SPILL      ( -8)

#define EDX_SPILL      (-12) /* <- esp following dmMterpStdRun header */

#define rPC_SPILL      (-16)

#define rFP_SPILL      (-20)

#define rGLUE_SPILL    (-24)

#define rIBASE_SPILL   (-28)

#define rINST_FULL_SPILL    (-32)

#define TMP_SPILL      (-36)

#define LOCAL0_OFFSET  (-40)

#define LOCAL1_OFFSET  (-44)

#define LOCAL2_OFFSET  (-48)

#define LOCAL3_OFFSET  (-52)

/* Out Arg offsets, relative to %sp */

#define OUT_ARG4       ( 16)

#define OUT_ARG3       ( 12)

#define OUT_ARG2       (  8)

#define OUT_ARG1       (  4)

#define OUT_ARG0       (  0)  /* <- dvmMterpStdRun esp */

 

这段结构对于一趟解释只存在一个。

下面是一些比较难以理解的汇编代码部分:

 

五.关于本地方法的调用

       本地方法的调用不属于虚拟机中构造的栈的一部分,不要混淆。

       任何本地方法都会进入如下汇编代码段:

.LinvokeNative:

    GET_GLUE(%ecx)                      /* %ecxßpMterpGlue,得到胶合结构体指针 */

    movl        %eax, OUT_ARG1(%esp)    /* 将要调用的方法指针存入第1个输出参数 */

    movl        offGlue_self(%ecx), %ecx     /* %ecxßglue->self,得到Thread结构 */

    movl           offThread_jniLocal_nextEntry(%ecx), %eax

    movl              %eax, offStackSaveArea_localRefTop(%edx) /* newSaveArea->localRefTopßrefNext,保存本地方法引用对象表的底部地址 */

    movl        %edx, OUT_ARG4(%esp)    /* 保存newSaveArea(stack save area指针)到第4个输出参数 */

    movl        LOCAL1_OFFSET(%ebp), %edx /* %edxßnewFP,保存新的帧指针 */

    movl        %edx, offThread_curFrame(%ecx) /* glue->self->curFrame<- newFPThread结构体的当前帧赋为新的帧指针 */

    movl        %ecx, OUT_ARG3(%esp)    /* save glue->self,保存Thread结构到第3个输出参数 */

movl        %ecx, OUT_ARG2(%esp)    /* save glue->self,保存Thread结构到第2个输出参数作为本地方法的参数 */

GET_GLUE(%ecx)                            /* %ecxßpMterpGlue,得到胶合结构体指针 */

    movl        OUT_ARG1(%esp), %eax    /* 取得%eaxßmethodToCall,要调用的方法*/

    lea         offGlue_retval(%ecx), %ecx   /* %ecxß&retvalecx设为函数函数返回值的地址 */

    movl        %ecx, OUT_ARG0(%esp)     /* 将返回值作为第0个参数 */

    push        %edx                      /* parameter newFP,将当前帧压进栈,调用以上这些参数所在的地址供本地方法调用 */

    call        *offMethod_nativeFunc(%eax)  /* call methodToCall->nativeFunc,调用本地方法 */

    lea         4(%esp), %esp                        /* 跳过刚才压入的%edx */

    movl     OUT_ARG4(%esp), %ecx    /* %ecxßnewSaveArea,取出newSaveArea(stack save area指针) */

    movl        OUT_ARG3(%esp), %eax    /* %eaxßglue->self,取出Thread结构 */

    movl        offStackSaveArea_localRefTop(%ecx), %edx /* %edxß newSaveArea->localRefTop,取出本地方法引用对象表的底部地址 */

    cmp         $0, offThread_exception(%eax) /* 检查是否有异常发生 */

    movl        rFP, offThread_curFrame(%eax) /* glue->self->curFrameß rFP,重新设置当前帧 */

    movl  %edx, offThread_jniLocal_nextEntry(%eax) /* glue->self<- newSaveArea->localRefTop,设置回本地方法引用对象表的底部地址 */

UNSPILL(rPC)                                                                                   /* 取回当前的解释循环pc */

    jne         common_exceptionThrown   /* 不为0,表示有异常发生,跳转到处理异常的代码段 */

    FETCH_INST_WORD(3)                         

    ADVANCE_PC(3)                                     /* 跳过刚才的字节码指令 */

GOTO_NEXT                           # jump to next instruction

 

本地方法的指针在dex文件解析时都转成了dvmResolveNativeMethod,具体代码在oo/Class.cloadMethodFromDex函数里:

meth->nativeFunc = dvmResolveNativeMethod;

根据vm/native.c又将其设置为InternalNative.c中的对应c函数。这样就把java本地方法和c函数桥接起来了。

 

六.一般的方法调用

方法调用以invokeVirtual为例:

 

.L_OP_INVOKE_VIRTUAL: /* 0x6e */

/* 调用方法所在的dex文件的第CCCC个方法,参数为vD,vE,vF,vG,vAvB表示有几个参数 */

GET_GLUE(%eax)                                                  /* 得到glue结构指针 */

movzwl    2(rPC),%ecx                                 /* 取到CCCC,它代表方法的索引 */

movl      offGlue_methodClassDex(%eax),%eax  /* 取到eaxßpDvmDex */

EXPORT_PC()

movl      offDvmDex_pResMethods(%eax),%eax  /* eaxß pDvmDex->pResMethods,取到这个文件中所有方法的首地址 */

movl      (%eax,%ecx,4),%eax                   /* 取到该方法的偏移位置处的方法 */

testl       %eax,%eax                           /* 是否已经解析了该方法 */

jne       .LOP_INVOKE_VIRTUAL_continue    /* 已经解析了,则跳转 */

GET_GLUE(%eax)                                                  /* 得到glue结构指针 */

movl      %ecx,OUT_ARG1(%esp)                /* CCCC赋值到第一个输出参数 */

movl      offGlue_method(%eax),%eax               /* 得到gluemethod结构指针 */

SPILL(rPC)                                                            /* 保存当前的解释循环pc */

jmp       .LOP_INVOKE_VIRTUAL_more             /* 跳转到解析这个方法的代码段中*/

 

.LOP_INVOKE_VIRTUAL_more:

    movl      offMethod_clazz(%eax),%eax               /* 得到method所属的类指针 */

    movl      %eax,OUT_ARG0(%esp)                   /* 把它作为第0个输出参数 */

    movl      $METHOD_VIRTUAL,OUT_ARG2(%esp) /* METHOD_VIRTUAL类型值作为第2个参数 */

    call      _dvmResolveMethod                       /* eaxßcall(clazz, ref, flags),调用dvmResolveMethod方法 */

    UNSPILL(rPC)                                                       /* 取出之前保存的解释循环pc */

    testl     %eax,%eax                                /* 解析返回的方法时候为空 */

    jne       .LOP_INVOKE_VIRTUAL_continue    /* 不为空,说明解析完了则转入调用的代码段 */

    jmp       common_exceptionThrown                /* 为空则跳到异常代码处理段 */

 

.LOP_INVOKE_VIRTUAL_continue:

    movzwl    4(rPC),%ecx                                    /* 得到ecxßGFED保存着这四个参数的地址的变量 */

    .if       (!0)

    andl      $0xf,%ecx                                 /* 得到ecxßD */

.endif

GET_VREG(%ecx,%ecx)                                   /* 获得调用这个方法的对象的指针 */

    movzwl    offMethod_methodIndex(%eax),%eax  /* 方法在虚方法表中的下标 */

    testl       %ecx,%ecx                                    /* 测试下是否为空对象 */

    je         common_errNullObject                     /* 是空对象则表示出错 */

    movl      offObject_clazz(%ecx),%ecx                       /* 获得对象所指的类型对象 */

    movl      offClassObject_vtable(%ecx),%ecx               /* 获得类型对象的虚方法表 */

    movl      (%ecx,%eax,4),%eax                       /* 得到eaxßvtable[methodIndex]方法指针 */

jmp       common_invokeMethodNoRang              /* 跳转到这个代码段调用方法 */

 

common_invokeMethodNoRange:

.LinvokeNewNoRange:

movzbl    1(rPC),rINST_FULL                  /* rINST_FULLßBA,保存之前的参数BA */

SPILL(rPC)                                                            /* 保存rPC */

movl  rINST_FULL, LOCAL0_OFFSET(%ebp)       /* LOCAL0_OFFSET(%ebp)ßBA,将BA保存在局部变量local0的位置 */

shrl        $4, LOCAL0_OFFSET(%ebp)            /* LOCAL0_OFFSET(%ebp)ßB,只保存B */

je          .LinvokeArgsDone                         /* 如果为0个参数, 那么直接跳转到args done */

movzwl      4(rPC), %ecx                                 /* 取得%ecxßGFED */

SAVEAREA_FROM_FP(%edx,rFP)                      /* 取得%edxß&StackSaveArea,跨过虚拟机特定参数存储区 */

 

/*

* %eax=methodToCall, %ecx=GFED, LOCAL0_OFFSET(%ebp)=count, %edx=outs

*/

 

.LinvokeNonRange:

cmp   $2, LOCAL0_OFFSET(%ebp)                   /* LOCAL0_OFFSET(%ebp)2作比较 */

movl  %ecx, LOCAL1_OFFSET(%ebp)                 /* LOCAL1_OFFSET(%ebp)ßGFED,将GFED保存到第二个局部变量的位置 */

jl     1f                                                             /* 2小说明只有一个参数,跳转到一个参数的处理 */

je     2f                                                            /* 等于2说明有两个参数,跳转到两个参数的处理 */

cmp   $4, LOCAL0_OFFSET(%ebp)                    /* LOCAL0_OFFSET(%ebp)4作比较 */

jl     3f                                                            /* 4小说明有三个参数,跳转到三个参数的处理 */

je     4f                                                           /* 等于4说明有四个参数,跳转到四个参数的处理 */

5:                                                                          /* 有五个参数 */

andl   $15, rINST_FULL                                  /* rINSTßA */

lea    -4(%edx), %edx                                   /* %edxß update &outs; &outs--,填第4out参数的地址 */

movl  (rFP, rINST_FULL, 4), %ecx                              /* %ecxßvA */

movl  %ecx, (%edx)                                    /* outsßvAvA到第4个输出参数 */

movl  LOCAL1_OFFSET(%ebp), %ecx             /* %ecxß GFED */

4:

shr         $12, %ecx                                 /* %ecxßG */

lea         -4(%edx), %edx                             /* %edxß update &outs; &outs--,填第3二个out参数的地址 */

movl        (rFP, %ecx, 4), %ecx                              /* %ecxß vG */

movl        %ecx, (%edx)                              /* outsßvGvG到第3个输出参数 */

movl        LOCAL1_OFFSET(%ebp), %ecx       /* %ecx<- GFED */

3:

and         $0x0f00, %ecx   

shr         $8, %ecx                                  /* ecxß F */

lea         -4(%edx), %edx                             /* %edxß update &outs; &outs--,填第2out参数的地址 */

movl        (rFP, %ecx, 4), %ecx                              /* %ecxß vF */

movl        %ecx, (%edx)                              /* outsßvFvF到第2个输出参数 */

movl        LOCAL1_OFFSET(%ebp), %ecx       /* %ecxß GFED */

2:

and         $0x00f0, %ecx     

shr         $4, %ecx                                  /* %ecxß E */

lea         -4(%edx), %edx                             /* %edxß update &outs; &outs--,填第1out参数的地址 */

movl        (rFP, %ecx, 4), %ecx                              /* ecxßvE */

movl        %ecx, (%edx)                                    /* outsßvEvE到第1个输出参数 */

movl        LOCAL1_OFFSET(%ebp), %ecx       /* %ecx<- GFED */

1:

and         $0x000f, %ecx      

movl        (rFP, %ecx, 4), %ecx                       /* %ecxßvD */

movl        %ecx, -4(%edx)                           /* outsßvDvD到第0个输出参数 */

 

0:

 

   /*

    * %eax is "Method* methodToCall", the method we're trying to call

    * find space for the new stack frame, check for overflow

    */

 

.LinvokeArgsDone:

movzwl  offMethod_registersSize(%eax), %edx       /* %edxßmethodToCall->regsSize,需要的寄存器总总数 */

movzwl  offMethod_outsSize(%eax), %ecx             /* %ecxßmethodToCall->outsSize*/

movl  %eax, LOCAL0_OFFSET(%ebp)                 /* LOCAL0_OFFSETßmethodToCall */

shl    $2, %edx                                                /* %edxßupdate offset,总共需要空出多少寄存器空间 */

SAVEAREA_FROM_FP(%eax,rFP)                      /* 取得%eaxß&StackSaveArea,跨过虚拟机特定参数存储区 */

subl   %edx, %eax                                              /* %eaxßnewFP; (old savearea - regsSize),新的一帧的帧起始指针赋给eax */

GET_GLUE(%edx)                                               /* %edxßpMterpGlue,胶合结构体的指针 */

movl  %eax, LOCAL1_OFFSET(%ebp)                 /* LOCAL1_OFFSET(%ebp)ß&outs,新的一帧的起始指针 */

subl   $sizeofStackSaveArea, %eax                        /* 取得%eaxßnewSaveArea (stack save area using newFP),跨过虚拟机特定参数存储区 */

movl  offGlue_interpStackEnd(%edx), %edx           /* 取得%edxßglue->interpStackEndglue结构体保存的interStackEnd */

movl %edx, LOCAL2_OFFSET(%ebp)                    /* LOCAL2_OFFSETßglue->interpStackEnd,将interpStackEnd存入局部变量2 */

shl  $2, %ecx                                                   /* 取得%ecxßupdate offset for outsSizeout参数的总大小 */

movl  %eax, %edx                                               /* 取得%edxßnewSaveArea,特定参数区的起始地址 */

sub  %ecx, %eax                                                /*  %eaxßbottom; (newSaveArea - outsSize),再减去out参数空间的总大小,就是下一帧的最低地址即栈底 */

cmp  LOCAL2_OFFSET(%ebp), %eax                   /* 比较interpStackEnd栈底 */

movl  LOCAL0_OFFSET(%ebp), %eax                  /* %eaxßrestore methodToCall,取出保存在局部变量0中的要调用方法的指针 */

jl     .LstackOverflow                                             /* 栈底过小即溢出,跳转到溢出处理 */

 

   /*

    * set up newSaveArea

    */

 

#ifdef EASY_GDB

SAVEAREA_FROM_FP(%ecx,rFP)          # %ecx<- &StackSaveArea

movl   %ecx, offStackSaveArea_prevSave(%edx) # newSaveArea->prevSave<- &outs

#endif

 

movl   rFP, offStackSaveArea_prevFrame(%edx)    /* newSaveArea->prevFrameß rFP,在新的一帧中保存前一帧起始地址的指针 */

movl    rPC_SPILL(%ebp), %ecx                         /* 取得当前帧当前pc */

movl    %ecx, offStackSaveArea_savedPc(%edx)   /* newSaveArea->savedPcßrPC,当前pc存入新帧中保存上一帧pc的位置 */

testl    $ACC_NATIVE, offMethod_accessFlags(%eax) /* check for native call,判断要调用的方法是否是本地方法 */

movl   %eax, offStackSaveArea_method(%edx)      /* newSaveArea->methodßmethod to call,将当前方法保存到指定位置 */

jne     .LinvokeNative                                         /* 是本地方法,跳转到处理本地方法的代码段处 */

 

/*

* Update "glue" values for the new method

* %eax=methodToCall, LOCAL1_OFFSET(%ebp)=newFp

*/

 

movl    offMethod_clazz(%eax), %edx                  /* 取得%edxßmethod->clazz,将要执行的方法 */

GET_GLUE(%ecx)                                              /* 取得%ecx<- pMterpGlue */

movl      offClassObject_pDvmDex(%edx), %edx     /* 取得%edxßmethod->clazz->pDvmDex */

movl    %eax, offGlue_method(%ecx)                   /* glue->methodßmethodToCall保存到glue结构中 */

movl       %edx, offGlue_methodClassDex(%ecx)/* glue->methodClassDexß method->clazz->pDvmDex */

movl    offMethod_insns(%eax), rPC                    /* rPCßmethodToCall->insns,指令为新方法的指令 */

movl    offGlue_self(%ecx), %eax                        /* 取得%eaxßglue->self */

movl    LOCAL1_OFFSET(%ebp), rFP                 /* 取得rFPßnewFP,新帧的起始地址 */

movl    rFP, offThread_curFrame(%eax)                      /* glue->self->curFrameßnewFP,线程的当前帧为新帧 */

FETCH_INST()

GOTO_NEXT                                    /* jmp to methodToCall->insns */

 

 

.L_OP_INVOKE_VIRTUAL_RANGE: /* 0x74 */

    /* 新方法为方法所在dex文件第BBBB个方法,并将v[CCCC]v[CCCC+AA-1]中的内容依次复制到当前帧的输出区 */

    GET_GLUE(%eax)                                                  /* 取得%eaxßpMterpGlue,胶合结构体的指针 */

    movzwl    2(rPC),%ecx                                             /* 取得ecxßBBBB,它代表方法的索引*/

    movl      offGlue_methodClassDex(%eax),%eax  /* 取得eaxßpDvmDex */

    EXPORT_PC()

    movl        offDvmDex_pResMethods(%eax),%eax   /* 取得eaxßpDvmDex->pResMethods,取到这个文件中所有方法的首地址*/

    movl      (%eax,%ecx,4),%eax                      /* 取到该方法的偏移位置处的方法 */

    testl       %eax,%eax                            /* 是否已经解析了该方法 */

    jne       .LOP_INVOKE_VIRTUAL_RANGE_continue   /* 已经解析了,则跳转 */

    GET_GLUE(%eax)                                                 /* 取得%eaxßpMterpGlue,胶合结构体的指针 */

    movl      %ecx,OUT_ARG1(%esp)                      /* BBBB赋值到第一个输出参数 */

    movl      offGlue_method(%eax),%eax               /* 得到gluemethod结构指针 */

SPILL(rPC)                                                       /* 保存当前的解释循环pc */

jmp       .LOP_INVOKE_VIRTUAL_RANGE_more /* 跳转到解析这个方法的代码段中*/

 

.LOP_INVOKE_VIRTUAL_RANGE_more:

    movl      offMethod_clazz(%eax),%eax               /* 得到method所属的类指针 */

    movl      %eax,OUT_ARG0(%esp)                  /* 把它作为第0个输出参数 */

movl      $METHOD_VIRTUAL,OUT_ARG2(%esp) /* METHOD_VIRTUAL类型值作为第2个参数 */

call      _dvmResolveMethod                             /* eaxßcall(clazz, ref, flags),调用dvmResolveMethod方法 */

    UNSPILL(rPC)                                                       /* 取出之前保存的解释循环pc */

    testl     %eax,%eax                                    /* 解析返回的方法时候为空 */

    jne       .LOP_INVOKE_VIRTUAL_RANGE_continue /* 不为空,说明解析完了则转入调用的代码段 */

    jmp       common_exceptionThrown                 /* 为空则跳到异常代码处理段 */

 

    /* At this point:

     *   eax = resolved base method

     *   ecx = scratch

     */

.LOP_INVOKE_VIRTUAL_RANGE_continue:

    movzwl    4(rPC),%ecx                                   /* 得到ecxßCCCC偏移位置 */

    .if       (!1)

    andl      $0xf,%ecx                                 /*不参与编译*/

    .endif

    GET_VREG(%ecx,%ecx)                             /* 获得调用这个方法的对象的指针即CCCC偏移位置处的对象 */

    movzwl    offMethod_methodIndex(%eax),%eax  /* 方法在虚方法表中的下标 */

    testl     %ecx,%ecx                                 /* 测试下是否为空对象 */

    je        common_errNullObject                      /* 是空对象则表示出错 */

    movl      offObject_clazz(%ecx),%ecx                      /* 获得对象所指的类型对象 */

    movl      offClassObject_vtable(%ecx),%ecx        /* 获得类型对象的虚方法表 */

    movl      (%ecx,%eax,4),%eax                     /* 得到eaxßvtable[methodIndex]方法指针 */

jmp       common_invokeMethodRange               /* 跳转到这个代码段调用方法 */

 

common_invokeMethodRange:

.LinvokeNewRange:

 

   /*

    * prepare to copy args to "outs" area of current frame

    */

 

    movzbl      1(rPC),rINST_FULL                            /* 取到rINST_FULLßAA */

    movzwl      4(rPC), %ecx                          /* 取到%ecxßCCCC的值 */

SPILL(rPC)                                                            /* 保存当前的解释循环pc */

    SAVEAREA_FROM_FP(%edx,rFP)                      /* 取得%edxß&StackSaveArea,跨过虚拟机特定参数存储区 */

    test        rINST_FULL, rINST_FULL                       /* 测试下AA是否为0 */

    movl      rINST_FULL, LOCAL0_OFFSET(%ebp) /* LOCAL0_OFFSET(%ebp)ßAA,保存AA的值到第0个局部变量 */

jz        .LinvokeArgsDone                             /* 如果为0个参数, 那么直接跳转到args done */

 

   /*

    * %eax=methodToCall, %ecx=CCCC, LOCAL0_OFFSET(%ebp)=count, %edx=&outs (&stackSaveArea)

    * (very few methods have > 10 args; could unroll for common cases)

    */

 

    movl     %ebx, LOCAL1_OFFSET(%ebp)            /* LOCAL1_OFFSET(%ebp)<- save %ebx,暂时将ebx的值保存到第1个局部变量中 */

    lea         (rFP, %ecx, 4), %ecx                      /* 取得%ecxßv[CCCC] */

    shll        $2, LOCAL0_OFFSET(%ebp)              /* LOCAL0_OFFSET(%ebp)ßoffsetAA*4 */

    subl        LOCAL0_OFFSET(%ebp), %edx        /* %edxßupdate &outs,输出参数的最下端地址 */

    shrl        $2, LOCAL0_OFFSET(%ebp)           /* LOCAL0_OFFSET(%ebp)ßoffset,恢复到AA */

1:

    movl        (%ecx), %ebx                           /* %ebxßvCCCC */

    lea         4(%ecx), %ecx                                 /* %ecxß&vCCCC++ */

    subl      $1, LOCAL0_OFFSET(%ebp)                /* LOCAL0_OFFSETßLOCAL0_OFFSET—AA-- */

    movl        %ebx, (%edx)                           /* *outs<- [vCCCC] */

    lea         4(%edx), %edx                                 /* outs++ */

    jne         1b                                      /* 循环如果count(LOCAL0_OFFSET(%ebp))非零 */

    movl        LOCAL1_OFFSET(%ebp), %ebx    /* 取回保存的%ebx */

jmp         .LinvokeArgsDone                        /* 参数复制完毕,继续 */

 

如果是static方法,则调用这些方法的字节码为LOP_INVOKE_STATIC(_RANGE)。在解析方法时会传入METHOD_STATIC作为参数。

如果是super修饰的语句则会调用LOP_INVOKE_SUPER(_RANGE)。在解析时仍然会传入METHOD_VIRTUAL

如果是一个接口引用调用方法,则调用这些方法的字节码为LOP_INVOKE_INTERFACE(_RANGE)。解析方法会调用dvmInterpFindInterfaceMethodInCache

如果是私有方法,应该就是调用LOP_INVOKE_DIRECT(_RANGE)。在解析方法时会传入METHOD_DIRECT作为参数。

 

七.方法的执行时机

static初始化方法的执行时机是在类被加载时执行,这是java语言的规范,不是字节码讨论的重点。

finalize是实例方法不是静态方法,是在heapworker线程做清理工作时执行的这是gc关心的重点,具体请看[2],不是字节码讨论的重点。

 

八.与GC的配合

       在字节码指令向回跳转或方法返回时会做一次检查(代码段为_common_periodicChecks),查看否需要挂起线程等,挂起线程的请求就是在这里响应的。

       gc之前会调用dvmSuspendAllThreads方法(vm/thread.c)。它要求每个线程挂起,并等待直到所有的线程都挂起后再返回。

 

 

转自http://blog.csdn.net/bazookier/archive/2009/08/09/4427441.aspx

原创粉丝点击