Dalvik虚拟机异常处理机制
来源:互联网 发布:mac电脑查看ip地址 编辑:程序博客网 时间:2024/04/19 23:20
在写这篇文章之前首先提几个问题,try catch的时候虚拟机到底做了些什么,Thread的UncaughtExceptionHandler是怎么回事?jni函数的异常是如何抛出的,又是如何被虚拟机捕获的?
我们以抛出一个异常为入口,来分析Dalvik虚拟机的异常处理机制,由于throw是关键字,执行时肯定为字节码,所以我们需要到虚拟机的解释器中查看,如果对解释器不太了解的话可以参考我之前的文章:Dalvik虚拟机线程初始化及函数执行流程
HANDLE_OPCODE(OP_THROW /*vAA*/) { Object* obj; EXPORT_PC(); vsrc1 = INST_AA(inst); obj = (Object*) GET_REGISTER(vsrc1); dvmSetException(self, obj); GOTO_exceptionThrown(); }OP_END#define EXPORT_PC() (SAVEAREA_FROM_FP(fp)->xtra.currentPc = pc)INLINE void dvmSetException(Thread* self, Object* exception) { self->exception = exception;}
这里面做了三件事,首先通过EXPORT_PC将抛出异常时的当前pc保存起来,然后获取到异常对象并设置到线程中,最后跳转到异常处理的标签处。
GOTO_TARGET(exceptionThrown) { Object* exception; int catchRelPc; catchRelPc = dvmFindCatchBlock(self, pc - curMethod->insns, exception, false, (void*)&fp); if (catchRelPc < 0) { dvmSetException(self, exception); GOTO_bail(); } curMethod = SAVEAREA_FROM_FP(fp)->method; methodClassDex = curMethod->clazz->pDvmDex; pc = curMethod->insns + catchRelPc; FINISH(0); }bail: interpState->retval = retval; return false;
这里首先通过dvmFindCatchBlock找到能catch住该异常的的handler的pc偏移,因为能catch住该异常的不一定是当前函数,可能是上层,所以这里传入fp的地址,说明寻找catch block时肯定会设置fp,指向最终能catch住该异常的函数的栈帧。通过栈帧对应的Method的insns加上这个偏移就能得到最终异常处理的handler的指令地址,然后调用FIINSH(0)开始一条条执行指令。如果没有发现能catch住该异常的block,那么整个线程的执行就终止了。
int dvmFindCatchBlock(Thread* self, int relPc, Object* exception, bool scanOnly, void** newFrame){ void* fp = self->curFrame; int catchAddr = -1; while (true) { StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); catchAddr = findCatchInMethod(self, saveArea->method, relPc, exception->clazz); if (catchAddr >= 0) break; if (dvmIsBreakFrame(saveArea->prevFrame)) { break; } else { fp = saveArea->prevFrame; relPc = saveArea->savedPc - SAVEAREA_FROM_FP(fp)->method->insns; } } self->curFrame = fp; *newFrame = fp; return catchAddr;}
这里从当前栈帧开始找起,在一个while循环中,退出条件是在当前函数中找到了catch点或者遇到了break frame。值得注意的是要不断更新relPc。因为在查找catch点的时候有一个原则,catch点必须在try block里。而这里saveArea->savedPc保存的是当前函数的返回地址,或者说是被上层函数调用的入口地址。这个地址减去insns的地址就是相对函数起始的偏移地址了。我们重点看看findCatchInMethod是怎么实现的,
static int findCatchInMethod(Thread* self, const Method* method, int relPc, ClassObject* excepClass){ DvmDex* pDvmDex = method->clazz->pDvmDex; const DexCode* pCode = dvmGetMethodCode(method); DexCatchIterator iterator; if (dexFindCatchHandler(&iterator, pCode, relPc)) { for (;;) { DexCatchHandler* handler = dexCatchIteratorNext(&iterator); if (handler == NULL) { break; } ClassObject* throwable = dvmDexGetResolvedClass(pDvmDex, handler->typeIdx); if (throwable == NULL) { throwable = dvmResolveClass(method->clazz, handler->typeIdx, true); if (throwable == NULL) { continue; } } if (dvmInstanceof(excepClass, throwable)) { return handler->address; } } } return -1;}
这里查找catch点的范围仅限于当前函数,首先获取函数的DexCode:
INLINE bool dvmIsBytecodeMethod(const Method* method) { return (method->accessFlags & (ACC_NATIVE | ACC_ABSTRACT)) == 0;}INLINE const DexCode* dvmGetMethodCode(const Method* meth) { if (dvmIsBytecodeMethod(meth)) { return (const DexCode*) (((const u1*) meth->insns) - offsetof(DexCode, insns)); } else { return NULL; }}
对于每个解释成Bytecode的函数,都有一个DexCode结构体与之对应,
typedef struct DexCode { u2 registersSize; u2 insSize; u2 outsSize; u2 triesSize; u4 debugInfoOff; /* file offset to debug info stream */ u4 insnsSize; /* size of the insns array, in u2 units */ u2 insns[1];} DexCode;
可见这里记录了该函数的参数和try个数等信息,且最后一个成员对应的就是该函数的Bytecode码数组。所以拿到了函数的insns地址,就可以通过偏移计算出DexCode的地址。再来看看dexFindCatchHandler是做什么的。
DEX_INLINE bool dexFindCatchHandler(DexCatchIterator *pIterator, const DexCode* pCode, u4 address) { u2 triesSize = pCode->triesSize; int offset = -1; switch (triesSize) { case 0: { break; } case 1: { const DexTry* tries = dexGetTries(pCode); u4 start = tries[0].startAddr; if (address < start) { break; } u4 end = start + tries[0].insnCount; if (address >= end) { break; } offset = tries[0].handlerOff; break; } default: { offset = dexFindCatchHandlerOffset0(triesSize, dexGetTries(pCode), address); } } if (offset < 0) { dexCatchIteratorClear(pIterator); // This squelches warnings. return false; } else { dexCatchIteratorInit(pIterator, pCode, offset); return true; }}
这里首先获取函数的triesSize,表示函数中try的个数,当个数是0时表示没有任何try,则直接返回false。如果是1就看看这个异常抛出点是否在try block中,如果有多个try点就调用dexFindCatchHandlerOffset0依次遍历这些try点看看到底哪个能捕获当前异常。这里offset是这个try对应的handler的偏移。因为函数中可能有多个try,每个try有多个handler。我们先看看dexGetTries的实现:
DEX_INLINE const DexTry* dexGetTries(const DexCode* pCode) { const u2* insnsEnd = &pCode->insns[pCode->insnsSize]; if ((((u4) insnsEnd) & 3) != 0) { insnsEnd++; } return (const DexTry*) insnsEnd;}
看来DexCode的insns结尾处还摆着一个DexTry数组,表示这个函数中有哪些try点。我们来看看DexTry的数据结构:
typedef struct DexTry { u4 startAddr; /* start address, in 16-bit code units */ u2 insnCount; /* instruction count, in 16-bit code units */ u2 handlerOff; /* offset in encoded handler data to handlers */} DexTry;
这个startAddr是try的起点地址,insnCount应该是try block的指令条数,handlerOff是try的handler的偏移。我们再来看看dexFindCatchHandlerOffset0的实现,如下:
int dexFindCatchHandlerOffset0(u2 triesSize, const DexTry* pTries, u4 address) { int min = 0; int max = triesSize - 1; while (max >= min) { int guess = (min + max) >> 1; const DexTry* pTry = &pTries[guess]; u4 start = pTry->startAddr; if (address < start) { max = guess - 1; continue; } u4 end = start + pTry->insnCount; if (address >= end) { min = guess + 1; continue; } return (int) pTry->handlerOff; } return -1;}
这里用二分查找法来定位异常抛出点到底在哪个catch block中,可见这个DexTry数组是按try点的起始地址排好序的。我们回到dexFindCatchHandler中,找到catch点后,要调用dexCatchIteratorInit初始化iterator,这个应该是为了之后遍历Exception的handler做准备的,因为try可能对应着若干个handler。
DEX_INLINE void dexCatchIteratorInit(DexCatchIterator* pIterator, const DexCode* pCode, u4 offset){ dexCatchIteratorInitToPointer(pIterator, dexGetCatchHandlerData(pCode) + offset);}DEX_INLINE void dexCatchIteratorInitToPointer(DexCatchIterator* pIterator, const u1* pEncodedData){ s4 count = readSignedLeb128(&pEncodedData); if (count <= 0) { pIterator->catchesAll = true; count = -count; } else { pIterator->catchesAll = false; } pIterator->pEncodedData = pEncodedData; pIterator->countRemaining = count;}DEX_INLINE const u1* dexGetCatchHandlerData(const DexCode* pCode) { const DexTry* pTries = dexGetTries(pCode); return (const u1*) &pTries[pCode->triesSize];}
这个dexGetCatchHandlerData是要获取handler的起始地址,这里每个函数的insns数组中,首先是函数的ByteCode,然后是DexTry数组,接下来是Handler。因为可能有多个try,每个try都有自己的handler地址,这里通过offset就可以得到try对应的handler地址。再来看dexCatchIteratorInitToPointer,这里要在handler地址处读取一个整数,表示该try的handler的个数。我们来看看是如何通过iterator获得DexCatchHandler的。
DEX_INLINE DexCatchHandler* dexCatchIteratorNext(DexCatchIterator* pIterator) { if (pIterator->countRemaining == 0) { if (! pIterator->catchesAll) { return NULL; } pIterator->catchesAll = false; pIterator->handler.typeIdx = kDexNoIndex; } else { u4 typeIdx = readUnsignedLeb128(&pIterator->pEncodedData); pIterator->handler.typeIdx = typeIdx; pIterator->countRemaining--; } pIterator->handler.address = readUnsignedLeb128(&pIterator->pEncodedData); return &pIterator->handler;}
这里首先读取handler的异常typeIdx,然后读取该异常处理的指令入口,设置iterator中的handler后返回。我们回到findCatchInMethod函数,拿到DexCatchHandler后,根据handler的typeIdx得到对应的异常的ClassObject,并看看这个异常是否和抛出的异常是同类型的,如果是就返回handler的指令地址,然后调用FINISH(0)从第一条指令开始执行。
至此,Dalvik虚拟机的异常处理机制我们已经走通了,其实核心就是寻找能catch住该异常的try block,当前函数找不到就跳到上层函数去找。找到之后再到try block的handlers中去找看有不有和这个异常匹配的handler,如果有就跳转到handler的指令入口处执行异常处理,否则继续跳到上层函数去找。
接下来,我们看看如果一直没有catch住这个异常会发生什么呢?我们回到exceptionThrown标签:
if (catchRelPc < 0) { dvmSetException(self, exception); GOTO_bail();}
当没有找到能捕获该异常的catch block时,就会GOTO_bail,这里其实就是退出线程执行了。我在文章Dalvik虚拟机线程初始化及函数执行流程里提到过,线程的入口函数是interpThreadStart,这个dvmCallMethod调的就是线程的run函数,最终会进入到Bytecode解释器的执行流程,当抛出异常且没有catch住时,该解释器执行就会提前返回,线程也就会退出了。
static void* interpThreadStart(Thread* self) { prepareThread(self); self->jniEnv = dvmCreateJNIEnv(self); Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run]; dvmCallMethod(self, run, self->threadObj, &unused); dvmDetachCurrentThread();}
我们来看看dvmDetachCurrentThread的实现,里面是线程结束的收尾工作。
void dvmDetachCurrentThread(void) { .......... if (dvmCheckException(self)) { threadExitUncaughtException(self, group); } ..........}
首先检查线程是否有未捕获的异常,如果有就调用threadExitUncaughtException处理该异常。
static void threadExitUncaughtException(Thread* self, Object* group){ Object* exception; Object* handlerObj; Method* uncaughtHandler = NULL; InstField* threadHandler; threadHandler = dvmFindInstanceField(gDvm.classJavaLangThread, "uncaughtHandler", "Ljava/lang/Thread$UncaughtExceptionHandler;"); if (threadHandler == NULL) { return; } handlerObj = dvmGetFieldObject(self->threadObj, threadHandler->byteOffset); if (handlerObj == NULL) handlerObj = group; uncaughtHandler = dvmFindVirtualMethodHierByDescriptor(handlerObj->clazz, "uncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V"); if (uncaughtHandler != NULL) { JValue unused; dvmCallMethod(self, uncaughtHandler, handlerObj, &unused, self->threadObj, exception); } else { dvmSetException(self, exception); dvmLogExceptionStackTrace(); }}
这里代码不少,但是做的事情很简单,就是找到uncaughtHandler对应的Method,然后调用它。
我们接下来看看如果是native中抛出了异常会如何?jni提供了接口可以用于抛出异常,如下:
static jint Throw(JNIEnv* env, jthrowable jobj) { jint retval; if (jobj != NULL) { Object* obj = dvmDecodeIndirectRef(env, jobj); dvmSetException(_self, obj); retval = JNI_OK; } else { retval = JNI_ERR; } return retval;}
可见所谓的抛出其实就是设置了一下线程的异常,那么理论上这个jni函数返回时应该检查线程是否有异常,如果有就回溯栈帧看谁能捕获该异常。如下:
GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall, u2 count, u2 regs) { u4* outs; int i; { StackSaveArea* newSaveArea; u4* newFp; newFp = (u4*) SAVEAREA_FROM_FP(fp) - methodToCall->registersSize; newSaveArea = SAVEAREA_FROM_FP(newFp); newSaveArea->prevFrame = fp; newSaveArea->savedPc = pc; newSaveArea->method = methodToCall; if (!dvmIsNativeMethod(methodToCall)) { curMethod = methodToCall; methodClassDex = curMethod->clazz->pDvmDex; pc = methodToCall->insns; fp = self->curFrame = newFp; FINISH(0); // jump to method start } else {#ifdef USE_INDIRECT_REF newSaveArea->xtra.localRefCookie = self->jniLocalRefTable.segmentState.all;#else newSaveArea->xtra.localRefCookie = self->jniLocalRefTable.nextEntry;#endif self->curFrame = newFp; (*methodToCall->nativeFunc)(newFp, &retval, methodToCall, self); dvmPopJniLocals(self, newSaveArea); self->curFrame = fp; if (dvmCheckException(self)) { GOTO_exceptionThrown(); } FINISH(3); } }GOTO_TARGET_END
果然,在Jni函数调用完毕后,会调用dvmCheckException检查是否有抛出异常,如果有就去处理异常。
到这里Dalvik虚拟机的异常处理机制就讲完了,是不是有豁然开朗的感觉。
- Dalvik虚拟机异常处理机制
- Dalvik虚拟机的优化机制
- Dalvik虚拟机垃圾收集机制
- Dalvik虚拟机的垃圾收集机制
- Dalvik虚拟机加载类的机制
- Dalvik虚拟机垃圾收集机制简要介绍
- 虚拟机异常处理
- Dalvik虚拟机
- Dalvik虚拟机
- Dalvik虚拟机
- Dalvik虚拟机
- dalvik 虚拟机
- Dalvik 虚拟机
- Dalvik虚拟机
- Dalvik 虚拟机
- Dalvik虚拟机
- Dalvik虚拟机
- Dalvik虚拟机
- 关于listview自定义滚动条和背景
- Python动态监控日志的内容
- javascript数组取值
- Environment Tree(环境树)
- hdu 5281 Senior's Gun(贪心)(思维)
- Dalvik虚拟机异常处理机制
- jsp 超链接跳转 弹出框提示是否操作
- hit-test的用法总结:如何阻止touch事件传递到子view
- 2016的路程--写给自己
- Android.mk文件分析
- Android 深入理解Android中的自定义属性
- 几个画图的js插件
- iOS开发:初识xib
- IOS-设备屏幕及适配方案