GOTO_TARGET_END GOTO_TARGET(invokeDirect, bool methodCallRange) { u2 thisReg; vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */ ref = FETCH(1); /* method ref */ vdst = FETCH(2); /* 4 regs -or- first reg */ EXPORT_PC(); if (methodCallRange) { ILOGV("|invoke-direct-range args=%d @0x%04x {regs=v%d-v%d}", vsrc1, ref, vdst, vdst+vsrc1-1); thisReg = vdst; } else { ILOGV("|invoke-direct args=%d @0x%04x {regs=0x%04x %x}", vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f); thisReg = vdst & 0x0f; } if (!checkForNull((Object*) GET_REGISTER(thisReg))) GOTO_exceptionThrown(); methodToCall = dvmDexGetResolvedMethod(methodClassDex, ref); if (methodToCall == NULL) { methodToCall = dvmResolveMethod(curMethod->clazz, ref, METHOD_DIRECT); if (methodToCall == NULL) { ILOGV("+ unknown direct method\n"); // should be impossible GOTO_exceptionThrown(); } } GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst); }
GOTO_TARGET_END GOTO_TARGET(invokeInterface, bool methodCallRange) { Object* thisPtr; ClassObject* thisClass; EXPORT_PC(); vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */ ref = FETCH(1); /* method ref */ vdst = FETCH(2); /* 4 regs -or- first reg */ /* * The object against which we are executing a method is always * in the first argument. */ if (methodCallRange) { assert(vsrc1 > 0); ILOGV("|invoke-interface-range args=%d @0x%04x {regs=v%d-v%d}", vsrc1, ref, vdst, vdst+vsrc1-1); thisPtr = (Object*) GET_REGISTER(vdst); } else { assert((vsrc1>>4) > 0); ILOGV("|invoke-interface args=%d @0x%04x {regs=0x%04x %x}", vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f); thisPtr = (Object*) GET_REGISTER(vdst & 0x0f); } if (!checkForNull(thisPtr)) GOTO_exceptionThrown(); thisClass = thisPtr->clazz; /* * Given a class and a method index, find the Method* with the * actual code we want to execute. */ methodToCall = dvmFindInterfaceMethodInCache(thisClass, ref, curMethod, methodClassDex); #if defined(WITH_JIT) && defined(MTERP_STUB) self->callsiteClass = thisClass; self->methodToCall = methodToCall; #endif if (methodToCall == NULL) { assert(dvmCheckException(self)); GOTO_exceptionThrown(); } GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst); }
/* * Check to see if "obj" is NULL. If so, export the PC into the stack * frame and throw an exception. * * Perform additional checks on debug builds. * * Use this to check for NULL when the instruction handler doesn't do * anything else that can throw an exception. */ static inline bool checkForNullExportPC(Object* obj, u4* fp, const u2* pc) { if (obj == NULL) { EXPORT_PC(); dvmThrowNullPointerException(NULL); return false; } #ifdef WITH_EXTRA_OBJECT_VALIDATION if (!dvmIsHeapAddress(obj)) { ALOGE("Invalid object %p", obj); dvmAbort(); } #endif #ifndef NDEBUG if (obj->clazz == NULL || ((u4) obj->clazz) <= 65536) { /* probable heap corruption */ ALOGE("Invalid object class %p (in %p)", obj->clazz, obj); dvmAbort(); } #endif return true; }
GOTO_TARGET_END GOTO_TARGET(invokeSuper, bool methodCallRange) { Method* baseMethod; u2 thisReg; EXPORT_PC(); vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */ ref = FETCH(1); /* method ref */ vdst = FETCH(2); /* 4 regs -or- first reg */ if (methodCallRange) { ILOGV("|invoke-super-range args=%d @0x%04x {regs=v%d-v%d}", vsrc1, ref, vdst, vdst+vsrc1-1); thisReg = vdst; } else { ILOGV("|invoke-super args=%d @0x%04x {regs=0x%04x %x}", vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f); thisReg = vdst & 0x0f; } /* impossible in well-formed code, but we must check nevertheless */ if (!checkForNull((Object*) GET_REGISTER(thisReg))) GOTO_exceptionThrown(); /* * Resolve the method. This is the correct method for the static * type of the object. We also verify access permissions here. * The first arg to dvmResolveMethod() is just the referring class * (used for class loaders and such), so we don't want to pass * the superclass into the resolution call. */ baseMethod = dvmDexGetResolvedMethod(methodClassDex, ref); if (baseMethod == NULL) { baseMethod = dvmResolveMethod(curMethod->clazz, ref,METHOD_VIRTUAL); if (baseMethod == NULL) { ILOGV("+ unknown method or access denied\n"); GOTO_exceptionThrown(); } } /* * Combine the object we found with the vtable offset in the * method's class. * * We're using the current method's class' superclass, not the * superclass of "this". This is because we might be executing * in a method inherited from a superclass, and we want to run * in that class' superclass. */ if (baseMethod->methodIndex >= curMethod->clazz->super->vtableCount) { /* * Method does not exist in the superclass. Could happen if * superclass gets updated. */ dvmThrowException("Ljava/lang/NoSuchMethodError;", baseMethod->name); GOTO_exceptionThrown(); } methodToCall = curMethod->clazz->super->vtable[baseMethod->methodIndex]; #if 0 if (dvmIsAbstractMethod(methodToCall)) { dvmThrowException("Ljava/lang/AbstractMethodError;", "abstract method not implemented"); GOTO_exceptionThrown(); } #else assert(!dvmIsAbstractMethod(methodToCall) || methodToCall->nativeFunc != NULL); #endif LOGVV("+++ base=%s.%s super-virtual=%s.%s\n", baseMethod->clazz->descriptor, baseMethod->name, methodToCall->clazz->descriptor, methodToCall->name); assert(methodToCall != NULL); GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst); }
GOTO_TARGET_END GOTO_TARGET(invokeVirtual, bool methodCallRange) { Method* baseMethod; Object* thisPtr; EXPORT_PC(); vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */ ref = FETCH(1); /* method ref */ vdst = FETCH(2); /* 4 regs -or- first reg */ /* * The object against which we are executing a method is always * in the first argument. */ if (methodCallRange) { assert(vsrc1 > 0); ILOGV("|invoke-virtual-range args=%d @0x%04x {regs=v%d-v%d}", vsrc1, ref, vdst, vdst+vsrc1-1); thisPtr = (Object*) GET_REGISTER(vdst); } else { assert((vsrc1>>4) > 0); ILOGV("|invoke-virtual args=%d @0x%04x {regs=0x%04x %x}", vsrc1 >> 4, ref, vdst, vsrc1 & 0x0f); thisPtr = (Object*) GET_REGISTER(vdst & 0x0f); } if (!checkForNull(thisPtr)) GOTO_exceptionThrown(); /* * Resolve the method. This is the correct method for the static * type of the object. We also verify access permissions here. */ baseMethod = dvmDexGetResolvedMethod(methodClassDex, ref); if (baseMethod == NULL) { baseMethod = dvmResolveMethod(curMethod->clazz, ref,METHOD_VIRTUAL); if (baseMethod == NULL) { ILOGV("+ unknown method or access denied\n"); GOTO_exceptionThrown(); } } /* * Combine the object we found with the vtable offset in the * method. */ assert(baseMethod->methodIndex < thisPtr->clazz->vtableCount); methodToCall = thisPtr->clazz->vtable[baseMethod->methodIndex]; #if 0 if (dvmIsAbstractMethod(methodToCall)) { /* * This can happen if you create two classes, Base and Sub, where * Sub is a sub-class of Base. Declare a protected abstract * method foo() in Base, and invoke foo() from a method in Base. * Base is an "abstract base class" and is never instantiated * directly. Now, Override foo() in Sub, and use Sub. This * Works fine unless Sub stops providing an implementation of * the method. */ dvmThrowException("Ljava/lang/AbstractMethodError;", "abstract method not implemented"); GOTO_exceptionThrown(); } #else assert(!dvmIsAbstractMethod(methodToCall) || methodToCall->nativeFunc != NULL); #endif LOGVV("+++ base=%s.%s virtual[%d]=%s.%s\n", baseMethod->clazz->descriptor, baseMethod->name, (u4) baseMethod->methodIndex, methodToCall->clazz->descriptor, methodToCall->name); assert(methodToCall != NULL); #if 0 if (vsrc1 != methodToCall->insSize) { LOGW("WRONG METHOD: base=%s.%s virtual[%d]=%s.%s\n", baseMethod->clazz->descriptor, baseMethod->name, (u4) baseMethod->methodIndex, methodToCall->clazz->descriptor, methodToCall->name); //dvmDumpClass(baseMethod->clazz); //dvmDumpClass(methodToCall->clazz); dvmDumpAllClasses(0); } #endif GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst); }
GOTO_TARGET(filledNewArray, bool methodCallRange) { ClassObject* arrayClass; ArrayObject* newArray; u4* contents; char typeCh; int i; u4 arg5; EXPORT_PC(); ref = FETCH(1); /* class ref */ vdst = FETCH(2); /* first 4 regs -or- range base */ if (methodCallRange) { vsrc1 = INST_AA(inst); /* #of elements */ arg5 = -1; /* silence compiler warning */ ILOGV("|filled-new-array-range args=%d @0x%04x {regs=v%d-v%d}", vsrc1, ref, vdst, vdst+vsrc1-1); } else { arg5 = INST_A(inst); vsrc1 = INST_B(inst); /* #of elements */ ILOGV("|filled-new-array args=%d @0x%04x {regs=0x%04x %x}", vsrc1, ref, vdst, arg5); } /* * Resolve the array class. */ arrayClass = dvmDexGetResolvedClass(methodClassDex, ref); if (arrayClass == NULL) { arrayClass = dvmResolveClass(curMethod->clazz, ref, false); if (arrayClass == NULL) GOTO_exceptionThrown(); } /* if (!dvmIsArrayClass(arrayClass)) { dvmThrowException("Ljava/lang/RuntimeError;", "filled-new-array needs array class"); GOTO_exceptionThrown(); } */ /* verifier guarantees this is an array class */ assert(dvmIsArrayClass(arrayClass)); assert(dvmIsClassInitialized(arrayClass)); /* * Create an array of the specified type. */ LOGVV("+++ filled-new-array type is '%s'\n", arrayClass->descriptor); typeCh = arrayClass->descriptor[1]; if (typeCh == 'D' || typeCh == 'J') { /* category 2 primitives not allowed */ dvmThrowException("Ljava/lang/RuntimeError;", "bad filled array req"); GOTO_exceptionThrown(); } else if (typeCh != 'L' && typeCh != '[' && typeCh != 'I') { /* TODO: requires multiple "fill in" loops with different widths */ LOGE("non-int primitives not implemented\n"); dvmThrowException("Ljava/lang/InternalError;", "filled-new-array not implemented for anything but 'int'"); GOTO_exceptionThrown(); } newArray = dvmAllocArrayByClass(arrayClass, vsrc1, ALLOC_DONT_TRACK); if (newArray == NULL) GOTO_exceptionThrown(); /* * Fill in the elements. It's legal for vsrc1 to be zero. */ contents = (u4*) newArray->contents; if (methodCallRange) { for (i = 0; i < vsrc1; i++) contents[i] = GET_REGISTER(vdst+i); } else { assert(vsrc1 <= 5); if (vsrc1 == 5) { contents[4] = GET_REGISTER(arg5); vsrc1--; } for (i = 0; i < vsrc1; i++) { contents[i] = GET_REGISTER(vdst & 0x0f); vdst >>= 4; } } retval.l = newArray; }
/* * Update the debugger on interesting events, such as hitting a breakpoint * or a single-step point. This is called from the top of the interpreter * loop, before the current instruction is processed. * * Set "methodEntry" if we've just entered the method. This detects * method exit by checking to see if the next instruction is "return". * * This can't catch native method entry/exit, so we have to handle that * at the point of invocation. We also need to catch it in dvmCallMethod * if we want to capture native->native calls made through JNI. * * Notes to self: * - Don't want to switch to VMWAIT while posting events to the debugger. * Let the debugger code decide if we need to change state. * - We may want to check for debugger-induced thread suspensions on * every instruction. That would make a "suspend all" more responsive * and reduce the chances of multiple simultaneous events occurring. * However, it could change the behavior some. * * TODO: method entry/exit events are probably less common than location * breakpoints. We may be able to speed things up a bit if we don't query * the event list unless we know there's at least one lurking within. */ static void updateDebugger(const Method* method, const u2* pc, const u4* fp, bool methodEntry, Thread* self) { int eventFlags = 0; /* * Update xtra.currentPc on every instruction. We need to do this if * there's a chance that we could get suspended. This can happen if * eventFlags != 0 here, or somebody manually requests a suspend * (which gets handled at PERIOD_CHECKS time). One place where this * needs to be correct is in dvmAddSingleStep(). */ EXPORT_PC(); if (methodEntry) eventFlags |= DBG_METHOD_ENTRY; /* * See if we have a breakpoint here. * * Depending on the "mods" associated with event(s) on this address, * we may or may not actually send a message to the debugger. */ #ifdef WITH_DEBUGGER if (INST_INST(*pc) == OP_BREAKPOINT) { LOGV("+++ breakpoint hit at %p\n", pc); eventFlags |= DBG_BREAKPOINT; } #endif /* * If the debugger is single-stepping one of our threads, check to * see if we're that thread and we've reached a step point. */ const StepControl* pCtrl = &gDvm.stepControl; if (pCtrl->active && pCtrl->thread == self) { int line, frameDepth; bool doStop = false; const char* msg = NULL; assert(!dvmIsNativeMethod(method)); if (pCtrl->depth == SD_INTO) { /* * Step into method calls. We break when the line number * or method pointer changes. If we're in SS_MIN mode, we * always stop. */ if (pCtrl->method != method) { doStop = true; msg = "new method"; } else if (pCtrl->size == SS_MIN) { doStop = true; msg = "new instruction"; } else if (!dvmAddressSetGet( pCtrl->pAddressSet, pc - method->insns)) { doStop = true; msg = "new line"; } } else if (pCtrl->depth == SD_OVER) { /* * Step over method calls. We break when the line number is * different and the frame depth is <= the original frame * depth. (We can't just compare on the method, because we * might get unrolled past it by an exception, and it's tricky * to identify recursion.) */ frameDepth = dvmComputeVagueFrameDepth(self, fp); if (frameDepth < pCtrl->frameDepth) { /* popped up one or more frames, always trigger */ doStop = true; msg = "method pop"; } else if (frameDepth == pCtrl->frameDepth) { /* same depth, see if we moved */ if (pCtrl->size == SS_MIN) { doStop = true; msg = "new instruction"; } else if (!dvmAddressSetGet(pCtrl->pAddressSet, pc - method->insns)) { doStop = true; msg = "new line"; } } } else { assert(pCtrl->depth == SD_OUT); /* * Return from the current method. We break when the frame * depth pops up. * * This differs from the "method exit" break in that it stops * with the PC at the next instruction in the returned-to * function, rather than the end of the returning function. */ frameDepth = dvmComputeVagueFrameDepth(self, fp); if (frameDepth < pCtrl->frameDepth) { doStop = true; msg = "method pop"; } } if (doStop) { LOGV("#####S %s\n", msg); eventFlags |= DBG_SINGLE_STEP; } } /* * Check to see if this is a "return" instruction. JDWP says we should * send the event *after* the code has been executed, but it also says * the location we provide is the last instruction. Since the "return" * instruction has no interesting side effects, we should be safe. * (We can't just move this down to the returnFromMethod label because * we potentially need to combine it with other events.) * * We're also not supposed to generate a method exit event if the method * terminates "with a thrown exception". */ u2 inst = INST_INST(FETCH(0)); if (inst == OP_RETURN_VOID || inst == OP_RETURN || inst == OP_RETURN_WIDE || inst == OP_RETURN_OBJECT) { eventFlags |= DBG_METHOD_EXIT; } /* * If there's something interesting going on, see if it matches one * of the debugger filters. */ if (eventFlags != 0) { Object* thisPtr = dvmGetThisPtr(method, fp); if (thisPtr != NULL && !dvmIsValidObject(thisPtr)) { /* * TODO: remove this check if we're confident that the "this" * pointer is where it should be -- slows us down, especially * during single-step. */ char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGE("HEY: invalid 'this' ptr %p (%s.%s %s)\n", thisPtr, method->clazz->descriptor, method->name, desc); free(desc); dvmAbort(); } dvmDbgPostLocationEvent(method, pc - method->insns, thisPtr, eventFlags); } }