以KK的dalvik源码为基础来解析。
使用的源码基于https://github.com/AOKP/dalvik, 可以从https://github.com/AOKP/dalvik/archive/kitkat.zip 下载。
我是在linux下,使用vim + ctags做分析的。
由于ARM架构是使用最多也是最频繁的架构,所以我分析的重点是ARM的汇编如何实现解释器的。所以我在分析过程中会忽略掉与ARM无关的代码。
//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;
......
};
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__));
/*
* 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)
};
/*
* 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);
}
循环的目的只是为了做原子同步。
#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
mov v1, v2
该字节码就是令v1 = v2。mov是opcode的助记符,v1, v2就是operand。
你需要了解基本的ARM汇编。如果不了解可以从网上了解下,只需要一些入门的知识即可。
我们所阅读的汇编代码,主要在vm/mterp/out/InterpAsm-armv7-a-neon.S文件中。
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
我们知道,mainHandlerTable对应的dvmAsmInstructionStart是从.L_OP_NONE开始的,汇编代码按照opcode的值顺序排列下来,每个标签间隔64字节,这样,就形成了一个完整的表。
altHandlerTable指向的dvmAsmAltInstructionStart,是一组以.L_ALT_OP_
/* ------------------------------ */
.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
大家请注意,上面代码最后一句: 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
#define FETCH_ADVANCE_INST(_count) ldrh rINST, [rPC, #((_count)*2)]!
该宏的等价表达式是 rINST = *(short*)(rPC += count*2)。
#define FETCH(_reg, _count) ldrh _reg, [rPC, #((_count)*2)]
#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
#define GET_VREG(_reg, _vreg) ldr _reg, [rFP, _vreg, lsl #2]
#define SET_VREG(_reg, _vreg) str _reg, [rFP, _vreg, lsl #2]
这两个是用来获取虚拟寄存器中的值到指定寄存器的函数。
/* 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对象的成员而准备的。
/* ------------------------------ */
.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 vAA, #+BBBBbbbb */
要让vAA = +BBBBbbbb值。
r3 = rINST >> 8;
r0 = *(++rPC)
r0 |= *(++rPC) << 16
ip = rINST & 255;
pc = rIBASE + ip * 64
.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
/* ------------------------------ */
.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
/* ------------------------------ */
.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寄存器中。
/* ------------------------------ */
.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
/* 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);
}
....
/* 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];
/*
* 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;
};
/*
* 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;
}
@ 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; }
add pc, pc, r2, lsl #4
巧妙的跳转到指定的位置,以便隔去那些不需要处理的分支。
.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]
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
....
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]; //完成跳转
异常分为两种,一种是系统运行中,由系统产生的异常,一种是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标签的内容)
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到了
{
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;
} else { //没有catch到
//进一步抛出异常
}
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实现。