/* * Find the return type in the signature, and convert it to a class * object. For primitive types we use a boxed class, for reference types * we do a name lookup. * * On failure, we return NULL with an exception raised. */ ClassObject* dvmGetBoxedReturnType(const Method* meth) { const char* sig = dexProtoGetReturnType(&meth->prototype); switch (*sig) { case 'Z': case 'C': case 'F': case 'D': case 'B': case 'S': case 'I': case 'J': case 'V': return dvmFindPrimitiveClass(*sig); case '[': case 'L': return dvmFindClass(sig, meth->clazz->classLoader); default: { /* should not have passed verification */ char* desc = dexProtoCopyMethodDescriptor(&meth->prototype); ALOGE("Bad return type in signature '%s'", desc); free(desc); dvmThrowInternalError(NULL); return NULL; } } }
/* * Populate the methods table on first use. It's possible the class * hasn't been resolved yet, so we need to do the full "calling the * method for the first time" routine. (It's probably okay to skip * the access checks.) * * Currently assuming that we're only inlining stuff loaded by the * bootstrap class loader. This is a safe assumption for many reasons. */ Method* dvmResolveInlineNative(int opIndex) { assert(opIndex >= 0 && opIndex < NELEM(gDvmInlineOpsTable)); Method* method = gDvm.inlinedMethods[opIndex]; if (method != NULL) { return method; } method = dvmFindInlinableMethod( gDvmInlineOpsTable[opIndex].classDescriptor, gDvmInlineOpsTable[opIndex].methodName, gDvmInlineOpsTable[opIndex].methodSignature); if (method == NULL) { /* We already reported the error. */ return NULL; } gDvm.inlinedMethods[opIndex] = method; IF_ALOGV() { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); ALOGV("Registered for profile: %s.%s %s", method->clazz->descriptor, method->name, desc); free(desc); } return method; }
/* * "Mterp entry point. */ void dvmMterpStd(Thread* self) { /* configure mterp items */ self->interpSave.methodClassDex = self->interpSave.method->clazz->pDvmDex; IF_LOGVV() { char* desc = dexProtoCopyMethodDescriptor( &self->interpSave.method->prototype); LOGVV("mterp threadid=%d : %s.%s %s", dvmThreadSelf()->threadId, self->interpSave.method->clazz->descriptor, self->interpSave.method->name, desc); free(desc); } //ALOGI("self is %p, pc=%p, fp=%p", self, self->interpSave.pc, // self->interpSave.curFrame); //ALOGI("first instruction is 0x%04x", self->interpSave.pc[0]); /* * Handle any ongoing profiling and prep for debugging */ if (self->interpBreak.ctl.subMode != 0) { TRACE_METHOD_ENTER(self, self->interpSave.method); self->debugIsMethodEntry = true; // Always true on startup } dvmMterpStdRun(self); #ifdef LOG_INSTR ALOGD("|-- Leaving interpreter loop"); #endif }
/* * Resolve a native method and invoke it. * * This is executed as if it were a native bridge or function. If the * resolution succeeds, method->insns is replaced, and we don't go through * here again. * * Initializes method's class if necessary. * * An exception is thrown on resolution failure. */ void dvmResolveNativeMethod(const u4* args, JValue* pResult, const Method* method, Thread* self) { ClassObject* clazz = method->clazz; void* func; /* * If this is a static method, it could be called before the class * has been initialized. */ if (dvmIsStaticMethod(method)) { if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) { assert(dvmCheckException(dvmThreadSelf())); return; } } else { assert(dvmIsClassInitialized(clazz) || dvmIsClassInitializing(clazz)); } /* start with our internal-native methods */ func = dvmLookupInternalNativeMethod(method); if (func != NULL) { /* resolution always gets the same answer, so no race here */ IF_LOGVV() { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGVV("+++ resolved native %s.%s %s, invoking\n", clazz->descriptor, method->name, desc); free(desc); }
static void invalidStream(const char *classDescriptor, const DexProto* proto) { // IF_ALOGE() { char* methodDescriptor = dexProtoCopyMethodDescriptor(proto); ALOGE("Invalid debug info stream. class %s; proto %s", classDescriptor, methodDescriptor); free(methodDescriptor); // } }
/* * "Standard" mterp entry point. This sets up a "glue" structure and then * calls into the assembly interpreter implementation. * * (There is presently no "debug" entry point.) */ bool dvmMterpStd(Thread* self, InterpState* glue) { int changeInterp; /* configure mterp items */ glue->self = self; glue->methodClassDex = glue->method->clazz->pDvmDex; glue->interpStackEnd = self->interpStackEnd; glue->pSelfSuspendCount = &self->suspendCount; glue->cardTable = gDvm.biasedCardTableBase; #if defined(WITH_JIT) glue->pJitProfTable = gDvmJit.pProfTable; glue->ppJitProfTable = &gDvmJit.pProfTable; glue->jitThreshold = gDvmJit.threshold; #endif if (gDvm.jdwpConfigured) { glue->pDebuggerActive = &gDvm.debuggerActive; } else { glue->pDebuggerActive = NULL; } glue->pActiveProfilers = &gDvm.activeProfilers; IF_LOGVV() { char* desc = dexProtoCopyMethodDescriptor(&glue->method->prototype); LOGVV("mterp threadid=%d entry %d: %s.%s %s\n", dvmThreadSelf()->threadId, glue->entryPoint, glue->method->clazz->descriptor, glue->method->name, desc); free(desc); } //LOGI("glue is %p, pc=%p, fp=%p\n", glue, glue->pc, glue->fp); //LOGI("first instruction is 0x%04x\n", glue->pc[0]); changeInterp = dvmMterpStdRun(glue); #if defined(WITH_JIT) if (glue->jitState != kJitSingleStep) { glue->self->inJitCodeCache = NULL; } #endif if (!changeInterp) { /* this is a "normal" exit; we're not coming back */ #ifdef LOG_INSTR LOGD("|-- Leaving interpreter loop"); #endif return false; } else { /* we're "standard", so switch to "debug" */ LOGVV(" mterp returned, changeInterp=%d\n", changeInterp); glue->nextMode = INTERP_DBG; return true; } }
/* * Make an inline call for the "debug" interpreter, used when the debugger * or profiler is active. */ bool dvmPerformInlineOp4Dbg(u4 arg0, u4 arg1, u4 arg2, u4 arg3, JValue* pResult, int opIndex) { Thread* self = dvmThreadSelf(); bool result; assert(opIndex >= 0 && opIndex < NELEM(gDvmInlineOpsTable)); #ifdef WITH_PROFILER /* * Populate the methods table on first use. It's possible the class * hasn't been resolved yet, so we need to do the full "calling the * method for the first time" routine. (It's probably okay to skip * the access checks.) * * Currently assuming that we're only inlining stuff loaded by the * bootstrap class loader. This is a safe assumption for many reasons. */ Method* method = gDvm.inlinedMethods[opIndex]; if (method == NULL) { ClassObject* clazz; clazz = dvmFindClassNoInit( gDvmInlineOpsTable[opIndex].classDescriptor, NULL); if (clazz == NULL) { LOGW("Warning: can't find class '%s'\n", clazz->descriptor); goto skip_prof; } method = dvmFindDirectMethodByDescriptor(clazz, gDvmInlineOpsTable[opIndex].methodName, gDvmInlineOpsTable[opIndex].methodSignature); if (method == NULL) method = dvmFindVirtualMethodByDescriptor(clazz, gDvmInlineOpsTable[opIndex].methodName, gDvmInlineOpsTable[opIndex].methodSignature); if (method == NULL) { LOGW("Warning: can't find method %s.%s %s\n", clazz->descriptor, gDvmInlineOpsTable[opIndex].methodName, gDvmInlineOpsTable[opIndex].methodSignature); goto skip_prof; } gDvm.inlinedMethods[opIndex] = method; IF_LOGV() { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGV("Registered for profile: %s.%s %s\n", method->clazz->descriptor, method->name, desc); free(desc); } }
std::string dvmHumanReadableMethod(const Method* method, bool withSignature) { if (method == NULL) { return "(null)"; } std::string result(dvmHumanReadableDescriptor(method->clazz->descriptor)); result += '.'; result += method->name; if (withSignature) { // TODO: the types in this aren't human readable! char* signature = dexProtoCopyMethodDescriptor(&method->prototype); result += signature; free(signature); } return result; }
/* * Output a code verifier warning message. For the pre-verifier it's not * a big deal if something fails (and it may even be expected), but if * we're doing just-in-time verification it's significant. */ void dvmLogVerifyFailure(const Method *meth, const char *format, ...) { va_list ap; int logLevel; if (gDvm.optimizing) { return; //logLevel = ANDROID_LOG_DEBUG; } else { logLevel = ANDROID_LOG_WARN; } va_start(ap, format); LOG_PRI_VA(logLevel, LOG_TAG, format, ap); if (meth != NULL) { char *desc = dexProtoCopyMethodDescriptor(&meth->prototype); LOG_PRI(logLevel, LOG_TAG, "VFY: rejected %s.%s %s", meth->clazz->descriptor, meth->name, desc); free(desc); } }
/* * Does the bulk of the work for common_printMethod(). */ void dvmMterpPrintMethod(Method* method) { /* * It is a direct (non-virtual) method if it is static, private, * or a constructor. */ bool isDirect = ((method->accessFlags & (ACC_STATIC|ACC_PRIVATE)) != 0) || (method->name[0] == '<'); char* desc = dexProtoCopyMethodDescriptor(&method->prototype); printf("<%c:%s.%s %s> ", isDirect ? 'D' : 'V', method->clazz->descriptor, method->name, desc); free(desc); }
// TODO optimize localCb == NULL case void dexDecodeDebugInfo( const DexFile* pDexFile, const DexCode* pCode, const char* classDescriptor, u4 protoIdx, u4 accessFlags, DexDebugNewPositionCb posCb, DexDebugNewLocalCb localCb, void* cnxt) { const u1 *stream = dexGetDebugInfoStream(pDexFile, pCode); u4 line; u4 parametersSize; u4 address = 0; LocalInfo localInReg[pCode->registersSize]; u4 insnsSize = pCode->insnsSize; DexProto proto = { pDexFile, protoIdx }; memset(localInReg, 0, sizeof(LocalInfo) * pCode->registersSize); if (stream == NULL) { goto end; } line = readUnsignedLeb128(&stream); parametersSize = readUnsignedLeb128(&stream); u2 argReg = pCode->registersSize - pCode->insSize; if ((accessFlags & ACC_STATIC) == 0) { /* * The code is an instance method, which means that there is * an initial this parameter. Also, the proto list should * contain exactly one fewer argument word than the insSize * indicates. */ assert(pCode->insSize == (dexProtoComputeArgsSize(&proto) + 1)); localInReg[argReg].name = "this"; localInReg[argReg].descriptor = classDescriptor; localInReg[argReg].startAddress = 0; localInReg[argReg].live = true; argReg++; } else { assert(pCode->insSize == dexProtoComputeArgsSize(&proto)); } DexParameterIterator iterator; dexParameterIteratorInit(&iterator, &proto); while (parametersSize-- != 0) { const char* descriptor = dexParameterIteratorNextDescriptor(&iterator); const char *name; int reg; if ((argReg >= pCode->registersSize) || (descriptor == NULL)) { goto invalid_stream; } name = readStringIdx(pDexFile, &stream); reg = argReg; switch (descriptor[0]) { case 'D': case 'J': argReg += 2; break; default: argReg += 1; break; } if (name != NULL) { localInReg[reg].name = name; localInReg[reg].descriptor = descriptor; localInReg[reg].signature = NULL; localInReg[reg].startAddress = address; localInReg[reg].live = true; } } for (;;) { u1 opcode = *stream++; u2 reg; switch (opcode) { case DBG_END_SEQUENCE: goto end; case DBG_ADVANCE_PC: address += readUnsignedLeb128(&stream); break; case DBG_ADVANCE_LINE: line += readSignedLeb128(&stream); break; case DBG_START_LOCAL: case DBG_START_LOCAL_EXTENDED: reg = readUnsignedLeb128(&stream); if (reg > pCode->registersSize) goto invalid_stream; // Emit what was previously there, if anything emitLocalCbIfLive (cnxt, reg, address, localInReg, localCb); localInReg[reg].name = readStringIdx(pDexFile, &stream); localInReg[reg].descriptor = readTypeIdx(pDexFile, &stream); if (opcode == DBG_START_LOCAL_EXTENDED) { localInReg[reg].signature = readStringIdx(pDexFile, &stream); } else { localInReg[reg].signature = NULL; } localInReg[reg].startAddress = address; localInReg[reg].live = true; break; case DBG_END_LOCAL: reg = readUnsignedLeb128(&stream); if (reg > pCode->registersSize) goto invalid_stream; emitLocalCbIfLive (cnxt, reg, address, localInReg, localCb); localInReg[reg].live = false; break; case DBG_RESTART_LOCAL: reg = readUnsignedLeb128(&stream); if (reg > pCode->registersSize) goto invalid_stream; if (localInReg[reg].name == NULL || localInReg[reg].descriptor == NULL) { goto invalid_stream; } /* * If the register is live, the "restart" is superfluous, * and we don't want to mess with the existing start address. */ if (!localInReg[reg].live) { localInReg[reg].startAddress = address; localInReg[reg].live = true; } break; case DBG_SET_PROLOGUE_END: case DBG_SET_EPILOGUE_BEGIN: case DBG_SET_FILE: break; default: { int adjopcode = opcode - DBG_FIRST_SPECIAL; address += adjopcode / DBG_LINE_RANGE; line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); if (posCb != NULL) { int done; done = posCb(cnxt, address, line); if (done) { // early exit goto end; } } break; } } } end: { int reg; for (reg = 0; reg < pCode->registersSize; reg++) { emitLocalCbIfLive (cnxt, reg, insnsSize, localInReg, localCb); } } return; invalid_stream: IF_LOGE() { char* methodDescriptor = dexProtoCopyMethodDescriptor(&proto); LOGE("Invalid debug info stream. class %s; proto %s", classDescriptor, methodDescriptor); free(methodDescriptor); } }
/* * 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); } }
/* * Alternate version of dvmResolveMethod(). * * Doesn't throw exceptions, and checks access on every lookup. * * On failure, returns NULL, and sets *pFailure if pFailure is not NULL. */ Method* dvmOptResolveMethod(ClassObject* referrer, u4 methodIdx, MethodType methodType, VerifyError* pFailure) { DvmDex* pDvmDex = referrer->pDvmDex; Method* resMethod; assert(methodType == METHOD_DIRECT || methodType == METHOD_VIRTUAL || methodType == METHOD_STATIC); LOGVV("--- resolving method %u (referrer=%s)", methodIdx, referrer->descriptor); resMethod = dvmDexGetResolvedMethod(pDvmDex, methodIdx); if (resMethod == NULL) { const DexMethodId* pMethodId; ClassObject* resClass; pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx); resClass = dvmOptResolveClass(referrer, pMethodId->classIdx, pFailure); if (resClass == NULL) { /* * Can't find the class that the method is a part of, or don't * have permission to access the class. */ ALOGV("DexOpt: can't find called method's class (?.%s)", dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx)); if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); } return NULL; } if (dvmIsInterfaceClass(resClass)) { /* method is part of an interface; this is wrong method for that */ ALOGW("DexOpt: method is in an interface"); if (pFailure != NULL) *pFailure = VERIFY_ERROR_GENERIC; return NULL; } /* * We need to chase up the class hierarchy to find methods defined * in super-classes. (We only want to check the current class * if we're looking for a constructor.) */ DexProto proto; dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId); if (methodType == METHOD_DIRECT) { resMethod = dvmFindDirectMethod(resClass, dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), &proto); } else { /* METHOD_STATIC or METHOD_VIRTUAL */ resMethod = dvmFindMethodHier(resClass, dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), &proto); } if (resMethod == NULL) { ALOGV("DexOpt: couldn't find method '%s'", dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx)); if (pFailure != NULL) *pFailure = VERIFY_ERROR_NO_METHOD; return NULL; } if (methodType == METHOD_STATIC) { if (!dvmIsStaticMethod(resMethod)) { ALOGD("DexOpt: wanted static, got instance for method %s.%s", resClass->descriptor, resMethod->name); if (pFailure != NULL) *pFailure = VERIFY_ERROR_CLASS_CHANGE; return NULL; } } else if (methodType == METHOD_VIRTUAL) { if (dvmIsStaticMethod(resMethod)) { ALOGD("DexOpt: wanted instance, got static for method %s.%s", resClass->descriptor, resMethod->name); if (pFailure != NULL) *pFailure = VERIFY_ERROR_CLASS_CHANGE; return NULL; } } /* see if this is a pure-abstract method */ if (dvmIsAbstractMethod(resMethod) && !dvmIsAbstractClass(resClass)) { ALOGW("DexOpt: pure-abstract method '%s' in %s", dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), resClass->descriptor); if (pFailure != NULL) *pFailure = VERIFY_ERROR_GENERIC; return NULL; } /* * Add it to the resolved table so we're faster on the next lookup. * * We can only do this for static methods if we're not in "dexopt", * because the presence of a valid value in the resolution table * implies that the class containing the static field has been * initialized. */ if (methodType != METHOD_STATIC || gDvm.optimizing) dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod); } LOGVV("--- found method %d (%s.%s)", methodIdx, resMethod->clazz->descriptor, resMethod->name); /* access allowed? */ tweakLoader(referrer, resMethod->clazz); bool allowed = dvmCheckMethodAccess(referrer, resMethod); untweakLoader(referrer, resMethod->clazz); if (!allowed) { IF_ALOGI() { char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype); ALOGI("DexOpt: illegal method access (call %s.%s %s from %s)", resMethod->clazz->descriptor, resMethod->name, desc, referrer->descriptor); free(desc); }
/* * This is the common message body for proxy methods. * * The method we're calling looks like: * public Object invoke(Object proxy, Method method, Object[] args) * * This means we have to create a Method object, box our arguments into * a new Object[] array, make the call, and unbox the return value if * necessary. */ static void proxyInvoker(const u4* args, JValue* pResult, const Method* method, Thread* self) { Object* thisObj = (Object*) args[0]; Object* methodObj = NULL; ArrayObject* argArray = NULL; Object* handler; Method* invoke; ClassObject* returnType; int hOffset; JValue invokeResult; /* * Retrieve handler object for this proxy instance. */ hOffset = dvmFindFieldOffset(thisObj->clazz, "h", "Ljava/lang/reflect/InvocationHandler;"); if (hOffset < 0) { LOGE("Unable to find 'h' in Proxy object\n"); dvmAbort(); } handler = dvmGetFieldObject(thisObj, hOffset); /* * Find the invoke() method, looking in "this"s class. (Because we * start here we don't have to convert it to a vtable index and then * index into this' vtable.) */ invoke = dvmFindVirtualMethodHierByDescriptor(handler->clazz, "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"); if (invoke == NULL) { LOGE("Unable to find invoke()\n"); dvmAbort(); } LOGV("invoke: %s.%s, this=%p, handler=%s\n", method->clazz->descriptor, method->name, thisObj, handler->clazz->descriptor); /* * Create a java.lang.reflect.Method object for this method. * * We don't want to use "method", because that's the concrete * implementation in the proxy class. We want the abstract Method * from the declaring interface. We have a pointer to it tucked * away in the "insns" field. * * TODO: this could be cached for performance. */ methodObj = dvmCreateReflectMethodObject((Method*) method->insns); if (methodObj == NULL) { assert(dvmCheckException(self)); goto bail; } /* * Determine the return type from the signature. * * TODO: this could be cached for performance. */ returnType = dvmGetBoxedReturnType(method); if (returnType == NULL) { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGE("Could not determine return type for '%s'\n", desc); free(desc); assert(dvmCheckException(self)); goto bail; } LOGV(" return type will be %s\n", returnType->descriptor); /* * Convert "args" array into Object[] array, using the method * signature to determine types. If the method takes no arguments, * we must pass null. */ argArray = boxMethodArgs(method, args+1); if (dvmCheckException(self)) goto bail; /* * Call h.invoke(proxy, method, args). * * We don't need to repackage exceptions, so if one has been thrown * just jump to the end. */ dvmCallMethod(self, invoke, handler, &invokeResult, thisObj, methodObj, argArray); if (dvmCheckException(self)) goto bail; /* * Unbox the return value. If it's the wrong type, throw a * ClassCastException. If it's a null pointer and we need a * primitive type, throw a NullPointerException. */ if (returnType->primitiveType == PRIM_VOID) { LOGVV("+++ ignoring return to void\n"); } else if (invokeResult.l == NULL) { if (dvmIsPrimitiveClass(returnType)) { dvmThrowException("Ljava/lang/NullPointerException;", "null result when primitive expected"); goto bail; } pResult->l = NULL; } else { if (!dvmUnwrapPrimitive(invokeResult.l, returnType, pResult)) { dvmThrowExceptionWithClassMessage("Ljava/lang/ClassCastException;", ((Object*)invokeResult.l)->clazz->descriptor); goto bail; } } bail: dvmReleaseTrackedAlloc(methodObj, self); dvmReleaseTrackedAlloc((Object*)argArray, self); }
IF_ALOGE() { char* methodDescriptor = dexProtoCopyMethodDescriptor(proto); ALOGE("Invalid debug info stream. class %s; proto %s", classDescriptor, methodDescriptor); free(methodDescriptor); }
/* Used to propagate taint for JNI methods * Two types of propagation: * 1) simple conservative propagation based on parameters * 2) propagation based on function profile policies */ void dvmTaintPropJniMethod(const u4* args, JValue* pResult, const Method* method, const int nativeTaint) { const DexProto* proto = &method->prototype; DexParameterIterator pIterator; int nParams = dexProtoGetParameterCount(proto); int pStart = (dvmIsStaticMethod(method)?0:1); /* index where params start */ /* Consider 3 arguments. [x] indicates return taint index * 0 1 2 [3] 4 5 6 */ int nArgs = method->insSize; u4* rtaint = (u4*) &args[nArgs]; /* The return taint value */ int tStart = nArgs+1; /* index of args[] where taint values start */ int tEnd = nArgs*2; /* index of args[] where taint values end */ u4 tag = TAINT_CLEAR; int i; #if 0 { char *desc = dexProtoCopyMethodDescriptor(proto); ALOGW("Jni: %s.%s%s, descriptor: %s", method->clazz->descriptor, method->name, dvmIsStaticMethod(method)?"[static]":"", desc); free(desc); } #endif #ifdef TAINT_JNI_LOG /* JNI logging for debugging purposes */ if (gJniLog) { u4 hash; int len; char *inStr, *outStr; len = strlen(method->clazz->descriptor) + 1 + strlen(method->name); inStr = (char*) malloc(len+1); strcpy(inStr, method->clazz->descriptor); strcat(inStr, "."); strcat(inStr, method->name); hash = dvmComputeUtf8Hash(inStr); dvmHashTableLock(gJniLogSeen); outStr = (char*) dvmHashTableLookup(gJniLogSeen, hash, inStr, (HashCompareFunc) strcmp, false); if (outStr == NULL) { /* New method! */ /* add it */ dvmHashTableLookup(gJniLogSeen, hash, inStr, (HashCompareFunc) strcmp, true); } else { free(inStr); /* don't need this anymore */ } dvmHashTableUnlock(gJniLogSeen); } #endif /* Union the taint tags, this includes object ref tags * - we don't need to worry about static vs. not static, because getting * the taint tag on the "this" object reference is a good * - we don't need to worry about wide registers, because the stack * interleaving of taint tags makes it transparent */ /*for (i = tStart; i <= tEnd; i++) { tag |= args[i]; if (args[i]!=0 && args[i]!=TAINT_ACCELEROMETER) //found a taint, and its not the accelerometer spam either ALOGD("dvmTaintPropJNIMethod found taint %08x in method=%s:%s(%s) for arg[%d]=%08x", args[i], method->clazz->descriptor, method->name, method->shorty, i-nArgs, args[i-nArgs]); }*/ /* If not static, pull any taint from the "this" object */ /*if (!dvmIsStaticMethod(method)) { tag |= getObjectTaint((Object*)args[0], method->clazz->descriptor); }*/ /* Union taint from Objects we care about */ dexParameterIteratorInit(&pIterator, proto); for (i=pStart; ; i++) { const char* desc = dexParameterIteratorNextDescriptor(&pIterator); if (desc == NULL) { break; } if (desc[0] == '[' || desc[0] == 'L') { tag |= getObjectTaint((Object*) args[i], desc); } if (desc[0] == 'J' || desc[0] == 'D') { /* wide argument, increment index one more */ i++; } } /* Look at the taint policy profiles (may have return taint) */ //tag |= propMethodProfile(args, method); /* Update return taint according to the return type */ //Native Tainting: get taint Value from native code //if (tag) { if (nativeTaint) { const char* desc = dexProtoGetReturnType(proto); //setReturnTaint(tag, rtaint, pResult, desc); ALOGD("dvmTaintPropJniMethod: setting result taint to %x", nativeTaint); setReturnTaint(nativeTaint, rtaint, pResult, desc); } }
/* * Common code for dvmCallMethodV/A and dvmInvokeMethod. * * Pushes a call frame on, advancing self->interpSave.curFrame. */ static ClassObject* callPrep(Thread* self, const Method* method, Object* obj, bool checkAccess) { ClassObject* clazz; #ifndef NDEBUG if (self->status != THREAD_RUNNING) { ALOGW("threadid=%d: status=%d on call to %s.%s -", self->threadId, self->status, method->clazz->descriptor, method->name); } #endif assert(self != NULL); assert(method != NULL); if (obj != NULL) clazz = obj->clazz; else clazz = method->clazz; //salma // __android_log_print(ANDROID_LOG_DEBUG, "DVM DEBUG","call prep: methodclazz=%s, methodname=%s", clazz->descriptor, method->name); //endsalma IF_LOGVV() { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGVV("thread=%d native code calling %s.%s %s", self->threadId, clazz->descriptor, method->name, desc); free(desc); } if (checkAccess) { /* needed for java.lang.reflect.Method.invoke */ if (!dvmCheckMethodAccess(dvmGetCaller2Class(self->interpSave.curFrame), method)) { /* note this throws IAException, not IAError */ dvmThrowIllegalAccessException("access to method denied"); return NULL; } } /* * Push a call frame on. If there isn't enough room for ins, locals, * outs, and the saved state, it will throw an exception. * * This updates self->interpSave.curFrame. */ if (dvmIsNativeMethod(method)) { /* native code calling native code the hard way */ if (!dvmPushJNIFrame(self, method)) { assert(dvmCheckException(self)); return NULL; } } else { /* native code calling interpreted code */ if (!dvmPushInterpFrame(self, method)) { assert(dvmCheckException(self)); return NULL; } } return clazz; }
/* Make sure that the HeapWorker thread hasn't spent an inordinate * amount of time inside a finalizer. * * Aborts the VM if the thread appears to be wedged. * * The caller must hold the heapWorkerLock to guarantee an atomic * read of the watchdog values. */ void dvmAssertHeapWorkerThreadRunning() { if (gDvm.gcHeap->heapWorkerCurrentObject != NULL) { static const u8 HEAP_WORKER_WATCHDOG_TIMEOUT = 10*1000*1000LL; // 10sec u8 heapWorkerInterpStartTime = gDvm.gcHeap->heapWorkerInterpStartTime; u8 now = dvmGetRelativeTimeUsec(); u8 delta = now - heapWorkerInterpStartTime; if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT && (gDvm.debuggerActive || gDvm.nativeDebuggerActive)) { /* * Debugger suspension can block the thread indefinitely. For * best results we should reset this explicitly whenever the * HeapWorker thread is resumed. Unfortunately this is also * affected by native debuggers, and we have no visibility * into how they're manipulating us. So, we ignore the * watchdog and just reset the timer. */ LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n"); gDvm.gcHeap->heapWorkerInterpStartTime = now; /* reset timer */ } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) { /* * Before we give up entirely, see if maybe we're just not * getting any CPU time because we're stuck in a background * process group. If we successfully move the thread into the * foreground we'll just leave it there (it doesn't do anything * if the process isn't GCing). */ dvmLockThreadList(NULL); Thread* thread = dvmGetThreadByHandle(gDvm.heapWorkerHandle); dvmUnlockThreadList(); if (thread != NULL) { int priChangeFlags, threadPrio; SchedPolicy threadPolicy; priChangeFlags = dvmRaiseThreadPriorityIfNeeded(thread, &threadPrio, &threadPolicy); if (priChangeFlags != 0) { LOGI("HeapWorker watchdog expired, raising priority" " and retrying\n"); gDvm.gcHeap->heapWorkerInterpStartTime = now; return; } } char* desc = dexProtoCopyMethodDescriptor( &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n", delta / 1000, gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); free(desc); dvmDumpAllThreads(true); /* try to get a debuggerd dump from the target thread */ dvmNukeThread(thread); /* abort the VM */ dvmAbort(); } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) { char* desc = dexProtoCopyMethodDescriptor( &gDvm.gcHeap->heapWorkerCurrentMethod->prototype); LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n", delta / 1000, gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor, gDvm.gcHeap->heapWorkerCurrentMethod->name, desc); free(desc); } } }