/* * 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. */ #include "Dalvik.h" #include "interp/InterpDefs.h" #if defined(WITH_JIT) #include "interp/Jit.h" #endif /* * =========================================================================== * Debugger support * =========================================================================== */ // fwd static BreakpointSet* dvmBreakpointSetAlloc(); static void dvmBreakpointSetFree(BreakpointSet* pSet); #if defined(WITH_JIT) /* Target-specific save/restore */ extern "C" void dvmJitCalleeSave(double *saveArea); extern "C" void dvmJitCalleeRestore(double *saveArea); /* Interpreter entry points from compiled code */ extern "C" void dvmJitToInterpNormal(); extern "C" void dvmJitToInterpNoChain(); extern "C" void dvmJitToInterpPunt(); extern "C" void dvmJitToInterpSingleStep(); extern "C" void dvmJitToInterpTraceSelect(); #if defined(WITH_SELF_VERIFICATION) extern "C" void dvmJitToInterpBackwardBranch(); #endif #endif /* * Initialize global breakpoint structures. */ bool dvmBreakpointStartup() { gDvm.breakpointSet = dvmBreakpointSetAlloc(); return (gDvm.breakpointSet != NULL); } /* * Free resources. */ void dvmBreakpointShutdown() { dvmBreakpointSetFree(gDvm.breakpointSet); } /* * This represents a breakpoint inserted in the instruction stream. * * The debugger may ask us to create the same breakpoint multiple times. * We only remove the breakpoint when the last instance is cleared. */ struct Breakpoint { Method* method; /* method we're associated with */ u2* addr; /* absolute memory address */ u1 originalOpcode; /* original 8-bit opcode value */ int setCount; /* #of times this breakpoint was set */ }; /* * Set of breakpoints. */ struct BreakpointSet { /* grab lock before reading or writing anything else in here */ pthread_mutex_t lock; /* vector of breakpoint structures */ int alloc; int count; Breakpoint* breakpoints; }; /* * Initialize a BreakpointSet. Initially empty. */ static BreakpointSet* dvmBreakpointSetAlloc() { BreakpointSet* pSet = (BreakpointSet*) calloc(1, sizeof(*pSet)); dvmInitMutex(&pSet->lock); /* leave the rest zeroed -- will alloc on first use */ return pSet; } /* * Free storage associated with a BreakpointSet. */ static void dvmBreakpointSetFree(BreakpointSet* pSet) { if (pSet == NULL) return; free(pSet->breakpoints); free(pSet); } /* * Lock the breakpoint set. * * It's not currently necessary to switch to VMWAIT in the event of * contention, because nothing in here can block. However, it's possible * that the bytecode-updater code could become fancier in the future, so * we do the trylock dance as a bit of future-proofing. */ static void dvmBreakpointSetLock(BreakpointSet* pSet) { if (dvmTryLockMutex(&pSet->lock) != 0) { Thread* self = dvmThreadSelf(); ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); dvmLockMutex(&pSet->lock); dvmChangeStatus(self, oldStatus); } } /* * Unlock the breakpoint set. */ static void dvmBreakpointSetUnlock(BreakpointSet* pSet) { dvmUnlockMutex(&pSet->lock); } /* * Return the #of breakpoints. */ static int dvmBreakpointSetCount(const BreakpointSet* pSet) { return pSet->count; } /* * See if we already have an entry for this address. * * The BreakpointSet's lock must be acquired before calling here. * * Returns the index of the breakpoint entry, or -1 if not found. */ static int dvmBreakpointSetFind(const BreakpointSet* pSet, const u2* addr) { int i; for (i = 0; i < pSet->count; i++) { Breakpoint* pBreak = &pSet->breakpoints[i]; if (pBreak->addr == addr) return i; } return -1; } /* * Retrieve the opcode that was originally at the specified location. * * The BreakpointSet's lock must be acquired before calling here. * * Returns "true" with the opcode in *pOrig on success. */ static bool dvmBreakpointSetOriginalOpcode(const BreakpointSet* pSet, const u2* addr, u1* pOrig) { int idx = dvmBreakpointSetFind(pSet, addr); if (idx < 0) return false; *pOrig = pSet->breakpoints[idx].originalOpcode; return true; } /* * Check the opcode. If it's a "magic" NOP, indicating the start of * switch or array data in the instruction stream, we don't want to set * a breakpoint. * * This can happen because the line number information dx generates * associates the switch data with the switch statement's line number, * and some debuggers put breakpoints at every address associated with * a given line. The result is that the breakpoint stomps on the NOP * instruction that doubles as a data table magic number, and an explicit * check in the interpreter results in an exception being thrown. * * We don't want to simply refuse to add the breakpoint to the table, * because that confuses the housekeeping. We don't want to reject the * debugger's event request, and we want to be sure that there's exactly * one un-set operation for every set op. */ static bool instructionIsMagicNop(const u2* addr) { u2 curVal = *addr; return ((GET_OPCODE(curVal)) == OP_NOP && (curVal >> 8) != 0); } /* * Add a breakpoint at a specific address. If the address is already * present in the table, this just increments the count. * * For a new entry, this will extract and preserve the current opcode from * the instruction stream, and replace it with a breakpoint opcode. * * The BreakpointSet's lock must be acquired before calling here. * * Returns "true" on success. */ static bool dvmBreakpointSetAdd(BreakpointSet* pSet, Method* method, unsigned int instrOffset) { const int kBreakpointGrowth = 10; const u2* addr = method->insns + instrOffset; int idx = dvmBreakpointSetFind(pSet, addr); Breakpoint* pBreak; if (idx < 0) { if (pSet->count == pSet->alloc) { int newSize = pSet->alloc + kBreakpointGrowth; Breakpoint* newVec; LOGV("+++ increasing breakpoint set size to %d", newSize); /* pSet->breakpoints will be NULL on first entry */ newVec = (Breakpoint*)realloc(pSet->breakpoints, newSize * sizeof(Breakpoint)); if (newVec == NULL) return false; pSet->breakpoints = newVec; pSet->alloc = newSize; } pBreak = &pSet->breakpoints[pSet->count++]; pBreak->method = method; pBreak->addr = (u2*)addr; pBreak->originalOpcode = *(u1*)addr; pBreak->setCount = 1; /* * Change the opcode. We must ensure that the BreakpointSet * updates happen before we change the opcode. * * If the method has not been verified, we do NOT insert the * breakpoint yet, since that will screw up the verifier. The * debugger is allowed to insert breakpoints in unverified code, * but since we don't execute unverified code we don't need to * alter the bytecode yet. * * The class init code will "flush" all pending opcode writes * before verification completes. */ assert(*(u1*)addr != OP_BREAKPOINT); if (dvmIsClassVerified(method->clazz)) { LOGV("Class %s verified, adding breakpoint at %p", method->clazz->descriptor, addr); if (instructionIsMagicNop(addr)) { LOGV("Refusing to set breakpoint on %04x at %s.%s + %#x", *addr, method->clazz->descriptor, method->name, instrOffset); } else { ANDROID_MEMBAR_FULL(); dvmDexChangeDex1(method->clazz->pDvmDex, (u1*)addr, OP_BREAKPOINT); } } else { LOGV("Class %s NOT verified, deferring breakpoint at %p", method->clazz->descriptor, addr); } } else { /* * Breakpoint already exists, just increase the count. */ pBreak = &pSet->breakpoints[idx]; pBreak->setCount++; } return true; } /* * Remove one instance of the specified breakpoint. When the count * reaches zero, the entry is removed from the table, and the original * opcode is restored. * * The BreakpointSet's lock must be acquired before calling here. */ static void dvmBreakpointSetRemove(BreakpointSet* pSet, Method* method, unsigned int instrOffset) { const u2* addr = method->insns + instrOffset; int idx = dvmBreakpointSetFind(pSet, addr); if (idx < 0) { /* breakpoint not found in set -- unexpected */ if (*(u1*)addr == OP_BREAKPOINT) { LOGE("Unable to restore breakpoint opcode (%s.%s +%#x)", method->clazz->descriptor, method->name, instrOffset); dvmAbort(); } else { LOGW("Breakpoint was already restored? (%s.%s +%#x)", method->clazz->descriptor, method->name, instrOffset); } } else { Breakpoint* pBreak = &pSet->breakpoints[idx]; if (pBreak->setCount == 1) { /* * Must restore opcode before removing set entry. * * If the breakpoint was never flushed, we could be ovewriting * a value with the same value. Not a problem, though we * could end up causing a copy-on-write here when we didn't * need to. (Not worth worrying about.) */ dvmDexChangeDex1(method->clazz->pDvmDex, (u1*)addr, pBreak->originalOpcode); ANDROID_MEMBAR_FULL(); if (idx != pSet->count-1) { /* shift down */ memmove(&pSet->breakpoints[idx], &pSet->breakpoints[idx+1], (pSet->count-1 - idx) * sizeof(pSet->breakpoints[0])); } pSet->count--; pSet->breakpoints[pSet->count].addr = (u2*) 0xdecadead; // debug } else { pBreak->setCount--; assert(pBreak->setCount > 0); } } } /* * Flush any breakpoints associated with methods in "clazz". We want to * change the opcode, which might not have happened when the breakpoint * was initially set because the class was in the process of being * verified. * * The BreakpointSet's lock must be acquired before calling here. */ static void dvmBreakpointSetFlush(BreakpointSet* pSet, ClassObject* clazz) { int i; for (i = 0; i < pSet->count; i++) { Breakpoint* pBreak = &pSet->breakpoints[i]; if (pBreak->method->clazz == clazz) { /* * The breakpoint is associated with a method in this class. * It might already be there or it might not; either way, * flush it out. */ LOGV("Flushing breakpoint at %p for %s", pBreak->addr, clazz->descriptor); if (instructionIsMagicNop(pBreak->addr)) { LOGV("Refusing to flush breakpoint on %04x at %s.%s + %#x", *pBreak->addr, pBreak->method->clazz->descriptor, pBreak->method->name, pBreak->addr - pBreak->method->insns); } else { dvmDexChangeDex1(clazz->pDvmDex, (u1*)pBreak->addr, OP_BREAKPOINT); } } } } /* * Do any debugger-attach-time initialization. */ void dvmInitBreakpoints() { /* quick sanity check */ BreakpointSet* pSet = gDvm.breakpointSet; dvmBreakpointSetLock(pSet); if (dvmBreakpointSetCount(pSet) != 0) { LOGW("WARNING: %d leftover breakpoints", dvmBreakpointSetCount(pSet)); /* generally not good, but we can keep going */ } dvmBreakpointSetUnlock(pSet); } /* * 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, unsigned int instrOffset) { BreakpointSet* pSet = gDvm.breakpointSet; dvmBreakpointSetLock(pSet); dvmBreakpointSetAdd(pSet, method, instrOffset); dvmBreakpointSetUnlock(pSet); } /* * 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, unsigned int instrOffset) { BreakpointSet* pSet = gDvm.breakpointSet; dvmBreakpointSetLock(pSet); dvmBreakpointSetRemove(pSet, method, instrOffset); dvmBreakpointSetUnlock(pSet); } /* * Get the original opcode from under a breakpoint. * * On SMP hardware it's possible one core might try to execute a breakpoint * after another core has cleared it. We need to handle the case where * there's no entry in the breakpoint set. (The memory barriers in the * locks and in the breakpoint update code should ensure that, once we've * observed the absence of a breakpoint entry, we will also now observe * the restoration of the original opcode. The fact that we're holding * the lock prevents other threads from confusing things further.) */ u1 dvmGetOriginalOpcode(const u2* addr) { BreakpointSet* pSet = gDvm.breakpointSet; u1 orig = 0; dvmBreakpointSetLock(pSet); if (!dvmBreakpointSetOriginalOpcode(pSet, addr, &orig)) { orig = *(u1*)addr; if (orig == OP_BREAKPOINT) { LOGE("GLITCH: can't find breakpoint, opcode is still set"); dvmAbort(); } } dvmBreakpointSetUnlock(pSet); return orig; } /* * Flush any breakpoints associated with methods in "clazz". * * We don't want to modify the bytecode of a method before the verifier * gets a chance to look at it, so we postpone opcode replacement until * after verification completes. */ void dvmFlushBreakpoints(ClassObject* clazz) { BreakpointSet* pSet = gDvm.breakpointSet; if (pSet == NULL) return; assert(dvmIsClassVerified(clazz)); dvmBreakpointSetLock(pSet); dvmBreakpointSetFlush(pSet, clazz); dvmBreakpointSetUnlock(pSet); } /* * 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) { StepControl* pCtrl = &gDvm.stepControl; if (pCtrl->active && thread != pCtrl->thread) { LOGW("WARNING: single-step active for %p; adding %p", 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 = static_cast<JdwpStepSize>(size); pCtrl->depth = static_cast<JdwpStepDepth>(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; u4* fp; u4* prevFp = NULL; for (fp = thread->interpSave.curFrame; fp != NULL; fp = saveArea->prevFrame) { const Method* method; saveArea = SAVEAREA_FROM_FP(fp); method = saveArea->method; if (!dvmIsBreakFrame((u4*)fp) && !dvmIsNativeMethod(method)) break; prevFp = fp; } if (fp == NULL) { LOGW("Unexpected: step req in native-only threadid=%d", 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"); fp = prevFp; assert(!dvmIsBreakFrame((u4*)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->interpSave.curFrame); pCtrl->active = true; LOGV("##### step init: thread=%p meth=%p '%s' line=%d frameDepth=%d depth=%s size=%s", pCtrl->thread, pCtrl->method, pCtrl->method->name, pCtrl->line, pCtrl->frameDepth, dvmJdwpStepDepthStr(pCtrl->depth), dvmJdwpStepSizeStr(pCtrl->size)); return true; } /* * Disable a single step event. */ void dvmClearSingleStep(Thread* thread) { UNUSED_PARAMETER(thread); gDvm.stepControl.active = false; } /* * The interpreter just threw. Handle any special subMode requirements. * All interpSave state must be valid on entry. */ void dvmReportExceptionThrow(Thread* self, Object* exception) { const Method* curMethod = self->interpSave.method; #if defined(WITH_JIT) if (self->interpBreak.ctl.subMode & kSubModeJitTraceBuild) { dvmJitEndTraceSelect(self, self->interpSave.pc); } if (self->interpBreak.ctl.breakFlags & kInterpSingleStep) { /* Discard any single-step native returns to translation */ self->jitResumeNPC = NULL; } #endif if (self->interpBreak.ctl.subMode & kSubModeDebuggerActive) { void *catchFrame; int offset = self->interpSave.pc - curMethod->insns; int catchRelPc = dvmFindCatchBlock(self, offset, exception, true, &catchFrame); dvmDbgPostException(self->interpSave.curFrame, offset, catchFrame, catchRelPc, exception); } } /* * The interpreter is preparing to do an invoke (both native & normal). * Handle any special subMode requirements. All interpSave state * must be valid on entry. */ void dvmReportInvoke(Thread* self, const Method* methodToCall) { TRACE_METHOD_ENTER(self, methodToCall); } /* * The interpreter is preparing to do a native invoke. Handle any * special subMode requirements. NOTE: for a native invoke, * dvmReportInvoke() and dvmReportPreNativeInvoke() will both * be called prior to the invoke. fp is the Dalvik FP of the calling * method. */ void dvmReportPreNativeInvoke(const Method* methodToCall, Thread* self, u4* fp) { #if defined(WITH_JIT) /* * Actively building a trace? If so, end it now. The trace * builder can't follow into or through a native method. */ if (self->interpBreak.ctl.subMode & kSubModeJitTraceBuild) { dvmCheckJit(self->interpSave.pc, self); } #endif if (self->interpBreak.ctl.subMode & kSubModeDebuggerActive) { Object* thisPtr = dvmGetThisPtr(self->interpSave.method, fp); assert(thisPtr == NULL || dvmIsHeapAddress(thisPtr)); dvmDbgPostLocationEvent(methodToCall, -1, thisPtr, DBG_METHOD_ENTRY); } } /* * The interpreter has returned from a native invoke. Handle any * special subMode requirements. fp is the Dalvik FP of the calling * method. */ void dvmReportPostNativeInvoke(const Method* methodToCall, Thread* self, u4* fp) { if (self->interpBreak.ctl.subMode & kSubModeDebuggerActive) { Object* thisPtr = dvmGetThisPtr(self->interpSave.method, fp); assert(thisPtr == NULL || dvmIsHeapAddress(thisPtr)); dvmDbgPostLocationEvent(methodToCall, -1, thisPtr, DBG_METHOD_EXIT); } if (self->interpBreak.ctl.subMode & kSubModeMethodTrace) { dvmFastNativeMethodTraceExit(methodToCall, self); } } /* * The interpreter has returned from a normal method. Handle any special * subMode requirements. All interpSave state must be valid on entry. */ void dvmReportReturn(Thread* self) { TRACE_METHOD_EXIT(self, self->interpSave.method); #if defined(WITH_JIT) if (dvmIsBreakFrame(self->interpSave.curFrame) && (self->interpBreak.ctl.subMode & kSubModeJitTraceBuild)) { dvmCheckJit(self->interpSave.pc, self); } #endif } /* * 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, 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(). */ dvmExportPC(pc, fp); if (self->debugIsMethodEntry) { eventFlags |= DBG_METHOD_ENTRY; self->debugIsMethodEntry = false; } /* * 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. */ if (GET_OPCODE(*pc) == OP_BREAKPOINT) { LOGV("+++ breakpoint hit at %p", pc); eventFlags |= DBG_BREAKPOINT; } /* * 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 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", 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 opcode = GET_OPCODE(*pc); if (opcode == OP_RETURN_VOID || opcode == OP_RETURN || opcode == OP_RETURN_WIDE ||opcode == 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 && !dvmIsHeapAddress(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)", thisPtr, method->clazz->descriptor, method->name, desc); free(desc); dvmAbort(); } dvmDbgPostLocationEvent(method, pc - method->insns, thisPtr, eventFlags); } } /* * 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)", debugTrackedRefStart, count); desc = dexProtoCopyMethodDescriptor(&method->prototype); LOGE(" current method is %s.%s %s", method->clazz->descriptor, method->name, desc); free(desc); top = self->internalLocalRefTable.table + debugTrackedRefStart; while (top < self->internalLocalRefTable.nextEntry) { LOGE(" %p (%s)", *top, ((*top)->clazz != NULL) ? (*top)->clazz->descriptor : ""); top++; } dvmDumpThread(self, false); dvmAbort(); } //LOGI("TRACK OK"); } #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):", framePtr); for (i = method->registersSize-1; i >= 0; i--) { if (i >= localCount) { LOG(LOG_VERBOSE, LOG_TAG"i", " v%-2d in%-2d : 0x%08x", i, i-localCount, framePtr[i]); } else { if (inOnly) { LOG(LOG_VERBOSE, LOG_TAG"i", " [...]"); break; } const char* name = ""; #if 0 // "locals" structure has changed -- need to rewrite this int j; 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", 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 */ dvmThrowInternalError("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)", 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)", 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 size; const s4* keys; const s4* entries; /* * 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 */ dvmThrowInternalError("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); /* * Binary-search through the array of keys, which are guaranteed to * be sorted low-to-high. */ int lo = 0; int hi = size - 1; while (lo <= hi) { int mid = (lo + hi) >> 1; s4 foundVal = s4FromSwitchData(&keys[mid]); if (testVal < foundVal) { hi = mid - 1; } else if (testVal > foundVal) { lo = mid + 1; } else { LOGVV("Value %d found in entry %d (goto 0x%02x)", testVal, mid, s4FromSwitchData(&entries[mid])); return s4FromSwitchData(&entries[mid]); } } LOGVV("Value %d not found in switch", testVal); return kInstrLen; } /* * Copy data for a fill-array-data instruction. On a little-endian machine * we can just do a memcpy(), on a big-endian system we have work to do. * * The trick here is that dexopt has byte-swapped each code unit, which is * exactly what we want for short/char data. For byte data we need to undo * the swap, and for 4- or 8-byte values we need to swap pieces within * each word. */ static void copySwappedArrayData(void* dest, const u2* src, u4 size, u2 width) { #if __BYTE_ORDER == __LITTLE_ENDIAN memcpy(dest, src, size*width); #else int i; switch (width) { case 1: /* un-swap pairs of bytes as we go */ for (i = (size-1) & ~1; i >= 0; i -= 2) { ((u1*)dest)[i] = ((u1*)src)[i+1]; ((u1*)dest)[i+1] = ((u1*)src)[i]; } /* * "src" is padded to end on a two-byte boundary, but we don't want to * assume "dest" is, so we handle odd length specially. */ if ((size & 1) != 0) { ((u1*)dest)[size-1] = ((u1*)src)[size]; } break; case 2: /* already swapped correctly */ memcpy(dest, src, size*width); break; case 4: /* swap word halves */ for (i = 0; i < (int) size; i++) { ((u4*)dest)[i] = (src[(i << 1) + 1] << 16) | src[i << 1]; } break; case 8: /* swap word halves and words */ for (i = 0; i < (int) (size << 1); i += 2) { ((int*)dest)[i] = (src[(i << 1) + 3] << 16) | src[(i << 1) + 2]; ((int*)dest)[i+1] = (src[(i << 1) + 1] << 16) | src[i << 1]; } break; default: LOGE("Unexpected width %d in copySwappedArrayData", width); dvmAbort(); break; } #endif } /* * 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) { dvmThrowNullPointerException(NULL); return false; } assert (!IS_CLASS_FLAG_SET(((Object *)arrayObj)->clazz, CLASS_ISOBJECTARRAY)); /* * 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) { dvmThrowInternalError("bad array data magic"); return false; } width = arrayData[1]; size = arrayData[2] | (((u4)arrayData[3]) << 16); if (size > arrayObj->length) { dvmThrowArrayIndexOutOfBoundsException(arrayObj->length, size); return false; } copySwappedArrayData(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"); 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 */ dvmThrowIncompatibleClassChangeError("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)) { dvmThrowAbstractMethodError("interface method not implemented"); return NULL; } #else assert(!dvmIsAbstractMethod(methodToCall) || methodToCall->nativeFunc != NULL); #endif LOGVV("+++ interface=%s.%s concrete=%s.%s", absMethod->clazz->descriptor, absMethod->name, methodToCall->clazz->descriptor, methodToCall->name); assert(methodToCall != NULL); return methodToCall; } /* * Helpers for dvmThrowVerificationError(). * * Each returns a newly-allocated string. */ #define kThrowShow_accessFromClass 1 static std::string classNameFromIndex(const Method* method, int ref, VerifyErrorRefType refType, int flags) { const DvmDex* pDvmDex = method->clazz->pDvmDex; if (refType == VERIFY_ERROR_REF_FIELD) { /* get class ID from field ID */ const DexFieldId* pFieldId = dexGetFieldId(pDvmDex->pDexFile, ref); ref = pFieldId->classIdx; } else if (refType == VERIFY_ERROR_REF_METHOD) { /* get class ID from method ID */ const DexMethodId* pMethodId = dexGetMethodId(pDvmDex->pDexFile, ref); ref = pMethodId->classIdx; } const char* className = dexStringByTypeIdx(pDvmDex->pDexFile, ref); std::string dotClassName(dvmHumanReadableDescriptor(className)); if (flags == 0) { return dotClassName; } std::string result; if ((flags & kThrowShow_accessFromClass) != 0) { result += "tried to access class " + dotClassName; result += " from class " + dvmHumanReadableDescriptor(method->clazz->descriptor); } else { assert(false); // should've been caught above } return result; } static std::string fieldNameFromIndex(const Method* method, int ref, VerifyErrorRefType refType, int flags) { if (refType != VERIFY_ERROR_REF_FIELD) { LOGW("Expected ref type %d, got %d", VERIFY_ERROR_REF_FIELD, refType); return NULL; /* no message */ } const DvmDex* pDvmDex = method->clazz->pDvmDex; const DexFieldId* pFieldId = dexGetFieldId(pDvmDex->pDexFile, ref); const char* className = dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->classIdx); const char* fieldName = dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx); std::string dotName(dvmHumanReadableDescriptor(className)); if ((flags & kThrowShow_accessFromClass) != 0) { std::string result; result += "tried to access field "; result += dotName + "." + fieldName; result += " from class "; result += dvmHumanReadableDescriptor(method->clazz->descriptor); return result; } return dotName + "." + fieldName; } static std::string methodNameFromIndex(const Method* method, int ref, VerifyErrorRefType refType, int flags) { if (refType != VERIFY_ERROR_REF_METHOD) { LOGW("Expected ref type %d, got %d", VERIFY_ERROR_REF_METHOD,refType); return NULL; /* no message */ } const DvmDex* pDvmDex = method->clazz->pDvmDex; const DexMethodId* pMethodId = dexGetMethodId(pDvmDex->pDexFile, ref); const char* className = dexStringByTypeIdx(pDvmDex->pDexFile, pMethodId->classIdx); const char* methodName = dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx); std::string dotName(dvmHumanReadableDescriptor(className)); if ((flags & kThrowShow_accessFromClass) != 0) { char* desc = dexProtoCopyMethodDescriptor(&method->prototype); std::string result; result += "tried to access method "; result += dotName + "." + methodName + ":" + desc; result += " from class " + dvmHumanReadableDescriptor(method->clazz->descriptor); free(desc); return result; } return dotName + "." + methodName; } /* * Throw an exception for a problem identified by the verifier. * * This is used by the invoke-verification-error instruction. It always * throws an exception. * * "kind" indicates the kind of failure encountered by the verifier. It * has two parts, an error code and an indication of the reference type. */ void dvmThrowVerificationError(const Method* method, int kind, int ref) { int errorPart = kind & ~(0xff << kVerifyErrorRefTypeShift); int errorRefPart = kind >> kVerifyErrorRefTypeShift; VerifyError errorKind = static_cast<VerifyError>(errorPart); VerifyErrorRefType refType = static_cast<VerifyErrorRefType>(errorRefPart); ClassObject* exceptionClass = gDvm.exVerifyError; std::string msg; switch ((VerifyError) errorKind) { case VERIFY_ERROR_NO_CLASS: exceptionClass = gDvm.exNoClassDefFoundError; msg = classNameFromIndex(method, ref, refType, 0); break; case VERIFY_ERROR_NO_FIELD: exceptionClass = gDvm.exNoSuchFieldError; msg = fieldNameFromIndex(method, ref, refType, 0); break; case VERIFY_ERROR_NO_METHOD: exceptionClass = gDvm.exNoSuchMethodError; msg = methodNameFromIndex(method, ref, refType, 0); break; case VERIFY_ERROR_ACCESS_CLASS: exceptionClass = gDvm.exIllegalAccessError; msg = classNameFromIndex(method, ref, refType, kThrowShow_accessFromClass); break; case VERIFY_ERROR_ACCESS_FIELD: exceptionClass = gDvm.exIllegalAccessError; msg = fieldNameFromIndex(method, ref, refType, kThrowShow_accessFromClass); break; case VERIFY_ERROR_ACCESS_METHOD: exceptionClass = gDvm.exIllegalAccessError; msg = methodNameFromIndex(method, ref, refType, kThrowShow_accessFromClass); break; case VERIFY_ERROR_CLASS_CHANGE: exceptionClass = gDvm.exIncompatibleClassChangeError; msg = classNameFromIndex(method, ref, refType, 0); break; case VERIFY_ERROR_INSTANTIATION: exceptionClass = gDvm.exInstantiationError; msg = classNameFromIndex(method, ref, refType, 0); break; case VERIFY_ERROR_GENERIC: /* generic VerifyError; use default exception, no message */ break; case VERIFY_ERROR_NONE: /* should never happen; use default exception */ assert(false); msg = strdup("weird - no error specified"); break; /* no default clause -- want warning if enum updated */ } dvmThrowException(exceptionClass, msg.c_str()); } /* * Update interpBreak for a single thread. */ void updateInterpBreak(Thread* thread, ExecutionSubModes subMode, bool enable) { InterpBreak oldValue, newValue; do { oldValue = newValue = thread->interpBreak; newValue.ctl.breakFlags = kInterpNoBreak; // Assume full reset if (enable) newValue.ctl.subMode |= subMode; else newValue.ctl.subMode &= ~subMode; if (newValue.ctl.subMode & SINGLESTEP_BREAK_MASK) newValue.ctl.breakFlags |= kInterpSingleStep; if (newValue.ctl.subMode & SAFEPOINT_BREAK_MASK) newValue.ctl.breakFlags |= kInterpSafePoint; newValue.ctl.curHandlerTable = (newValue.ctl.breakFlags) ? thread->altHandlerTable : thread->mainHandlerTable; } while (dvmQuasiAtomicCas64(oldValue.all, newValue.all, &thread->interpBreak.all) != 0); } /* * Update interpBreak for all threads. */ void updateAllInterpBreak(ExecutionSubModes subMode, bool enable) { Thread* self = dvmThreadSelf(); Thread* thread; dvmLockThreadList(self); for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { updateInterpBreak(thread, subMode, enable); } dvmUnlockThreadList(); } /* * Update the normal and debugger suspend counts for a thread. * threadSuspendCount must be acquired before calling this to * ensure a clean update of suspendCount, dbgSuspendCount and * sumThreadSuspendCount. * * CLEANUP TODO: Currently only the JIT is using sumThreadSuspendCount. * Move under WITH_JIT ifdefs. */ void dvmAddToSuspendCounts(Thread* thread, int delta, int dbgDelta) { thread->suspendCount += delta; thread->dbgSuspendCount += dbgDelta; updateInterpBreak(thread, kSubModeSuspendPending, (thread->suspendCount != 0)); // Update the global suspend count total gDvm.sumThreadSuspendCount += delta; } void dvmDisableSubMode(Thread* thread, ExecutionSubModes subMode) { updateInterpBreak(thread, subMode, false); } void dvmEnableSubMode(Thread* thread, ExecutionSubModes subMode) { updateInterpBreak(thread, subMode, true); } void dvmEnableAllSubMode(ExecutionSubModes subMode) { updateAllInterpBreak(subMode, true); } void dvmDisableAllSubMode(ExecutionSubModes subMode) { updateAllInterpBreak(subMode, false); } /* * Do a sanity check on interpreter state saved to Thread. * A failure here doesn't necessarily mean that something is wrong, * so this code should only be used during development to suggest * a possible problem. */ void dvmCheckInterpStateConsistency() { Thread* self = dvmThreadSelf(); Thread* thread; uint8_t breakFlags; uint8_t subMode; void* handlerTable; dvmLockThreadList(self); breakFlags = self->interpBreak.ctl.breakFlags; subMode = self->interpBreak.ctl.subMode; handlerTable = self->interpBreak.ctl.curHandlerTable; for (thread = gDvm.threadList; thread != NULL; thread = thread->next) { if (subMode != thread->interpBreak.ctl.subMode) { LOGD("Warning: subMode mismatch - %#x:%#x, tid[%d]", subMode,thread->interpBreak.ctl.subMode,thread->threadId); } if (breakFlags != thread->interpBreak.ctl.breakFlags) { LOGD("Warning: breakFlags mismatch - %#x:%#x, tid[%d]", breakFlags,thread->interpBreak.ctl.breakFlags,thread->threadId); } if (handlerTable != thread->interpBreak.ctl.curHandlerTable) { LOGD("Warning: curHandlerTable mismatch - %#x:%#x, tid[%d]", (int)handlerTable,(int)thread->interpBreak.ctl.curHandlerTable, thread->threadId); } #if defined(WITH_JIT) if (thread->pJitProfTable != gDvmJit.pProfTable) { LOGD("Warning: pJitProfTable mismatch - %#x:%#x, tid[%d]", (int)thread->pJitProfTable,(int)gDvmJit.pProfTable, thread->threadId); } if (thread->jitThreshold != gDvmJit.threshold) { LOGD("Warning: jitThreshold mismatch - %#x:%#x, tid[%d]", (int)thread->jitThreshold,(int)gDvmJit.threshold, thread->threadId); } #endif } dvmUnlockThreadList(); } /* * Arm a safepoint callback for a thread. If funct is null, * clear any pending callback. * TODO: only gc is currently using this feature, and will have * at most a single outstanding callback request. Until we need * something more capable and flexible, enforce this limit. */ void dvmArmSafePointCallback(Thread* thread, SafePointCallback funct, void* arg) { dvmLockMutex(&thread->callbackMutex); if ((funct == NULL) || (thread->callback == NULL)) { thread->callback = funct; thread->callbackArg = arg; if (funct != NULL) { dvmEnableSubMode(thread, kSubModeCallbackPending); } else { dvmDisableSubMode(thread, kSubModeCallbackPending); } } else { // Already armed. Different? if ((funct != thread->callback) || (arg != thread->callbackArg)) { // Yes - report failure and die LOGE("ArmSafePointCallback failed, thread %d", thread->threadId); dvmUnlockMutex(&thread->callbackMutex); dvmAbort(); } } dvmUnlockMutex(&thread->callbackMutex); } /* * One-time initialization at thread creation. Here we initialize * useful constants. */ void dvmInitInterpreterState(Thread* self) { #if defined(WITH_JIT) /* * Reserve a static entity here to quickly setup runtime contents as * gcc will issue block copy instructions. */ static struct JitToInterpEntries jitToInterpEntries = { dvmJitToInterpNormal, dvmJitToInterpNoChain, dvmJitToInterpPunt, dvmJitToInterpSingleStep, dvmJitToInterpTraceSelect, #if defined(WITH_SELF_VERIFICATION) dvmJitToInterpBackwardBranch, #else NULL, #endif }; #endif // Begin initialization self->cardTable = gDvm.biasedCardTableBase; #if defined(WITH_JIT) // One-time initializations self->jitToInterpEntries = jitToInterpEntries; self->icRechainCount = PREDICTED_CHAIN_COUNTER_RECHAIN; self->pProfileCountdown = &gDvmJit.profileCountdown; // Jit state that can change dvmJitUpdateThreadStateSingle(self); #endif dvmInitializeInterpBreak(self); } /* * For a newly-created thread, we need to start off with interpBreak * set to any existing global modes. The caller must hold the * thread list lock. */ void dvmInitializeInterpBreak(Thread* thread) { if (gDvm.instructionCountEnableCount > 0) { dvmEnableSubMode(thread, kSubModeInstCounting); } if (dvmIsMethodTraceActive()) { dvmEnableSubMode(thread, kSubModeMethodTrace); } if (gDvm.emulatorTraceEnableCount > 0) { dvmEnableSubMode(thread, kSubModeEmulatorTrace); } if (gDvm.debuggerActive) { dvmEnableSubMode(thread, kSubModeDebuggerActive); } #if 0 // Debugging stress mode - force checkBefore dvmEnableSubMode(thread, kSubModeCheckAlways); #endif } /* * Inter-instruction handler invoked in between instruction interpretations * to handle exceptional events such as debugging housekeeping, instruction * count profiling, JIT trace building, etc. Dalvik PC has been exported * prior to call, but Thread copy of dPC & fp are not current. */ void dvmCheckBefore(const u2 *pc, u4 *fp, Thread* self) { const Method* method = self->interpSave.method; assert(self->interpBreak.ctl.breakFlags != 0); assert(pc >= method->insns && pc < method->insns + dvmGetMethodInsnsSize(method)); #if 0 /* * When we hit a specific method, enable verbose instruction logging. * Sometimes it's helpful to use the debugger attach as a trigger too. */ if (*pIsMethodEntry) { static const char* cd = "Landroid/test/Arithmetic;"; static const char* mn = "shiftTest2"; static const char* sg = "()V"; if (/*self->interpBreak.ctl.subMode & kSubModeDebuggerActive &&*/ strcmp(method->clazz->descriptor, cd) == 0 && strcmp(method->name, mn) == 0 && strcmp(method->shorty, sg) == 0) { LOGW("Reached %s.%s, enabling verbose mode", method->clazz->descriptor, method->name); android_setMinPriority(LOG_TAG"i", ANDROID_LOG_VERBOSE); dumpRegs(method, fp, true); } if (!gDvm.debuggerActive) *pIsMethodEntry = false; } #endif /* Safe point handling */ if (self->suspendCount || (self->interpBreak.ctl.subMode & kSubModeCallbackPending)) { // Are we are a safe point? int flags; flags = dexGetFlagsFromOpcode(dexOpcodeFromCodeUnit(*pc)); if (flags & (VERIFY_GC_INST_MASK & ~kInstrCanThrow)) { // Yes, at a safe point. Pending callback? if (self->interpBreak.ctl.subMode & kSubModeCallbackPending) { SafePointCallback callback; void* arg; // Get consistent funct/arg pair dvmLockMutex(&self->callbackMutex); callback = self->callback; arg = self->callbackArg; dvmUnlockMutex(&self->callbackMutex); // Update Thread structure self->interpSave.pc = pc; self->interpSave.curFrame = fp; if (callback != NULL) { // Do the callback if (!callback(self,arg)) { // disarm dvmArmSafePointCallback(self, NULL, NULL); } } } // Need to suspend? if (self->suspendCount) { dvmExportPC(pc, fp); dvmCheckSuspendPending(self); } } } if (self->interpBreak.ctl.subMode & kSubModeDebuggerActive) { updateDebugger(method, pc, fp, self); } if (gDvm.instructionCountEnableCount != 0) { /* * Count up the #of executed instructions. This isn't synchronized * for thread-safety; if we need that we should make this * thread-local and merge counts into the global area when threads * exit (perhaps suspending all other threads GC-style and pulling * the data out of them). */ gDvm.executedInstrCounts[GET_OPCODE(*pc)]++; } #if defined(WITH_TRACKREF_CHECKS) dvmInterpCheckTrackedRefs(self, method, self->interpSave.debugTrackedRefStart); #endif #if defined(WITH_JIT) // Does the JIT need anything done now? if (self->interpBreak.ctl.subMode & (kSubModeJitTraceBuild | kSubModeJitSV)) { // Are we building a trace? if (self->interpBreak.ctl.subMode & kSubModeJitTraceBuild) { dvmCheckJit(pc, self); } #if defined(WITH_SELF_VERIFICATION) // Are we replaying a trace? if (self->interpBreak.ctl.subMode & kSubModeJitSV) { dvmCheckSelfVerification(pc, self); } #endif } #endif /* * CountedStep processing. NOTE: must be the last here to allow * preceeding special case handler to manipulate single-step count. */ if (self->interpBreak.ctl.subMode & kSubModeCountedStep) { if (self->singleStepCount == 0) { // We've exhausted our single step count dvmDisableSubMode(self, kSubModeCountedStep); #if defined(WITH_JIT) #if 0 /* * For debugging. If jitResumeDPC is non-zero, then * we expect to return to a trace in progress. There * are valid reasons why we wouldn't (such as an exception * throw), but here we can keep track. */ if (self->jitResumeDPC != NULL) { if (self->jitResumeDPC == pc) { if (self->jitResumeNPC != NULL) { LOGD("SS return to trace - pc:%#x to 0x:%x", (int)pc, (int)self->jitResumeNPC); } else { LOGD("SS return to interp - pc:%#x",(int)pc); } } else { LOGD("SS failed to return. Expected %#x, now at %#x", (int)self->jitResumeDPC, (int)pc); } } #endif #if 0 // TODO - fix JIT single-stepping resume mode (b/5551114) // self->jitResumeNPC needs to be cleared in callPrep // If we've got a native return and no other reasons to // remain in singlestep/break mode, do a long jump if (self->jitResumeNPC != NULL && self->interpBreak.ctl.breakFlags == 0) { assert(self->jitResumeDPC == pc); self->jitResumeDPC = NULL; dvmJitResumeTranslation(self, pc, fp); // Doesn't return dvmAbort(); } // In case resume is blocked by non-zero breakFlags, clear // jitResumeNPC here. self->jitResumeNPC = NULL; self->jitResumeDPC = NULL; self->inJitCodeCache = NULL; #endif #endif } else { self->singleStepCount--; #if defined(WITH_JIT) if ((self->singleStepCount > 0) && (self->jitResumeNPC != NULL)) { /* * Direct return to an existing translation following a * single step is valid only if we step once. If we're * here, an additional step was added so we need to invalidate * the return to translation. */ self->jitResumeNPC = NULL; self->inJitCodeCache = NULL; } #endif } } } /* * Main interpreter loop entry point. * * 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) { InterpSaveState interpSaveState; ExecutionSubModes savedSubModes; #if defined(WITH_JIT) /* Target-specific save/restore */ double calleeSave[JIT_CALLEE_SAVE_DOUBLE_COUNT]; /* * If the previous VM left the code cache through single-stepping the * inJitCodeCache flag will be set when the VM is re-entered (for example, * in self-verification mode we single-step NEW_INSTANCE which may re-enter * the VM through findClassFromLoaderNoInit). Because of that, we cannot * assert that self->inJitCodeCache is NULL here. */ #endif /* * Save interpreter state from previous activation, linking * new to last. */ interpSaveState = self->interpSave; self->interpSave.prev = &interpSaveState; /* * Strip out and save any flags that should not be inherited by * nested interpreter activation. */ savedSubModes = (ExecutionSubModes)( self->interpBreak.ctl.subMode & LOCAL_SUBMODE); if (savedSubModes != kSubModeNormal) { dvmDisableSubMode(self, savedSubModes); } #if defined(WITH_JIT) dvmJitCalleeSave(calleeSave); #endif #if defined(WITH_TRACKREF_CHECKS) self->interpSave.debugTrackedRefStart = dvmReferenceTableEntries(&self->internalLocalRefTable); #endif self->debugIsMethodEntry = true; #if defined(WITH_JIT) dvmJitCalleeSave(calleeSave); /* Initialize the state to kJitNot */ self->jitState = kJitNot; #endif /* * Initialize working state. * * No need to initialize "retval". */ self->interpSave.method = method; self->interpSave.curFrame = (u4*) self->interpSave.curFrame; self->interpSave.pc = method->insns; 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)", method->clazz->descriptor, method->clazz->status); dvmDumpThread(self, false); dvmAbort(); } typedef void (*Interpreter)(Thread*); Interpreter stdInterp; if (gDvm.executionMode == kExecutionModeInterpFast) stdInterp = dvmMterpStd; #if defined(WITH_JIT) else if (gDvm.executionMode == kExecutionModeJit) stdInterp = dvmMterpStd; #endif else stdInterp = dvmInterpretPortable; // Call the interpreter (*stdInterp)(self); *pResult = self->interpSave.retval; /* Restore interpreter state from previous activation */ self->interpSave = interpSaveState; #if defined(WITH_JIT) dvmJitCalleeRestore(calleeSave); #endif if (savedSubModes != kSubModeNormal) { dvmEnableSubMode(self, savedSubModes); } }