/* * Tell the debugger that we have finished initializing. This is always * sent, even if the debugger hasn't requested it. * * This should be sent "before the main thread is started and before * any application code has been executed". The thread ID in the message * must be for the main thread. */ bool dvmJdwpPostVMStart(JdwpState* state, bool suspend) { JdwpSuspendPolicy suspendPolicy; ObjectId threadId = dvmDbgGetThreadSelfId(); if (suspend) suspendPolicy = SP_ALL; else suspendPolicy = SP_NONE; /* probably don't need this here */ lockEventMutex(state); ExpandBuf* pReq = NULL; if (true) { ALOGV("EVENT: %s", dvmJdwpEventKindStr(EK_VM_START)); ALOGV(" suspendPolicy=%s", dvmJdwpSuspendPolicyStr(suspendPolicy)); pReq = eventPrep(); expandBufAdd1(pReq, suspendPolicy); expandBufAdd4BE(pReq, 1); expandBufAdd1(pReq, EK_VM_START); expandBufAdd4BE(pReq, 0); /* requestId */ expandBufAdd8BE(pReq, threadId); } unlockEventMutex(state); /* send request and possibly suspend ourselves */ if (pReq != NULL) { int oldStatus = dvmDbgThreadWaiting(); if (suspendPolicy != SP_NONE) dvmJdwpSetWaitForEventThread(state, threadId); eventFinish(state, pReq); suspendByPolicy(state, suspendPolicy); dvmDbgThreadContinuing(oldStatus); } return true; }
/* * Send up a chunk of DDM data. * * While this takes the form of a JDWP "event", it doesn't interact with * other debugger traffic, and can't suspend the VM, so we skip all of * the fun event token gymnastics. */ void dvmJdwpDdmSendChunkV(JdwpState* state, int type, const struct iovec* iov, int iovcnt) { u1 header[kJDWPHeaderLen + 8]; size_t dataLen = 0; assert(iov != NULL); assert(iovcnt > 0 && iovcnt < 10); /* * "Wrap" the contents of the iovec with a JDWP/DDMS header. We do * this by creating a new copy of the vector with space for the header. */ struct iovec wrapiov[iovcnt+1]; for (int i = 0; i < iovcnt; i++) { wrapiov[i+1].iov_base = iov[i].iov_base; wrapiov[i+1].iov_len = iov[i].iov_len; dataLen += iov[i].iov_len; } /* form the header (JDWP plus DDMS) */ set4BE(header, sizeof(header) + dataLen); set4BE(header+4, dvmJdwpNextRequestSerial(state)); set1(header+8, 0); /* flags */ set1(header+9, kJDWPDdmCmdSet); set1(header+10, kJDWPDdmCmd); set4BE(header+11, type); set4BE(header+15, dataLen); wrapiov[0].iov_base = header; wrapiov[0].iov_len = sizeof(header); /* * Make sure we're in VMWAIT in case the write blocks. */ int oldStatus = dvmDbgThreadWaiting(); dvmJdwpSendBufferedRequest(state, wrapiov, iovcnt+1); dvmDbgThreadContinuing(oldStatus); }
/* * A thread is starting or stopping. * * Valid mods: * Count, ThreadOnly */ bool dvmJdwpPostThreadChange(JdwpState* state, ObjectId threadId, bool start) { JdwpSuspendPolicy suspendPolicy = SP_NONE; assert(threadId == dvmDbgGetThreadSelfId()); /* * I don't think this can happen. */ if (invokeInProgress(state)) { ALOGW("Not posting thread change during invoke"); return false; } ModBasket basket; memset(&basket, 0, sizeof(basket)); basket.threadId = threadId; /* don't allow the list to be updated while we scan it */ lockEventMutex(state); JdwpEvent** matchList = allocMatchList(state); int matchCount = 0; if (start) findMatchingEvents(state, EK_THREAD_START, &basket, matchList, &matchCount); else findMatchingEvents(state, EK_THREAD_DEATH, &basket, matchList, &matchCount); ExpandBuf* pReq = NULL; if (matchCount != 0) { ALOGV("EVENT: %s(%d total) thread=%llx)", dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, basket.threadId); suspendPolicy = scanSuspendPolicy(matchList, matchCount); ALOGV(" suspendPolicy=%s", dvmJdwpSuspendPolicyStr(suspendPolicy)); pReq = eventPrep(); expandBufAdd1(pReq, suspendPolicy); expandBufAdd4BE(pReq, matchCount); for (int i = 0; i < matchCount; i++) { expandBufAdd1(pReq, matchList[i]->eventKind); expandBufAdd4BE(pReq, matchList[i]->requestId); expandBufAdd8BE(pReq, basket.threadId); } } cleanupMatchList(state, matchList, matchCount); unlockEventMutex(state); /* send request and possibly suspend ourselves */ if (pReq != NULL) { int oldStatus = dvmDbgThreadWaiting(); if (suspendPolicy != SP_NONE) dvmJdwpSetWaitForEventThread(state, basket.threadId); eventFinish(state, pReq); suspendByPolicy(state, suspendPolicy); dvmDbgThreadContinuing(oldStatus); } return matchCount != 0; }
/* * A location of interest has been reached. This handles: * Breakpoint * SingleStep * MethodEntry * MethodExit * These four types must be grouped together in a single response. The * "eventFlags" indicates the type of event(s) that have happened. * * Valid mods: * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, InstanceOnly * LocationOnly (for breakpoint/step only) * Step (for step only) * * Interesting test cases: * - Put a breakpoint on a native method. Eclipse creates METHOD_ENTRY * and METHOD_EXIT events with a ClassOnly mod on the method's class. * - Use "run to line". Eclipse creates a BREAKPOINT with Count=1. * - Single-step to a line with a breakpoint. Should get a single * event message with both events in it. */ bool dvmJdwpPostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) { JdwpSuspendPolicy suspendPolicy = SP_NONE; ModBasket basket; char* nameAlloc = NULL; memset(&basket, 0, sizeof(basket)); basket.pLoc = pLoc; basket.classId = pLoc->classId; basket.thisPtr = thisPtr; basket.threadId = dvmDbgGetThreadSelfId(); basket.className = nameAlloc = dvmDescriptorToName(dvmDbgGetClassDescriptor(pLoc->classId)); /* * On rare occasions we may need to execute interpreted code in the VM * while handling a request from the debugger. Don't fire breakpoints * while doing so. (I don't think we currently do this at all, so * this is mostly paranoia.) */ if (basket.threadId == state->debugThreadId) { ALOGV("Ignoring location event in JDWP thread"); free(nameAlloc); return false; } /* * The debugger variable display tab may invoke the interpreter to format * complex objects. We want to ignore breakpoints and method entry/exit * traps while working on behalf of the debugger. * * If we don't ignore them, the VM will get hung up, because we'll * suspend on a breakpoint while the debugger is still waiting for its * method invocation to complete. */ if (invokeInProgress(state)) { ALOGV("Not checking breakpoints during invoke (%s)", basket.className); free(nameAlloc); return false; } /* don't allow the list to be updated while we scan it */ lockEventMutex(state); JdwpEvent** matchList = allocMatchList(state); int matchCount = 0; if ((eventFlags & DBG_BREAKPOINT) != 0) findMatchingEvents(state, EK_BREAKPOINT, &basket, matchList, &matchCount); if ((eventFlags & DBG_SINGLE_STEP) != 0) findMatchingEvents(state, EK_SINGLE_STEP, &basket, matchList, &matchCount); if ((eventFlags & DBG_METHOD_ENTRY) != 0) findMatchingEvents(state, EK_METHOD_ENTRY, &basket, matchList, &matchCount); if ((eventFlags & DBG_METHOD_EXIT) != 0) findMatchingEvents(state, EK_METHOD_EXIT, &basket, matchList, &matchCount); ExpandBuf* pReq = NULL; if (matchCount != 0) { ALOGV("EVENT: %s(%d total) %s.%s thread=%llx code=%llx)", dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, basket.className, dvmDbgGetMethodName(pLoc->classId, pLoc->methodId), basket.threadId, pLoc->idx); suspendPolicy = scanSuspendPolicy(matchList, matchCount); ALOGV(" suspendPolicy=%s", dvmJdwpSuspendPolicyStr(suspendPolicy)); pReq = eventPrep(); expandBufAdd1(pReq, suspendPolicy); expandBufAdd4BE(pReq, matchCount); for (int i = 0; i < matchCount; i++) { expandBufAdd1(pReq, matchList[i]->eventKind); expandBufAdd4BE(pReq, matchList[i]->requestId); expandBufAdd8BE(pReq, basket.threadId); dvmJdwpAddLocation(pReq, pLoc); } } cleanupMatchList(state, matchList, matchCount); unlockEventMutex(state); /* send request and possibly suspend ourselves */ if (pReq != NULL) { int oldStatus = dvmDbgThreadWaiting(); if (suspendPolicy != SP_NONE) dvmJdwpSetWaitForEventThread(state, basket.threadId); eventFinish(state, pReq); suspendByPolicy(state, suspendPolicy); dvmDbgThreadContinuing(oldStatus); } free(nameAlloc); return matchCount != 0; }
/* * Announce that a class has been loaded. * * Valid mods: * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude */ bool dvmJdwpPostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId, const char* signature, int status) { JdwpSuspendPolicy suspendPolicy = SP_NONE; ModBasket basket; char* nameAlloc = NULL; memset(&basket, 0, sizeof(basket)); basket.classId = refTypeId; basket.threadId = dvmDbgGetThreadSelfId(); basket.className = nameAlloc = dvmDescriptorToName(dvmDbgGetClassDescriptor(basket.classId)); /* suppress class prep caused by debugger */ if (invokeInProgress(state)) { ALOGV("Not posting class prep caused by invoke (%s)",basket.className); free(nameAlloc); return false; } /* don't allow the list to be updated while we scan it */ lockEventMutex(state); JdwpEvent** matchList = allocMatchList(state); int matchCount = 0; findMatchingEvents(state, EK_CLASS_PREPARE, &basket, matchList, &matchCount); ExpandBuf* pReq = NULL; if (matchCount != 0) { ALOGV("EVENT: %s(%d total) thread=%llx)", dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, basket.threadId); suspendPolicy = scanSuspendPolicy(matchList, matchCount); ALOGV(" suspendPolicy=%s", dvmJdwpSuspendPolicyStr(suspendPolicy)); if (basket.threadId == state->debugThreadId) { /* * JDWP says that, for a class prep in the debugger thread, we * should set threadId to null and if any threads were supposed * to be suspended then we suspend all other threads. */ ALOGV(" NOTE: class prepare in debugger thread!"); basket.threadId = 0; if (suspendPolicy == SP_EVENT_THREAD) suspendPolicy = SP_ALL; } pReq = eventPrep(); expandBufAdd1(pReq, suspendPolicy); expandBufAdd4BE(pReq, matchCount); for (int i = 0; i < matchCount; i++) { expandBufAdd1(pReq, matchList[i]->eventKind); expandBufAdd4BE(pReq, matchList[i]->requestId); expandBufAdd8BE(pReq, basket.threadId); expandBufAdd1(pReq, tag); expandBufAdd8BE(pReq, refTypeId); expandBufAddUtf8String(pReq, (const u1*) signature); expandBufAdd4BE(pReq, status); } } cleanupMatchList(state, matchList, matchCount); unlockEventMutex(state); /* send request and possibly suspend ourselves */ if (pReq != NULL) { int oldStatus = dvmDbgThreadWaiting(); if (suspendPolicy != SP_NONE) dvmJdwpSetWaitForEventThread(state, basket.threadId); eventFinish(state, pReq); suspendByPolicy(state, suspendPolicy); dvmDbgThreadContinuing(oldStatus); } free(nameAlloc); return matchCount != 0; }
/* * An exception has been thrown. It may or may not have been caught. * * Valid mods: * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly, * ExceptionOnly, InstanceOnly * * The "exceptionId" has not been added to the GC-visible object registry, * because there's a pretty good chance that we're not going to send it * up the debugger. */ bool dvmJdwpPostException(JdwpState* state, const JdwpLocation* pThrowLoc, ObjectId exceptionId, RefTypeId exceptionClassId, const JdwpLocation* pCatchLoc, ObjectId thisPtr) { JdwpSuspendPolicy suspendPolicy = SP_NONE; ModBasket basket; char* nameAlloc = NULL; memset(&basket, 0, sizeof(basket)); basket.pLoc = pThrowLoc; basket.classId = pThrowLoc->classId; basket.threadId = dvmDbgGetThreadSelfId(); basket.className = nameAlloc = dvmDescriptorToName(dvmDbgGetClassDescriptor(basket.classId)); basket.excepClassId = exceptionClassId; basket.caught = (pCatchLoc->classId != 0); basket.thisPtr = thisPtr; /* don't try to post an exception caused by the debugger */ if (invokeInProgress(state)) { ALOGV("Not posting exception hit during invoke (%s)",basket.className); free(nameAlloc); return false; } /* don't allow the list to be updated while we scan it */ lockEventMutex(state); JdwpEvent** matchList = allocMatchList(state); int matchCount = 0; findMatchingEvents(state, EK_EXCEPTION, &basket, matchList, &matchCount); ExpandBuf* pReq = NULL; if (matchCount != 0) { ALOGV("EVENT: %s(%d total) thread=%llx exceptId=%llx caught=%d)", dvmJdwpEventKindStr(matchList[0]->eventKind), matchCount, basket.threadId, exceptionId, basket.caught); ALOGV(" throw: %d %llx %x %lld (%s.%s)", pThrowLoc->typeTag, pThrowLoc->classId, pThrowLoc->methodId, pThrowLoc->idx, dvmDbgGetClassDescriptor(pThrowLoc->classId), dvmDbgGetMethodName(pThrowLoc->classId, pThrowLoc->methodId)); if (pCatchLoc->classId == 0) { ALOGV(" catch: (not caught)"); } else { ALOGV(" catch: %d %llx %x %lld (%s.%s)", pCatchLoc->typeTag, pCatchLoc->classId, pCatchLoc->methodId, pCatchLoc->idx, dvmDbgGetClassDescriptor(pCatchLoc->classId), dvmDbgGetMethodName(pCatchLoc->classId, pCatchLoc->methodId)); } suspendPolicy = scanSuspendPolicy(matchList, matchCount); ALOGV(" suspendPolicy=%s", dvmJdwpSuspendPolicyStr(suspendPolicy)); pReq = eventPrep(); expandBufAdd1(pReq, suspendPolicy); expandBufAdd4BE(pReq, matchCount); for (int i = 0; i < matchCount; i++) { expandBufAdd1(pReq, matchList[i]->eventKind); expandBufAdd4BE(pReq, matchList[i]->requestId); expandBufAdd8BE(pReq, basket.threadId); dvmJdwpAddLocation(pReq, pThrowLoc); expandBufAdd1(pReq, JT_OBJECT); expandBufAdd8BE(pReq, exceptionId); dvmJdwpAddLocation(pReq, pCatchLoc); } /* don't let the GC discard it */ dvmDbgRegisterObjectId(exceptionId); } cleanupMatchList(state, matchList, matchCount); unlockEventMutex(state); /* send request and possibly suspend ourselves */ if (pReq != NULL) { int oldStatus = dvmDbgThreadWaiting(); if (suspendPolicy != SP_NONE) dvmJdwpSetWaitForEventThread(state, basket.threadId); eventFinish(state, pReq); suspendByPolicy(state, suspendPolicy); dvmDbgThreadContinuing(oldStatus); } free(nameAlloc); return matchCount != 0; }
/* * Entry point for JDWP thread. The thread was created through the VM * mechanisms, so there is a java/lang/Thread associated with us. */ static void* jdwpThreadStart(void* arg) { JdwpState* state = (JdwpState*) arg; LOGV("JDWP: thread running\n"); /* * Finish initializing "state", then notify the creating thread that * we're running. */ state->debugThreadHandle = dvmThreadSelf()->handle; state->run = true; android_atomic_release_store(true, &state->debugThreadStarted); dvmDbgLockMutex(&state->threadStartLock); dvmDbgCondBroadcast(&state->threadStartCond); dvmDbgUnlockMutex(&state->threadStartLock); /* set the thread state to VMWAIT so GCs don't wait for us */ dvmDbgThreadWaiting(); /* * Loop forever if we're in server mode, processing connections. In * non-server mode, we bail out of the thread when the debugger drops * us. * * We broadcast a notification when a debugger attaches, after we * successfully process the handshake. */ while (state->run) { bool first; if (state->params.server) { /* * Block forever, waiting for a connection. To support the * "timeout=xxx" option we'll need to tweak this. */ if (!dvmJdwpAcceptConnection(state)) break; } else { /* * If we're not acting as a server, we need to connect out to the * debugger. To support the "timeout=xxx" option we need to * have a timeout if the handshake reply isn't received in a * reasonable amount of time. */ if (!dvmJdwpEstablishConnection(state)) { /* wake anybody who was waiting for us to succeed */ dvmDbgLockMutex(&state->attachLock); dvmDbgCondBroadcast(&state->attachCond); dvmDbgUnlockMutex(&state->attachLock); break; } } /* prep debug code to handle the new connection */ dvmDbgConnected(); /* process requests until the debugger drops */ first = true; while (true) { // sanity check -- shouldn't happen? if (dvmThreadSelf()->status != THREAD_VMWAIT) { LOGE("JDWP thread no longer in VMWAIT (now %d); resetting\n", dvmThreadSelf()->status); dvmDbgThreadWaiting(); } if (!dvmJdwpProcessIncoming(state)) /* blocking read */ break; if (first && !dvmJdwpAwaitingHandshake(state)) { /* handshake worked, tell the interpreter that we're active */ first = false; /* set thread ID; requires object registry to be active */ state->debugThreadId = dvmDbgGetThreadSelfId(); /* wake anybody who's waiting for us */ dvmDbgLockMutex(&state->attachLock); dvmDbgCondBroadcast(&state->attachCond); dvmDbgUnlockMutex(&state->attachLock); } } dvmJdwpCloseConnection(state); if (state->ddmActive) { state->ddmActive = false; /* broadcast the disconnect; must be in RUNNING state */ dvmDbgThreadRunning(); dvmDbgDdmDisconnected(); dvmDbgThreadWaiting(); } /* release session state, e.g. remove breakpoint instructions */ dvmJdwpResetState(state); /* tell the interpreter that the debugger is no longer around */ dvmDbgDisconnected(); /* if we had threads suspended, resume them now */ dvmUndoDebuggerSuspensions(); /* if we connected out, this was a one-shot deal */ if (!state->params.server) state->run = false; } /* back to running, for thread shutdown */ dvmDbgThreadRunning(); LOGV("JDWP: thread exiting\n"); return NULL; }