卷二 Dalvik与Android源码分析 第五章 Interpreter与JIT 5.3-C解释器 5.4--汇编解释器 图书版试读--请勿转发

时间:2022-08-15 09:23:48

作者 crosskernel@gmail.com


5.3 Portable Interpreter



最初的几个andoid版本里,dalvik的解释器是用c写的。这种解释器执行速度较慢,但可读性较强,移植性好,在以后Android版本里尽管实现了汇编优化的解释器,但这种portable解释器依然存在。在Android向某个全新架构的处理器上移植时,是没有对应的汇编解释器的,这时portable的价值就体现出来了。


该解释器的核心是一个handler数组,定义如下:
#define DEFINE_GOTO_TABLE(_name) \
    static const void* _name[kNumDalvikInstructions] = {                    \

        H(OP_MOVE_WIDE_FROM16),                                               \
        H(OP_MOVE_WIDE_16),                                                   \
        H(OP_MOVE_OBJECT),                                                    \
        H(OP_MOVE_OBJECT_FROM16),                                             \
        H(OP_MOVE_OBJECT_16),                                                 \
        H(OP_MOVE_RESULT),                                                    \
        H(OP_MOVE_RESULT_WIDE),                                               \
        H(OP_MOVE_RESULT_OBJECT),                                             \
        H(OP_MOVE_EXCEPTION),    
…                                            
\ }


该数组handlerTable存放着每个操作码的handler地址,每遇到一个操作码就跳到这个数组里取出handler来执行该操作码。


而每条操作码的handler结构被定义如下:
HANDLE_OPCODE(OP_XXX)
    FINISH(…);
OP_END


其中HANDLE_OPCODE(OP_XXX)被定义成该段handler标号,handlerTable对应项指向这个地址,而每个操作码执行完,都通过FINISH(…)取出下一个操作码,然后跳入该操作码对应的handler中:


# define FINISH(_offset) {                                                  \
//将PC指向下一条字节码
        ADJUST_PC(_offset);                                                 \
//取出字节码到inst
        inst = FETCH(0);                                                   \
        …
        //“INST_INST(inst)”即为指令编号,根据这个编号索引handlerTable的位置
        goto *handlerTable[INST_INST(inst)];                                \
    }


其中,ADJUST_PC(_offset)是将操作码代码段的pc指针指向下一个操作码,FETCH(0)的作用是取出这个操作码。最后跳入下一个操作码的handler


5.4 ASM Interpreter


Dalvik默认的解释器就是这种汇编优化的解释器,根据不同CPU架构有不同的实现,本文主要讨论的是arm V7架构的实现。


5.4.1 基本结构


跟portbale解释器一样,ASM解释器也是一个由不同字节码解释器组成的大数组,这是ASM Interpreter的mainhandler,在正常运行时使用,其实现在文件dalvik/vm/mterp/out/InterpAsm-armv7-a-neon.S”中:


mainHandler大数组定义如下:
//大数组的基地址:dvmAsmInstructionStart。即为编号为零的NOP指令的的地址
dvmAsmInstructionStart = .L_OP_NOP
//代码段
    .text
/*偏移量64,每个handler 64 byte,不够的话再跳的其他地方,但要保证每个handler 64字节的入口*/ 
    .balign 64
.L_OP_NOP: /* 0x00 */
/* File: armv5te/OP_NOP.S */
  …
//64字节对齐,第二个字节码MOVE的handler
    .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
    …
.balign 64
    .size   dvmAsmInstructionStart, .-dvmAsmInstructionStart
.global dvmAsmInstructionEnd
//mainhandler数组结束
dvmAsmInstructionEnd:


对于有些字节码不能用64字节完成其handler实现,asm解释器将其余实现放在代码段:dvmAsmSisterStart里。


ALThandler
ASM Interpreter的还有一个ALThandler,在JIT和debugger时使用,其实现也在文件dalvik/vm/mterp/out/InterpAsm-armv7-a-neon.S”中:


//全局变量dvmAsmAltInstructionStart
.global dvmAsmAltInstructionStart
.type   dvmAsmAltInstructionStart, %function
//代码段
    .text
//ALThandler数组也是跟字节码指令一一对应
dvmAsmAltInstructionStart = .L_ALT_OP_NOP
//也是64字节对齐
    .balign 64
.L_ALT_OP_NOP: /* 0x00 */

    .balign 64
.L_ALT_OP_MOVE: /* 0x01 */

.balign 64
//详细分析一个ALT handler的结构,其余ALThandler类似
.L_ALT_OP_IF_GEZ: /* 0x3b */
//把线程结构的breakFlags放到r3
    ldrb   r3, [rSELF, #offThread_breakFlags]
    adrl   lr, dvmAsmInstructionStart + (59 * 64)
ldr    rIBASE, [rSELF, #offThread_curHandlerTable]
/*检查breakFlags是否为0,如为0直接跳到mainhandler,lr为mainhandler数组里对应地址*/
    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
//breakFlags被置为,需要进一步到dvmCheckBefore检查
    b      dvmCheckBefore       @ (dPC,dFP,self) tail call

.balign 64
//althander数组长度
    .size   dvmAsmAltInstructionStart, .-dvmAsmAltInstructionStart
.global dvmAsmAltInstructionEnd
//althander数组结束
dvmAsmAltInstructionEnd:




Handler的启用
在一个dalvik创建之初,在线程的管理结构里记录下该handler的地址。
static Thread* allocThread(int interpStackSize)
{   …
//“mainHandlerTable”偏移值为88,即为:“offThread_mainHandlerTable”
thread->mainHandlerTable = dvmAsmInstructionStart;
// “altHandlerTable”即为“dvmAsmAltInstructionStart;”
thread->altHandlerTable = dvmAsmAltInstructionStart;
//“interpBreak.ctl.curHandlerTable”偏移值为40,即为:“offThread_curHandlerTable”
    thread->interpBreak.ctl.curHandlerTable = thread->mainHandlerTable;
    …
}


5.4.2 运行时模型与基本操作


Asm解释器定义了专门的寄存器来对应Dalvik虚拟机模型
// rPC指向dalvik操作码的地址
#define rPC     r4
//fFP指向dalvik的帧,这是在编译时确定下来的寄存器组
#define rFP     r5
//rSELF指向当前线程的struct Thread结构。
#define rSELF   r6
// rINST为当前指令
#define rINST   r7
// rIBASE指向字节码handler大数组的基地址
#define rIBASE  r8


基本操作


//把以当前操作码为基址,偏移量为_countX2,开始的无符号半字加载到寄存器_reg中
#define FETCH(_reg, _count)     ldrh    _reg, [rPC, #((_count)*2)]


//把rPC和rFP从当前线程的struct Thread结构里取出来
#define LOAD_PC_FP_FROM_SELF()  ldmia   rSELF, {rPC, rFP}




//把以_vreg为索引的寄存器加载在_reg中,_vreg的索引值以rFP为基准
#define GET_VREG(_reg, _vreg)   ldr     _reg, [rFP, _vreg, lsl #2]
//从“struct InterpSaveState”取出字节码指令地址跟帧地址放入rPC和rFP
#define LOAD_PC_FP_FROM_SELF()  ldmia   rSELF, {rPC, rFP}


//跳到_reg字节码对应的handler,因为是64字节对齐,所以lsl#6
#define GOTO_OPCODE(_reg)       add     pc, rIBASE, _reg, lsl #6


5.4.3 ASM Interpreter入口


“dvmMterpStdRun”是解释器入口,不同的解释器有着不同的实现,对于ASM Interpreter,不仅要满足“C to ASM”调用规范,而且承接好DVM虚拟机Context, 其实现如下:


//ASM版解释器入口
dvmMterpStdRun:
#define MTERP_ENTRY1 \
    .save {r4-r10,fp,lr}; \
    stmfd   sp!, {r4-r10,fp,lr}         
#define MTERP_ENTRY2 \
    .pad    #4; \
sub     sp, sp, #4                


/*引用上文定义宏,保存r4-r10,fp,lr 寄存器*/
    .fnstart
    MTERP_ENTRY1
    MTERP_ENTRY2


    /* 保存栈指针 */
    str     sp, [r0, #offThread_bailPtr]   


    /* r0里存放当前线程的“struct Thread” */
mov     rSELF, r0                    
/* 从当前线程“struct Thread”的从“struct InterpSaveState”取出字节码地址放入fPC,帧地址放入rFP */
LOAD_PC_FP_FROM_SELF()               
/* rIBASE 就是handler数组的地址*/
    ldr     rIBASE, [rSELF, #offThread_curHandlerTable] @ set rIBASE


#if defined(WITH_JIT)
/*jit功能enable时的处理: */
.LentryInstr:
/* Entry is always a possible trace start */
/*把“struct Thread的“ pJitProfTable;”放入r0,JitProfTable是用来统计热点的阀值表*/
ldr     r0, [rSELF, #offThread_pJitProfTable]
//取出当前指令
    FETCH_INST()
    mov     r1, #0                      @ prepare the value for the new state
str     r1, [rSELF, #offThread_inJitCodeCache] @ back to the interp land
/*如果“ pJitProfTable”为零就表示没有热点检测,自然就没有jit这回事了*/
    cmp     r0,#0                       @ is profiling disabled?
#if !defined(WITH_SELF_VERIFICATION)
/*入口也是种跳转,去检测是否热点,是否需要jit*/
    bne     common_updateProfile        @ profiling is enabled
#else
    …
#endif
/*这是jit eable时有效的编译,第一条字节码已经被取出到rINST,把rINST里的指令编码取出来放到寄存器ip里 */
1:
GET_INST_OPCODE(ip)
/*跟据寄存器ip的值,跳转到第一条字节码对应的handler,至此asm interpreter启动了,以后每条字节码都会取出其后的字节码,并跳入对应的handler */
    GOTO_OPCODE(ip)
#else
    /* start executing the instruction at rPC */
    FETCH_INST()                        @ load rINST from rPC
    GET_INST_OPCODE(ip)                 @ extract opcode from rINST
    GOTO_OPCODE(ip)                     @ jump to next instruction

#endif