解释器汇编代码运行原理
一.解释器汇编代码的重要性以及生成方法
Dalvik 的 vm 目录是虚拟机最重要的部分,而解释更是重中之重。所有的 java 字节码都经过解释器解释执行,因此解释器的准确性和性能直接影响程序的执行。
在 Dalvik 里,有解释器的 c 语言实现的版本位于 vm 的 interp 目录下。其主要的代码在 interpcore.h 文件中。
正因为解释器对代码的执行影响太大了,所以就有了解释器的汇编语言的版本。对应不同的硬件 cpu 就有不同的汇编语言实现。它们都位于 vm 的 mterp 目录下,每个 cpu 都有自己的子目录,里面是源代码。通过 python 工具可以生成目标代码,然后参与编译。在 mterp 目录下有各个 cpu 编译的配置文件。
以 x86 为例,配置文件名为 config-x86 。使用 python 根据这个配置文件最后会生成两个文件 InterpAsm-x86.S 和 InterpC-x86.c 。同样道理,如果是 armv5te ,就是 InterpAsm-armv5te.S 和 InterpC-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<- newFP , Thread 结构体的当前帧赋为新的帧指针 */
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 ß &retval , ecx 设为函数函数返回值的地址 */
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.c 的 loadMethodFromDex 函数里:
meth->nativeFunc = dvmResolveNativeMethod;
根据 vm/native.c 又将其设置为 InternalNative.c 中的对应 c 函数。这样就把 java 本地方法和 c 函数桥接起来了。
六.一般的方法调用
方法调用以 invokeVirtual 为例:
.L_OP_INVOKE_VIRTUAL: /* 0x6e */
/* 调用方法所在的 dex 文件的第 CCCC 个方法,参数为 vD,vE,vF,vG,vA , vB 表示有几个参数 */
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 /* 得到 glue 的 method 结构指针 */
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-- ,填第 4 个 out 参数的地址 */
movl (rFP, rINST_FULL, 4), %ecx /* %ecx ß vA */
movl %ecx, (%edx) /* outs ß vA , vA 到第 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 ß vG , vG 到第 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-- ,填第 2 个 out 参数的地址 */
movl (rFP, %ecx, 4), %ecx /* %ecx ß vF */
movl %ecx, (%edx) /* outs ß vF , vF 到第 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-- ,填第 1 个 out 参数的地址 */
movl (rFP, %ecx, 4), %ecx /* ecx ß vE */
movl %ecx, (%edx) /* outs ß vE , vE 到第 1 个输出参数 */
movl LOCAL1_OFFSET(%ebp), %ecx /* %ecx<- GFED */
1:
and $0x000f, %ecx
movl (rFP, %ecx, 4), %ecx /* %ecx ß vD */
movl %ecx, -4(%edx) /* outs ß vD , vD 到第 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->interpStackEnd , glue 结构体保存的 interStackEnd 值 */
movl %edx, LOCAL2_OFFSET(%ebp) /* LOCAL2_OFFSET ß glue->interpStackEnd ,将 interpStackEnd 存入局部变量 2 中 */
shl $2, %ecx /* 取得 %ecx ß update offset for outsSize , out 参数的总大小 */
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 /* 得到 glue 的 method 结构指针 */
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) ß offset 即 AA*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