blob: 1d0618886ee8bb8ce6f688bfcfb63596e51ec3ab [file] [log] [blame]
/* 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]++;
}
}