| /* |
| * 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); |
| } |
| } |