| /* code in here is only included in portable-debug interpreter */ |
| |
| /* |
| * Update the debugger on interesting events, such as hitting a breakpoint |
| * or a single-step point. This is called from the top of the interpreter |
| * loop, before the current instruction is processed. |
| * |
| * Set "methodEntry" if we've just entered the method. This detects |
| * method exit by checking to see if the next instruction is "return". |
| * |
| * This can't catch native method entry/exit, so we have to handle that |
| * at the point of invocation. We also need to catch it in dvmCallMethod |
| * if we want to capture native->native calls made through JNI. |
| * |
| * Notes to self: |
| * - Don't want to switch to VMWAIT while posting events to the debugger. |
| * Let the debugger code decide if we need to change state. |
| * - We may want to check for debugger-induced thread suspensions on |
| * every instruction. That would make a "suspend all" more responsive |
| * and reduce the chances of multiple simultaneous events occurring. |
| * However, it could change the behavior some. |
| * |
| * TODO: method entry/exit events are probably less common than location |
| * breakpoints. We may be able to speed things up a bit if we don't query |
| * the event list unless we know there's at least one lurking within. |
| */ |
| static void updateDebugger(const Method* method, const u2* pc, const u4* fp, |
| bool methodEntry, Thread* self) |
| { |
| int eventFlags = 0; |
| |
| /* |
| * Update xtra.currentPc on every instruction. We need to do this if |
| * there's a chance that we could get suspended. This can happen if |
| * eventFlags != 0 here, or somebody manually requests a suspend |
| * (which gets handled at PERIOD_CHECKS time). One place where this |
| * needs to be correct is in dvmAddSingleStep(). |
| */ |
| EXPORT_PC(); |
| |
| if (methodEntry) |
| eventFlags |= DBG_METHOD_ENTRY; |
| |
| /* |
| * See if we have a breakpoint here. |
| * |
| * Depending on the "mods" associated with event(s) on this address, |
| * we may or may not actually send a message to the debugger. |
| */ |
| if (INST_INST(*pc) == OP_BREAKPOINT) { |
| LOGV("+++ breakpoint hit at %p\n", 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\n", msg); |
| eventFlags |= DBG_SINGLE_STEP; |
| } |
| } |
| |
| /* |
| * Check to see if this is a "return" instruction. JDWP says we should |
| * send the event *after* the code has been executed, but it also says |
| * the location we provide is the last instruction. Since the "return" |
| * instruction has no interesting side effects, we should be safe. |
| * (We can't just move this down to the returnFromMethod label because |
| * we potentially need to combine it with other events.) |
| * |
| * We're also not supposed to generate a method exit event if the method |
| * terminates "with a thrown exception". |
| */ |
| u2 inst = INST_INST(FETCH(0)); |
| if (inst == OP_RETURN_VOID || inst == OP_RETURN || inst == OP_RETURN_WIDE || |
| inst == OP_RETURN_OBJECT) |
| { |
| eventFlags |= DBG_METHOD_EXIT; |
| } |
| |
| /* |
| * If there's something interesting going on, see if it matches one |
| * of the debugger filters. |
| */ |
| if (eventFlags != 0) { |
| Object* thisPtr = dvmGetThisPtr(method, fp); |
| if (thisPtr != NULL && !dvmIsValidObject(thisPtr)) { |
| /* |
| * TODO: remove this check if we're confident that the "this" |
| * pointer is where it should be -- slows us down, especially |
| * during single-step. |
| */ |
| char* desc = dexProtoCopyMethodDescriptor(&method->prototype); |
| LOGE("HEY: invalid 'this' ptr %p (%s.%s %s)\n", thisPtr, |
| method->clazz->descriptor, method->name, desc); |
| free(desc); |
| dvmAbort(); |
| } |
| dvmDbgPostLocationEvent(method, pc - method->insns, thisPtr, |
| eventFlags); |
| } |
| } |
| |
| /* |
| * Perform some operations at the "top" of the interpreter loop. |
| * This stuff is required to support debugging and profiling. |
| * |
| * Using" __attribute__((noinline))" seems to do more harm than good. This |
| * is best when inlined due to the large number of parameters, most of |
| * which are local vars in the main interp loop. |
| */ |
| static void checkDebugAndProf(const u2* pc, const u4* fp, Thread* self, |
| const Method* method, bool* pIsMethodEntry) |
| { |
| /* check to see if we've run off end of method */ |
| 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 (/*gDvm.debuggerActive &&*/ |
| strcmp(method->clazz->descriptor, cd) == 0 && |
| strcmp(method->name, mn) == 0 && |
| strcmp(method->shorty, sg) == 0) |
| { |
| LOGW("Reached %s.%s, enabling verbose mode\n", |
| method->clazz->descriptor, method->name); |
| android_setMinPriority(LOG_TAG"i", ANDROID_LOG_VERBOSE); |
| dumpRegs(method, fp, true); |
| } |
| |
| if (!gDvm.debuggerActive) |
| *pIsMethodEntry = false; |
| } |
| #endif |
| |
| /* |
| * If the debugger is attached, check for events. If the profiler is |
| * enabled, update that too. |
| * |
| * This code is executed for every instruction we interpret, so for |
| * performance we use a couple of #ifdef blocks instead of runtime tests. |
| */ |
| bool isEntry = *pIsMethodEntry; |
| if (isEntry) { |
| *pIsMethodEntry = false; |
| TRACE_METHOD_ENTER(self, method); |
| } |
| if (gDvm.debuggerActive) { |
| updateDebugger(method, pc, fp, isEntry, 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). |
| */ |
| int inst = *pc & 0xff; |
| gDvm.executedInstrCounts[inst]++; |
| } |
| } |