/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Main interpreter entry point and support functions. * * The entry point selects the "standard" or "debug" interpreter and * facilitates switching between them. The standard interpreter may * use the "fast" or "portable" implementation. * * Some debugger support functions are included here. Ideally their * entire existence would be "#ifdef WITH_DEBUGGER", but we're not that * aggressive in other parts of the code yet. */ #include "Dalvik.h" #include "interp/InterpDefs.h" /* * =========================================================================== * Debugger support * =========================================================================== */ /* * Initialize the breakpoint address lookup table when the debugger attaches. * * This shouldn't be necessary -- the global area is initially zeroed out, * and the events should be cleaning up after themselves. */ void dvmInitBreakpoints(void) { #ifdef WITH_DEBUGGER memset(gDvm.debugBreakAddr, 0, sizeof(gDvm.debugBreakAddr)); #else assert(false); #endif } /* * Add an address to the list, putting it in the first non-empty slot. * * Sometimes the debugger likes to add two entries for one breakpoint. * We add two entries here, so that we get the right behavior when it's * removed twice. * * This will only be run from the JDWP thread, and it will happen while * we are updating the event list, which is synchronized. We're guaranteed * to be the only one adding entries, and the lock ensures that nobody * will be trying to remove them while we're in here. * * "addr" is the absolute address of the breakpoint bytecode. */ void dvmAddBreakAddr(Method* method, int instrOffset) { #ifdef WITH_DEBUGGER const u2* addr = method->insns + instrOffset; const u2** ptr = gDvm.debugBreakAddr; int i; LOGV("BKP: add %p %s.%s (%s:%d)\n", addr, method->clazz->descriptor, method->name, dvmGetMethodSourceFile(method), dvmLineNumFromPC(method, instrOffset)); method->debugBreakpointCount++; for (i = 0; i < MAX_BREAKPOINTS; i++, ptr++) { if (*ptr == NULL) { *ptr = addr; break; } } if (i == MAX_BREAKPOINTS) { /* no room; size is too small or we're not cleaning up properly */ LOGE("ERROR: max breakpoints exceeded\n"); assert(false); } #else assert(false); #endif } /* * Remove an address from the list by setting the entry to NULL. * * This can be called from the JDWP thread (because the debugger has * cancelled the breakpoint) or from an event thread (because it's a * single-shot breakpoint, e.g. "run to line"). We only get here as * the result of removing an entry from the event list, which is * synchronized, so it should not be possible for two threads to be * updating breakpoints at the same time. */ void dvmClearBreakAddr(Method* method, int instrOffset) { #ifdef WITH_DEBUGGER const u2* addr = method->insns + instrOffset; const u2** ptr = gDvm.debugBreakAddr; int i; LOGV("BKP: clear %p %s.%s (%s:%d)\n", addr, method->clazz->descriptor, method->name, dvmGetMethodSourceFile(method), dvmLineNumFromPC(method, instrOffset)); method->debugBreakpointCount--; assert(method->debugBreakpointCount >= 0); for (i = 0; i < MAX_BREAKPOINTS; i++, ptr++) { if (*ptr == addr) { *ptr = NULL; break; } } if (i == MAX_BREAKPOINTS) { /* didn't find it */ LOGE("ERROR: breakpoint on %p not found\n", addr); assert(false); } #else assert(false); #endif } /* * Add a single step event. Currently this is a global item. * * We set up some initial values based on the thread's current state. This * won't work well if the thread is running, so it's up to the caller to * verify that it's suspended. * * This is only called from the JDWP thread. */ bool dvmAddSingleStep(Thread* thread, int size, int depth) { #ifdef WITH_DEBUGGER StepControl* pCtrl = &gDvm.stepControl; if (pCtrl->active && thread != pCtrl->thread) { LOGW("WARNING: single-step active for %p; adding %p\n", pCtrl->thread, thread); /* * Keep going, overwriting previous. This can happen if you * suspend a thread in Object.wait, hit the single-step key, then * switch to another thread and do the same thing again. * The first thread's step is still pending. * * TODO: consider making single-step per-thread. Adds to the * overhead, but could be useful in rare situations. */ } pCtrl->size = size; pCtrl->depth = depth; pCtrl->thread = thread; /* * We may be stepping into or over method calls, or running until we * return from the current method. To make this work we need to track * the current line, current method, and current stack depth. We need * to be checking these after most instructions, notably those that * call methods, return from methods, or are on a different line from the * previous instruction. * * We have to start with a snapshot of the current state. If we're in * an interpreted method, everything we need is in the current frame. If * we're in a native method, possibly with some extra JNI frames pushed * on by PushLocalFrame, we want to use the topmost native method. */ const StackSaveArea* saveArea; void* fp; void* prevFp = NULL; for (fp = thread->curFrame; fp != NULL; fp = saveArea->prevFrame) { const Method* method; saveArea = SAVEAREA_FROM_FP(fp); method = saveArea->method; if (!dvmIsBreakFrame(fp) && !dvmIsNativeMethod(method)) break; prevFp = fp; } if (fp == NULL) { LOGW("Unexpected: step req in native-only threadid=%d\n", thread->threadId); return false; } if (prevFp != NULL) { /* * First interpreted frame wasn't the one at the bottom. Break * frames are only inserted when calling from native->interp, so we * don't need to worry about one being here. */ LOGV("##### init step while in native method\n"); fp = prevFp; assert(!dvmIsBreakFrame(fp)); assert(dvmIsNativeMethod(SAVEAREA_FROM_FP(fp)->method)); saveArea = SAVEAREA_FROM_FP(fp); } /* * Pull the goodies out. "xtra.currentPc" should be accurate since * we update it on every instruction while the debugger is connected. */ pCtrl->method = saveArea->method; // Clear out any old address set if (pCtrl->pAddressSet != NULL) { // (discard const) free((void *)pCtrl->pAddressSet); pCtrl->pAddressSet = NULL; } if (dvmIsNativeMethod(pCtrl->method)) { pCtrl->line = -1; } else { pCtrl->line = dvmLineNumFromPC(saveArea->method, saveArea->xtra.currentPc - saveArea->method->insns); pCtrl->pAddressSet = dvmAddressSetForLine(saveArea->method, pCtrl->line); } pCtrl->frameDepth = dvmComputeVagueFrameDepth(thread, thread->curFrame); pCtrl->active = true; LOGV("##### step init: thread=%p meth=%p '%s' line=%d frameDepth=%d depth=%s size=%s\n", pCtrl->thread, pCtrl->method, pCtrl->method->name, pCtrl->line, pCtrl->frameDepth, dvmJdwpStepDepthStr(pCtrl->depth), dvmJdwpStepSizeStr(pCtrl->size)); return true; #else assert(false); return false; #endif } /* * Disable a single step event. */ void dvmClearSingleStep(Thread* thread) { #ifdef WITH_DEBUGGER UNUSED_PARAMETER(thread); gDvm.stepControl.active = false; #else assert(false); #endif } /* * Recover the "this" pointer from the current interpreted method. "this" * is always in "in0" for non-static methods. * * The "ins" start at (#of registers - #of ins). Note in0 != v0. * * This works because "dx" guarantees that it will work. It's probably * fairly common to have a virtual method that doesn't use its "this" * pointer, in which case we're potentially wasting a register. However, * the debugger doesn't treat "this" as just another argument. For * example, events (such as breakpoints) can be enabled for specific * values of "this". There is also a separate StackFrame.ThisObject call * in JDWP that is expected to work for any non-native non-static method. * * Because we need it when setting up debugger event filters, we want to * be able to do this quickly. */ Object* dvmGetThisPtr(const Method* method, const u4* fp) { if (dvmIsStaticMethod(method)) return NULL; return (Object*)fp[method->registersSize - method->insSize]; } #if defined(WITH_TRACKREF_CHECKS) /* * Verify that all internally-tracked references have been released. If * they haven't, print them and abort the VM. * * "debugTrackedRefStart" indicates how many refs were on the list when * we were first invoked. */ void dvmInterpCheckTrackedRefs(Thread* self, const Method* method, int debugTrackedRefStart) { if (dvmReferenceTableEntries(&self->internalLocalRefTable) != (size_t) debugTrackedRefStart) { char* desc; Object** top; int count; count = dvmReferenceTableEntries(&self->internalLocalRefTable); LOGE("TRACK: unreleased internal reference (prev=%d total=%d)\n", debugTrackedRefStart, count); desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGE(" current method is %s.%s %s\n", method->clazz->descriptor, method->name, desc); free(desc); top = self->internalLocalRefTable.table + debugTrackedRefStart; while (top < self->internalLocalRefTable.nextEntry) { LOGE(" %p (%s)\n", *top, ((*top)->clazz != NULL) ? (*top)->clazz->descriptor : ""); top++; } dvmDumpThread(self, false); dvmAbort(); } //LOGI("TRACK OK\n"); } #endif #ifdef LOG_INSTR /* * Dump the v-registers. Sent to the ILOG log tag. */ void dvmDumpRegs(const Method* method, const u4* framePtr, bool inOnly) { int i, localCount; localCount = method->registersSize - method->insSize; LOG(LOG_VERBOSE, LOG_TAG"i", "Registers (fp=%p):\n", framePtr); for (i = method->registersSize-1; i >= 0; i--) { if (i >= localCount) { LOG(LOG_VERBOSE, LOG_TAG"i", " v%-2d in%-2d : 0x%08x\n", i, i-localCount, framePtr[i]); } else { if (inOnly) { LOG(LOG_VERBOSE, LOG_TAG"i", " [...]\n"); break; } const char* name = ""; int j; #if 0 // "locals" structure has changed -- need to rewrite this DexFile* pDexFile = method->clazz->pDexFile; const DexCode* pDexCode = dvmGetMethodCode(method); int localsSize = dexGetLocalsSize(pDexFile, pDexCode); const DexLocal* locals = dvmDexGetLocals(pDexFile, pDexCode); for (j = 0; j < localsSize, j++) { if (locals[j].registerNum == (u4) i) { name = dvmDexStringStr(locals[j].pName); break; } } #endif LOG(LOG_VERBOSE, LOG_TAG"i", " v%-2d : 0x%08x %s\n", i, framePtr[i], name); } } } #endif /* * =========================================================================== * Entry point and general support functions * =========================================================================== */ /* * Construct an s4 from two consecutive half-words of switch data. * This needs to check endianness because the DEX optimizer only swaps * half-words in instruction stream. * * "switchData" must be 32-bit aligned. */ #if __BYTE_ORDER == __LITTLE_ENDIAN static inline s4 s4FromSwitchData(const void* switchData) { return *(s4*) switchData; } #else static inline s4 s4FromSwitchData(const void* switchData) { u2* data = switchData; return data[0] | (((s4) data[1]) << 16); #endif /* * Find the matching case. Returns the offset to the handler instructions. * * Returns 3 if we don't find a match (it's the size of the packed-switch * instruction). */ s4 dvmInterpHandlePackedSwitch(const u2* switchData, s4 testVal) { const int kInstrLen = 3; u2 size; s4 firstKey; const s4* entries; /* * Packed switch data format: * ushort ident = 0x0100 magic value * ushort size number of entries in the table * int first_key first (and lowest) switch case value * int targets[size] branch targets, relative to switch opcode * * Total size is (4+size*2) 16-bit code units. */ if (*switchData++ != kPackedSwitchSignature) { /* should have been caught by verifier */ dvmThrowException("Ljava/lang/InternalError;", "bad packed switch magic"); return kInstrLen; } size = *switchData++; assert(size > 0); firstKey = *switchData++; firstKey |= (*switchData++) << 16; if (testVal < firstKey || testVal >= firstKey + size) { LOGVV("Value %d not found in switch (%d-%d)\n", testVal, firstKey, firstKey+size-1); return kInstrLen; } /* The entries are guaranteed to be aligned on a 32-bit boundary; * we can treat them as a native int array. */ entries = (const s4*) switchData; assert(((u4)entries & 0x3) == 0); assert(testVal - firstKey >= 0 && testVal - firstKey < size); LOGVV("Value %d found in slot %d (goto 0x%02x)\n", testVal, testVal - firstKey, s4FromSwitchData(&entries[testVal - firstKey])); return s4FromSwitchData(&entries[testVal - firstKey]); } /* * Find the matching case. Returns the offset to the handler instructions. * * Returns 3 if we don't find a match (it's the size of the sparse-switch * instruction). */ s4 dvmInterpHandleSparseSwitch(const u2* switchData, s4 testVal) { const int kInstrLen = 3; u2 ident, size; const s4* keys; const s4* entries; int i; /* * Sparse switch data format: * ushort ident = 0x0200 magic value * ushort size number of entries in the table; > 0 * int keys[size] keys, sorted low-to-high; 32-bit aligned * int targets[size] branch targets, relative to switch opcode * * Total size is (2+size*4) 16-bit code units. */ if (*switchData++ != kSparseSwitchSignature) { /* should have been caught by verifier */ dvmThrowException("Ljava/lang/InternalError;", "bad sparse switch magic"); return kInstrLen; } size = *switchData++; assert(size > 0); /* The keys are guaranteed to be aligned on a 32-bit boundary; * we can treat them as a native int array. */ keys = (const s4*) switchData; assert(((u4)keys & 0x3) == 0); /* The entries are guaranteed to be aligned on a 32-bit boundary; * we can treat them as a native int array. */ entries = keys + size; assert(((u4)entries & 0x3) == 0); /* * Run through the list of keys, which are guaranteed to * be sorted low-to-high. * * Most tables have 3-4 entries. Few have more than 10. A binary * search here is probably not useful. */ for (i = 0; i < size; i++) { s4 k = s4FromSwitchData(&keys[i]); if (k == testVal) { LOGVV("Value %d found in entry %d (goto 0x%02x)\n", testVal, i, s4FromSwitchData(&entries[i])); return s4FromSwitchData(&entries[i]); } else if (k > testVal) { break; } } LOGVV("Value %d not found in switch\n", testVal); return kInstrLen; } /* * Fill the array with predefined constant values. * * Returns true if job is completed, otherwise false to indicate that * an exception has been thrown. */ bool dvmInterpHandleFillArrayData(ArrayObject* arrayObj, const u2* arrayData) { u2 width; u4 size; if (arrayObj == NULL) { dvmThrowException("Ljava/lang/NullPointerException;", NULL); return false; } /* * Array data table format: * ushort ident = 0x0300 magic value * ushort width width of each element in the table * uint size number of elements in the table * ubyte data[size*width] table of data values (may contain a single-byte * padding at the end) * * Total size is 4+(width * size + 1)/2 16-bit code units. */ if (arrayData[0] != kArrayDataSignature) { dvmThrowException("Ljava/lang/InternalError;", "bad array data magic"); return false; } width = arrayData[1]; size = arrayData[2] | (((u4)arrayData[3]) << 16); if (size > arrayObj->length) { dvmThrowException("Ljava/lang/ArrayIndexOutOfBoundsException;", NULL); return false; } memcpy(arrayObj->contents, &arrayData[4], size*width); return true; } /* * 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); if (absMethod == NULL) { absMethod = dvmResolveInterfaceMethod(method->clazz, methodIdx); if (absMethod == NULL) { LOGV("+ unknown method\n"); return NULL; } } /* make sure absMethod->methodIndex means what we think it means */ assert(dvmIsAbstractMethod(absMethod)); /* * Run through the "this" object's iftable. Find the entry for * absMethod's class, then use absMethod->methodIndex to find * the method's entry. The value there is the offset into our * vtable of the actual method to execute. * * The verifier does not guarantee that objects stored into * interface references actually implement the interface, so this * check cannot be eliminated. */ for (i = 0; i < thisClass->iftableCount; i++) { if (thisClass->iftable[i].clazz == absMethod->clazz) break; } if (i == thisClass->iftableCount) { /* impossible in verified DEX, need to check for it in unverified */ dvmThrowException("Ljava/lang/IncompatibleClassChangeError;", "interface not implemented"); return NULL; } assert(absMethod->methodIndex < thisClass->iftable[i].clazz->virtualMethodCount); vtableIndex = thisClass->iftable[i].methodIndexArray[absMethod->methodIndex]; assert(vtableIndex >= 0 && vtableIndex < thisClass->vtableCount); methodToCall = thisClass->vtable[vtableIndex]; #if 0 /* this can happen when there's a stale class file */ if (dvmIsAbstractMethod(methodToCall)) { dvmThrowException("Ljava/lang/AbstractMethodError;", "interface method not implemented"); return NULL; } #else assert(!dvmIsAbstractMethod(methodToCall) || methodToCall->nativeFunc != NULL); #endif LOGVV("+++ interface=%s.%s concrete=%s.%s\n", absMethod->clazz->descriptor, absMethod->name, methodToCall->clazz->descriptor, methodToCall->name); assert(methodToCall != NULL); return methodToCall; } /* * Main interpreter loop entry point. Select "standard" or "debug" * interpreter and switch between them as required. * * This begins executing code at the start of "method". On exit, "pResult" * holds the return value of the method (or, if "method" returns NULL, it * holds an undefined value). * * The interpreted stack frame, which holds the method arguments, has * already been set up. */ void dvmInterpret(Thread* self, const Method* method, JValue* pResult) { InterpState interpState; bool change; #if defined(WITH_TRACKREF_CHECKS) interpState.debugTrackedRefStart = dvmReferenceTableEntries(&self->internalLocalRefTable); #endif #if defined(WITH_PROFILER) || defined(WITH_DEBUGGER) interpState.debugIsMethodEntry = true; #endif /* * Initialize working state. * * No need to initialize "retval". */ interpState.method = method; interpState.fp = (u4*) self->curFrame; interpState.pc = method->insns; interpState.entryPoint = kInterpEntryInstr; if (dvmDebuggerOrProfilerActive()) interpState.nextMode = INTERP_DBG; else interpState.nextMode = INTERP_STD; assert(!dvmIsNativeMethod(method)); /* * Make sure the class is ready to go. Shouldn't be possible to get * here otherwise. */ if (method->clazz->status < CLASS_INITIALIZING || method->clazz->status == CLASS_ERROR) { LOGE("ERROR: tried to execute code in unprepared class '%s' (%d)\n", method->clazz->descriptor, method->clazz->status); dvmDumpThread(self, false); dvmAbort(); } typedef bool (*Interpreter)(Thread*, InterpState*); Interpreter stdInterp; if (gDvm.executionMode == kExecutionModeInterpFast) stdInterp = dvmMterpStd; else stdInterp = dvmInterpretStd; change = true; while (change) { switch (interpState.nextMode) { case INTERP_STD: LOGVV("threadid=%d: interp STD\n", self->threadId); change = (*stdInterp)(self, &interpState); break; #if defined(WITH_PROFILER) || defined(WITH_DEBUGGER) case INTERP_DBG: LOGVV("threadid=%d: interp DBG\n", self->threadId); change = dvmInterpretDbg(self, &interpState); break; #endif default: dvmAbort(); } } *pResult = interpState.retval; }