Bring across the JDWP implementation.
This compiles and links, but does nothing until we fill out the 100 or so
unimplemented methods in "debugger.cc". Note that I also need to add the
extra command-line handling for the JDWP agent stuff, and add calls from
the runtime to the various "something interesting is going on" hooks.
Change-Id: I477cf3caf9e248c384ce1d739cbfadb60e2008bc
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
new file mode 100644
index 0000000..3a2d73e
--- /dev/null
+++ b/src/jdwp/jdwp_event.cc
@@ -0,0 +1,1211 @@
+/*
+ * 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.
+ */
+/*
+ * Send events to the debugger.
+ */
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_handler.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+#include "stringprintf.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h> /* for offsetof() */
+#include <unistd.h>
+
+/*
+General notes:
+
+The event add/remove stuff usually happens from the debugger thread,
+in response to requests from the debugger, but can also happen as the
+result of an event in an arbitrary thread (e.g. an event with a "count"
+mod expires). It's important to keep the event list locked when processing
+events.
+
+Event posting can happen from any thread. The JDWP thread will not usually
+post anything but VM start/death, but if a JDWP request causes a class
+to be loaded, the ClassPrepare event will come from the JDWP thread.
+
+
+We can have serialization issues when we post an event to the debugger.
+For example, a thread could send an "I hit a breakpoint and am suspending
+myself" message to the debugger. Before it manages to suspend itself, the
+debugger's response ("not interested, resume thread") arrives and is
+processed. We try to resume a thread that hasn't yet suspended.
+
+This means that, after posting an event to the debugger, we need to wait
+for the event thread to suspend itself (and, potentially, all other threads)
+before processing any additional requests from the debugger. While doing
+so we need to be aware that multiple threads may be hitting breakpoints
+or other events simultaneously, so we either need to wait for all of them
+or serialize the events with each other.
+
+The current mechanism works like this:
+ Event thread:
+ - If I'm going to suspend, grab the "I am posting an event" token. Wait
+ for it if it's not currently available.
+ - Post the event to the debugger.
+ - If appropriate, suspend others and then myself. As part of suspending
+ myself, release the "I am posting" token.
+ JDWP thread:
+ - When an event arrives, see if somebody is posting an event. If so,
+ sleep until we can acquire the "I am posting an event" token. Release
+ it immediately and continue processing -- the event we have already
+ received should not interfere with other events that haven't yet
+ been posted.
+
+Some care must be taken to avoid deadlock:
+
+ - thread A and thread B exit near-simultaneously, and post thread-death
+ events with a "suspend all" clause
+ - thread A gets the event token, thread B sits and waits for it
+ - thread A wants to suspend all other threads, but thread B is waiting
+ for the token and can't be suspended
+
+So we need to mark thread B in such a way that thread A doesn't wait for it.
+
+If we just bracket the "grab event token" call with a change to VMWAIT
+before sleeping, the switch back to RUNNING state when we get the token
+will cause thread B to suspend (remember, thread A's global suspend is
+still in force, even after it releases the token). Suspending while
+holding the event token is very bad, because it prevents the JDWP thread
+from processing incoming messages.
+
+We need to change to VMWAIT state at the *start* of posting an event,
+and stay there until we either finish posting the event or decide to
+put ourselves to sleep. That way we don't interfere with anyone else and
+don't allow anyone else to interfere with us.
+*/
+
+
+#define kJdwpEventCommandSet 64
+#define kJdwpCompositeCommand 100
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Stuff to compare against when deciding if a mod matches. Only the
+ * values for mods valid for the event being evaluated will be filled in.
+ * The rest will be zeroed.
+ */
+struct ModBasket {
+ const JdwpLocation* pLoc; /* LocationOnly */
+ const char* className; /* ClassMatch/ClassExclude */
+ ObjectId threadId; /* ThreadOnly */
+ RefTypeId classId; /* ClassOnly */
+ RefTypeId excepClassId; /* ExceptionOnly */
+ bool caught; /* ExceptionOnly */
+ FieldId field; /* FieldOnly */
+ ObjectId thisPtr; /* InstanceOnly */
+ /* nothing for StepOnly -- handled differently */
+};
+
+/*
+ * Get the next "request" serial number. We use this when sending
+ * packets to the debugger.
+ */
+uint32_t NextRequestSerial(JdwpState* state) {
+ MutexLock mu(state->serial_lock_);
+ return state->requestSerial++;
+}
+
+/*
+ * Get the next "event" serial number. We use this in the response to
+ * message type EventRequest.Set.
+ */
+uint32_t NextEventSerial(JdwpState* state) {
+ MutexLock mu(state->serial_lock_);
+ return state->eventSerial++;
+}
+
+/*
+ * Lock the "event" mutex, which guards the list of registered events.
+ */
+static void lockEventMutex(JdwpState* state) {
+ //Dbg::ThreadWaiting();
+ state->event_lock_.Lock();
+ //Dbg::ThreadRunning();
+}
+
+/*
+ * Unlock the "event" mutex.
+ */
+static void unlockEventMutex(JdwpState* state) {
+ state->event_lock_.Unlock();
+}
+
+/*
+ * Dump an event to the log file.
+ */
+static void dumpEvent(const JdwpEvent* pEvent) {
+ LOG(INFO) << StringPrintf("Event id=0x%4x %p (prev=%p next=%p):", pEvent->requestId, pEvent, pEvent->prev, pEvent->next);
+ LOG(INFO) << " kind=" << pEvent->eventKind << " susp=" << pEvent->suspendPolicy << " modCount=" << pEvent->modCount;
+
+ for (int i = 0; i < pEvent->modCount; i++) {
+ const JdwpEventMod* pMod = &pEvent->mods[i];
+ LOG(INFO) << " " << static_cast<JdwpModKind>(pMod->modKind);
+ /* TODO - show details */
+ }
+}
+
+/*
+ * Add an event to the list. Ordering is not important.
+ *
+ * If something prevents the event from being registered, e.g. it's a
+ * single-step request on a thread that doesn't exist, the event will
+ * not be added to the list, and an appropriate error will be returned.
+ */
+JdwpError RegisterEvent(JdwpState* state, JdwpEvent* pEvent) {
+ lockEventMutex(state);
+
+ CHECK(state != NULL);
+ CHECK(pEvent != NULL);
+ CHECK(pEvent->prev == NULL);
+ CHECK(pEvent->next == NULL);
+
+ /*
+ * If one or more "break"-type mods are used, register them with
+ * the interpreter.
+ */
+ for (int i = 0; i < pEvent->modCount; i++) {
+ const JdwpEventMod* pMod = &pEvent->mods[i];
+ if (pMod->modKind == MK_LOCATION_ONLY) {
+ /* should only be for Breakpoint, Step, and Exception */
+ Dbg::WatchLocation(&pMod->locationOnly.loc);
+ } else if (pMod->modKind == MK_STEP) {
+ /* should only be for EK_SINGLE_STEP; should only be one */
+ JdwpStepSize size = static_cast<JdwpStepSize>(pMod->step.size);
+ JdwpStepDepth depth = static_cast<JdwpStepDepth>(pMod->step.depth);
+ Dbg::ConfigureStep(pMod->step.threadId, size, depth);
+ } else if (pMod->modKind == MK_FIELD_ONLY) {
+ /* should be for EK_FIELD_ACCESS or EK_FIELD_MODIFICATION */
+ dumpEvent(pEvent); /* TODO - need for field watches */
+ }
+ }
+
+ /*
+ * Add to list.
+ */
+ if (state->eventList != NULL) {
+ pEvent->next = state->eventList;
+ state->eventList->prev = pEvent;
+ }
+ state->eventList = pEvent;
+ state->numEvents++;
+
+ unlockEventMutex(state);
+
+ return ERR_NONE;
+}
+
+/*
+ * Remove an event from the list. This will also remove the event from
+ * any optimization tables, e.g. breakpoints.
+ *
+ * Does not free the JdwpEvent.
+ *
+ * Grab the eventLock before calling here.
+ */
+static void unregisterEvent(JdwpState* state, JdwpEvent* pEvent) {
+ if (pEvent->prev == NULL) {
+ /* head of the list */
+ CHECK(state->eventList == pEvent);
+
+ state->eventList = pEvent->next;
+ } else {
+ pEvent->prev->next = pEvent->next;
+ }
+
+ if (pEvent->next != NULL) {
+ pEvent->next->prev = pEvent->prev;
+ pEvent->next = NULL;
+ }
+ pEvent->prev = NULL;
+
+ /*
+ * Unhook us from the interpreter, if necessary.
+ */
+ for (int i = 0; i < pEvent->modCount; i++) {
+ JdwpEventMod* pMod = &pEvent->mods[i];
+ if (pMod->modKind == MK_LOCATION_ONLY) {
+ /* should only be for Breakpoint, Step, and Exception */
+ Dbg::UnwatchLocation(&pMod->locationOnly.loc);
+ }
+ if (pMod->modKind == MK_STEP) {
+ /* should only be for EK_SINGLE_STEP; should only be one */
+ Dbg::UnconfigureStep(pMod->step.threadId);
+ }
+ }
+
+ state->numEvents--;
+ CHECK(state->numEvents != 0 || state->eventList == NULL);
+}
+
+/*
+ * Remove the event with the given ID from the list.
+ *
+ * Failure to find the event isn't really an error, but it is a little
+ * weird. (It looks like Eclipse will try to be extra careful and will
+ * explicitly remove one-off single-step events.)
+ */
+void UnregisterEventById(JdwpState* state, uint32_t requestId) {
+ lockEventMutex(state);
+
+ JdwpEvent* pEvent = state->eventList;
+ while (pEvent != NULL) {
+ if (pEvent->requestId == requestId) {
+ unregisterEvent(state, pEvent);
+ EventFree(pEvent);
+ goto done; /* there can be only one with a given ID */
+ }
+
+ pEvent = pEvent->next;
+ }
+
+ //LOGD("Odd: no match when removing event reqId=0x%04x", requestId);
+
+done:
+ unlockEventMutex(state);
+}
+
+/*
+ * Remove all entries from the event list.
+ */
+void UnregisterAll(JdwpState* state) {
+ lockEventMutex(state);
+
+ JdwpEvent* pEvent = state->eventList;
+ while (pEvent != NULL) {
+ JdwpEvent* pNextEvent = pEvent->next;
+
+ unregisterEvent(state, pEvent);
+ EventFree(pEvent);
+ pEvent = pNextEvent;
+ }
+
+ state->eventList = NULL;
+
+ unlockEventMutex(state);
+}
+
+/*
+ * Allocate a JdwpEvent struct with enough space to hold the specified
+ * number of mod records.
+ */
+JdwpEvent* EventAlloc(int numMods) {
+ JdwpEvent* newEvent;
+ int allocSize = offsetof(JdwpEvent, mods) + numMods * sizeof(newEvent->mods[0]);
+ newEvent = reinterpret_cast<JdwpEvent*>(malloc(allocSize));
+ memset(newEvent, 0, allocSize);
+ return newEvent;
+}
+
+/*
+ * Free a JdwpEvent.
+ *
+ * Do not call this until the event has been removed from the list.
+ */
+void EventFree(JdwpEvent* pEvent) {
+ if (pEvent == NULL) {
+ return;
+ }
+
+ /* make sure it was removed from the list */
+ CHECK(pEvent->prev == NULL);
+ CHECK(pEvent->next == NULL);
+ /* want to check state->eventList != pEvent */
+
+ /*
+ * Free any hairy bits in the mods.
+ */
+ for (int i = 0; i < pEvent->modCount; i++) {
+ if (pEvent->mods[i].modKind == MK_CLASS_MATCH) {
+ free(pEvent->mods[i].classMatch.classPattern);
+ pEvent->mods[i].classMatch.classPattern = NULL;
+ }
+ if (pEvent->mods[i].modKind == MK_CLASS_EXCLUDE) {
+ free(pEvent->mods[i].classExclude.classPattern);
+ pEvent->mods[i].classExclude.classPattern = NULL;
+ }
+ }
+
+ free(pEvent);
+}
+
+/*
+ * Allocate storage for matching events. To keep things simple we
+ * use an array with enough storage for the entire list.
+ *
+ * The state->eventLock should be held before calling.
+ */
+static JdwpEvent** allocMatchList(JdwpState* state) {
+ return (JdwpEvent**) malloc(sizeof(JdwpEvent*) * state->numEvents);
+}
+
+/*
+ * Run through the list and remove any entries with an expired "count" mod
+ * from the event list, then free the match list.
+ */
+static void cleanupMatchList(JdwpState* state, JdwpEvent** matchList, int matchCount) {
+ JdwpEvent** ppEvent = matchList;
+
+ while (matchCount--) {
+ JdwpEvent* pEvent = *ppEvent;
+
+ for (int i = 0; i < pEvent->modCount; i++) {
+ if (pEvent->mods[i].modKind == MK_COUNT && pEvent->mods[i].count.count == 0) {
+ LOG(VERBOSE) << "##### Removing expired event";
+ unregisterEvent(state, pEvent);
+ EventFree(pEvent);
+ break;
+ }
+ }
+
+ ppEvent++;
+ }
+
+ free(matchList);
+}
+
+/*
+ * Match a string against a "restricted regular expression", which is just
+ * a string that may start or end with '*' (e.g. "*.Foo" or "java.*").
+ *
+ * ("Restricted name globbing" might have been a better term.)
+ */
+static bool patternMatch(const char* pattern, const char* target) {
+ int patLen = strlen(pattern);
+
+ if (pattern[0] == '*') {
+ int targetLen = strlen(target);
+ patLen--;
+ // TODO: remove printf when we find a test case to verify this
+ LOG(ERROR) << ">>> comparing '" << (pattern + 1) << "' to '" << (target + (targetLen-patLen)) << "'";
+
+ if (targetLen < patLen) {
+ return false;
+ }
+ return strcmp(pattern+1, target + (targetLen-patLen)) == 0;
+ } else if (pattern[patLen-1] == '*') {
+ return strncmp(pattern, target, patLen-1) == 0;
+ } else {
+ return strcmp(pattern, target) == 0;
+ }
+}
+
+/*
+ * See if two locations are equal.
+ *
+ * It's tempting to do a bitwise compare ("struct ==" or memcmp), but if
+ * the storage wasn't zeroed out there could be undefined values in the
+ * padding. Besides, the odds of "idx" being equal while the others aren't
+ * is very small, so this is usually just a simple integer comparison.
+ */
+static inline bool locationMatch(const JdwpLocation* pLoc1, const JdwpLocation* pLoc2) {
+ return pLoc1->idx == pLoc2->idx &&
+ pLoc1->methodId == pLoc2->methodId &&
+ pLoc1->classId == pLoc2->classId &&
+ pLoc1->typeTag == pLoc2->typeTag;
+}
+
+/*
+ * See if the event's mods match up with the contents of "basket".
+ *
+ * If we find a Count mod before rejecting an event, we decrement it. We
+ * need to do this even if later mods cause us to ignore the event.
+ */
+static bool modsMatch(JdwpState* state, JdwpEvent* pEvent, ModBasket* basket) {
+ JdwpEventMod* pMod = pEvent->mods;
+
+ for (int i = pEvent->modCount; i > 0; i--, pMod++) {
+ switch (pMod->modKind) {
+ case MK_COUNT:
+ CHECK_GT(pMod->count.count, 0);
+ pMod->count.count--;
+ break;
+ case MK_CONDITIONAL:
+ CHECK(false); // should not be getting these
+ break;
+ case MK_THREAD_ONLY:
+ if (pMod->threadOnly.threadId != basket->threadId) {
+ return false;
+ }
+ break;
+ case MK_CLASS_ONLY:
+ if (!Dbg::MatchType(basket->classId, pMod->classOnly.refTypeId)) {
+ return false;
+ }
+ break;
+ case MK_CLASS_MATCH:
+ if (!patternMatch(pMod->classMatch.classPattern, basket->className)) {
+ return false;
+ }
+ break;
+ case MK_CLASS_EXCLUDE:
+ if (patternMatch(pMod->classMatch.classPattern, basket->className)) {
+ return false;
+ }
+ break;
+ case MK_LOCATION_ONLY:
+ if (!locationMatch(&pMod->locationOnly.loc, basket->pLoc)) {
+ return false;
+ }
+ break;
+ case MK_EXCEPTION_ONLY:
+ if (pMod->exceptionOnly.refTypeId != 0 && !Dbg::MatchType(basket->excepClassId, pMod->exceptionOnly.refTypeId)) {
+ return false;
+ }
+ if ((basket->caught && !pMod->exceptionOnly.caught) || (!basket->caught && !pMod->exceptionOnly.uncaught)) {
+ return false;
+ }
+ break;
+ case MK_FIELD_ONLY:
+ if (!Dbg::MatchType(basket->classId, pMod->fieldOnly.refTypeId) || pMod->fieldOnly.fieldId != basket->field) {
+ return false;
+ }
+ break;
+ case MK_STEP:
+ if (pMod->step.threadId != basket->threadId) {
+ return false;
+ }
+ break;
+ case MK_INSTANCE_ONLY:
+ if (pMod->instanceOnly.objectId != basket->thisPtr) {
+ return false;
+ }
+ break;
+ default:
+ LOG(ERROR) << "unhandled mod kind " << pMod->modKind;
+ CHECK(false);
+ break;
+ }
+ }
+ return true;
+}
+
+/*
+ * Find all events of type "eventKind" with mods that match up with the
+ * rest of the arguments.
+ *
+ * Found events are appended to "matchList", and "*pMatchCount" is advanced,
+ * so this may be called multiple times for grouped events.
+ *
+ * DO NOT call this multiple times for the same eventKind, as Count mods are
+ * decremented during the scan.
+ */
+static void findMatchingEvents(JdwpState* state, JdwpEventKind eventKind,
+ ModBasket* basket, JdwpEvent** matchList, int* pMatchCount) {
+ /* start after the existing entries */
+ matchList += *pMatchCount;
+
+ JdwpEvent* pEvent = state->eventList;
+ while (pEvent != NULL) {
+ if (pEvent->eventKind == eventKind && modsMatch(state, pEvent, basket)) {
+ *matchList++ = pEvent;
+ (*pMatchCount)++;
+ }
+
+ pEvent = pEvent->next;
+ }
+}
+
+/*
+ * Scan through the list of matches and determine the most severe
+ * suspension policy.
+ */
+static JdwpSuspendPolicy scanSuspendPolicy(JdwpEvent** matchList, int matchCount) {
+ JdwpSuspendPolicy policy = SP_NONE;
+
+ while (matchCount--) {
+ if ((*matchList)->suspendPolicy > policy) {
+ policy = (*matchList)->suspendPolicy;
+ }
+ matchList++;
+ }
+
+ return policy;
+}
+
+/*
+ * Three possibilities:
+ * SP_NONE - do nothing
+ * SP_EVENT_THREAD - suspend ourselves
+ * SP_ALL - suspend everybody except JDWP support thread
+ */
+static void suspendByPolicy(JdwpState* state, JdwpSuspendPolicy suspendPolicy) {
+ if (suspendPolicy == SP_NONE) {
+ return;
+ }
+
+ if (suspendPolicy == SP_ALL) {
+ Dbg::SuspendVM(true);
+ } else {
+ CHECK_EQ(suspendPolicy, SP_EVENT_THREAD);
+ }
+
+ /* this is rare but possible -- see CLASS_PREPARE handling */
+ if (Dbg::GetThreadSelfId() == state->debugThreadId) {
+ LOG(INFO) << "NOTE: suspendByPolicy not suspending JDWP thread";
+ return;
+ }
+
+ DebugInvokeReq* pReq = Dbg::GetInvokeReq();
+ while (true) {
+ pReq->ready = true;
+ Dbg::SuspendSelf();
+ pReq->ready = false;
+
+ /*
+ * The JDWP thread has told us (and possibly all other threads) to
+ * resume. See if it has left anything in our DebugInvokeReq mailbox.
+ */
+ if (!pReq->invokeNeeded) {
+ /*LOGD("suspendByPolicy: no invoke needed");*/
+ break;
+ }
+
+ /* grab this before posting/suspending again */
+ SetWaitForEventThread(state, Dbg::GetThreadSelfId());
+
+ /* leave pReq->invokeNeeded raised so we can check reentrancy */
+ LOG(VERBOSE) << "invoking method...";
+ Dbg::ExecuteMethod(pReq);
+
+ pReq->err = ERR_NONE;
+
+ /* clear this before signaling */
+ pReq->invokeNeeded = false;
+
+ LOG(VERBOSE) << "invoke complete, signaling and self-suspending";
+ MutexLock mu(pReq->lock_);
+ pReq->cond_.Signal();
+ }
+}
+
+/*
+ * Determine if there is a method invocation in progress in the current
+ * thread.
+ *
+ * We look at the "invokeNeeded" flag in the per-thread DebugInvokeReq
+ * state. If set, we're in the process of invoking a method.
+ */
+static bool invokeInProgress(JdwpState* state) {
+ DebugInvokeReq* pReq = Dbg::GetInvokeReq();
+ return pReq->invokeNeeded;
+}
+
+/*
+ * We need the JDWP thread to hold off on doing stuff while we post an
+ * event and then suspend ourselves.
+ *
+ * Call this with a threadId of zero if you just want to wait for the
+ * current thread operation to complete.
+ *
+ * This could go to sleep waiting for another thread, so it's important
+ * that the thread be marked as VMWAIT before calling here.
+ */
+void SetWaitForEventThread(JdwpState* state, ObjectId threadId) {
+ bool waited = false;
+
+ /* this is held for very brief periods; contention is unlikely */
+ MutexLock mu(state->event_thread_lock_);
+
+ /*
+ * If another thread is already doing stuff, wait for it. This can
+ * go to sleep indefinitely.
+ */
+ while (state->eventThreadId != 0) {
+ LOG(VERBOSE) << StringPrintf("event in progress (0x%llx), 0x%llx sleeping", state->eventThreadId, threadId);
+ waited = true;
+ state->event_thread_cond_.Wait(state->event_thread_lock_);
+ }
+
+ if (waited || threadId != 0) {
+ LOG(VERBOSE) << StringPrintf("event token grabbed (0x%llx)", threadId);
+ }
+ if (threadId != 0) {
+ state->eventThreadId = threadId;
+ }
+}
+
+/*
+ * Clear the threadId and signal anybody waiting.
+ */
+void ClearWaitForEventThread(JdwpState* state) {
+ /*
+ * Grab the mutex. Don't try to go in/out of VMWAIT mode, as this
+ * function is called by dvmSuspendSelf(), and the transition back
+ * to RUNNING would confuse it.
+ */
+ MutexLock mu(state->event_thread_lock_);
+
+ CHECK_NE(state->eventThreadId, 0U);
+ LOG(VERBOSE) << StringPrintf("cleared event token (0x%llx)", state->eventThreadId);
+
+ state->eventThreadId = 0;
+
+ state->event_thread_cond_.Signal();
+}
+
+
+/*
+ * Prep an event. Allocates storage for the message and leaves space for
+ * the header.
+ */
+static ExpandBuf* eventPrep() {
+ ExpandBuf* pReq = expandBufAlloc();
+ expandBufAddSpace(pReq, kJDWPHeaderLen);
+ return pReq;
+}
+
+/*
+ * Write the header into the buffer and send the packet off to the debugger.
+ *
+ * Takes ownership of "pReq" (currently discards it).
+ */
+static void eventFinish(JdwpState* state, ExpandBuf* pReq) {
+ uint8_t* buf = expandBufGetBuffer(pReq);
+
+ set4BE(buf, expandBufGetLength(pReq));
+ set4BE(buf+4, NextRequestSerial(state));
+ set1(buf+8, 0); /* flags */
+ set1(buf+9, kJdwpEventCommandSet);
+ set1(buf+10, kJdwpCompositeCommand);
+
+ SendRequest(state, pReq);
+
+ expandBufFree(pReq);
+}
+
+
+/*
+ * 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 PostVMStart(JdwpState* state, bool suspend) {
+ JdwpSuspendPolicy suspendPolicy;
+ ObjectId threadId = Dbg::GetThreadSelfId();
+
+ if (suspend) {
+ suspendPolicy = SP_ALL;
+ } else {
+ suspendPolicy = SP_NONE;
+ }
+
+ /* probably don't need this here */
+ lockEventMutex(state);
+
+ ExpandBuf* pReq = NULL;
+ if (true) {
+ LOG(VERBOSE) << "EVENT: " << EK_VM_START;
+ LOG(VERBOSE) << " suspendPolicy=" << 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 = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, threadId);
+ }
+
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ return true;
+}
+
+// TODO: This doesn't behave like the real dvmDescriptorToName.
+// I'm hoping this isn't used to communicate with the debugger, and we can just use descriptors.
+char* dvmDescriptorToName(const char* descriptor) {
+ return strdup(descriptor);
+}
+
+/*
+ * 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 PostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) {
+ ModBasket basket;
+ char* nameAlloc = NULL;
+
+ memset(&basket, 0, sizeof(basket));
+ basket.pLoc = pLoc;
+ basket.classId = pLoc->classId;
+ basket.thisPtr = thisPtr;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(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) {
+ LOG(VERBOSE) << "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)) {
+ LOG(VERBOSE) << "Not checking breakpoints during invoke (" << 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::kBreakPoint) != 0) {
+ findMatchingEvents(state, EK_BREAKPOINT, &basket, matchList, &matchCount);
+ }
+ if ((eventFlags & Dbg::kSingleStep) != 0) {
+ findMatchingEvents(state, EK_SINGLE_STEP, &basket, matchList, &matchCount);
+ }
+ if ((eventFlags & Dbg::kMethodEntry) != 0) {
+ findMatchingEvents(state, EK_METHOD_ENTRY, &basket, matchList, &matchCount);
+ }
+ if ((eventFlags & Dbg::kMethodExit) != 0) {
+ findMatchingEvents(state, EK_METHOD_EXIT, &basket, matchList, &matchCount);
+ }
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+ << basket.className << "." << Dbg::GetMethodName(pLoc->classId, pLoc->methodId)
+ << " thread=" << (void*) basket.threadId << " code=" << (void*) pLoc->idx << ")";
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << 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);
+ AddLocation(pReq, pLoc);
+ }
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ free(nameAlloc);
+ return matchCount != 0;
+}
+
+/*
+ * A thread is starting or stopping.
+ *
+ * Valid mods:
+ * Count, ThreadOnly
+ */
+bool PostThreadChange(JdwpState* state, ObjectId threadId, bool start) {
+ CHECK_EQ(threadId, Dbg::GetThreadSelfId());
+
+ /*
+ * I don't think this can happen.
+ */
+ if (invokeInProgress(state)) {
+ LOG(WARNING) << "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;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+ << "thread=" << (void*) basket.threadId << ")";
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << 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 = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ return matchCount != 0;
+}
+
+/*
+ * Send a polite "VM is dying" message to the debugger.
+ *
+ * Skips the usual "event token" stuff.
+ */
+bool PostVMDeath(JdwpState* state) {
+ LOG(VERBOSE) << "EVENT: " << EK_VM_DEATH;
+
+ ExpandBuf* pReq = eventPrep();
+ expandBufAdd1(pReq, SP_NONE);
+ expandBufAdd4BE(pReq, 1);
+
+ expandBufAdd1(pReq, EK_VM_DEATH);
+ expandBufAdd4BE(pReq, 0);
+ eventFinish(state, pReq);
+ return true;
+}
+
+/*
+ * 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 PostException(JdwpState* state, const JdwpLocation* pThrowLoc,
+ ObjectId exceptionId, RefTypeId exceptionClassId,
+ const JdwpLocation* pCatchLoc, ObjectId thisPtr)
+{
+ ModBasket basket;
+ char* nameAlloc = NULL;
+
+ memset(&basket, 0, sizeof(basket));
+ basket.pLoc = pThrowLoc;
+ basket.classId = pThrowLoc->classId;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(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)) {
+ LOG(VERBOSE) << "Not posting exception hit during invoke (" << 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;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total)"
+ << " thread=" << (void*) basket.threadId
+ << " exceptId=" << (void*) exceptionId
+ << " caught=" << basket.caught << ")";
+ LOG(VERBOSE) << StringPrintf(" throw: %d %llx %x %lld (%s.%s)", pThrowLoc->typeTag,
+ pThrowLoc->classId, pThrowLoc->methodId, pThrowLoc->idx,
+ Dbg::GetClassDescriptor(pThrowLoc->classId),
+ Dbg::GetMethodName(pThrowLoc->classId, pThrowLoc->methodId));
+ if (pCatchLoc->classId == 0) {
+ LOG(VERBOSE) << " catch: (not caught)";
+ } else {
+ LOG(VERBOSE) << StringPrintf(" catch: %d %llx %x %lld (%s.%s)", pCatchLoc->typeTag,
+ pCatchLoc->classId, pCatchLoc->methodId, pCatchLoc->idx,
+ Dbg::GetClassDescriptor(pCatchLoc->classId),
+ Dbg::GetMethodName(pCatchLoc->classId, pCatchLoc->methodId));
+ }
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << 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);
+
+ AddLocation(pReq, pThrowLoc);
+ expandBufAdd1(pReq, JT_OBJECT);
+ expandBufAdd8BE(pReq, exceptionId);
+ AddLocation(pReq, pCatchLoc);
+ }
+
+ /* don't let the GC discard it */
+ Dbg::RegisterObjectId(exceptionId);
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ free(nameAlloc);
+ return matchCount != 0;
+}
+
+/*
+ * Announce that a class has been loaded.
+ *
+ * Valid mods:
+ * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude
+ */
+bool PostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId, const char* signature, int status) {
+ ModBasket basket;
+ char* nameAlloc = NULL;
+
+ memset(&basket, 0, sizeof(basket));
+ basket.classId = refTypeId;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(basket.classId));
+
+ /* suppress class prep caused by debugger */
+ if (invokeInProgress(state)) {
+ LOG(VERBOSE) << "Not posting class prep caused by invoke (" << 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;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+ << "thread=" << (void*) basket.threadId << ")";
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << 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.
+ */
+ LOG(VERBOSE) << " 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 uint8_t*) signature);
+ expandBufAdd4BE(pReq, status);
+ }
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ free(nameAlloc);
+ return matchCount != 0;
+}
+
+bool SendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+ return (*state->transport->sendBufferedRequest)(state, iov, iovcnt);
+}
+
+/*
+ * 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 DdmSendChunkV(JdwpState* state, int type, const iovec* iov, int iovcnt) {
+ uint8_t header[kJDWPHeaderLen + 8];
+ size_t dataLen = 0;
+
+ CHECK(iov != NULL);
+ CHECK(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.
+ */
+ 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, NextRequestSerial(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 = Dbg::ThreadWaiting();
+ SendBufferedRequest(state, wrapiov, iovcnt+1);
+ Dbg::ThreadContinuing(oldStatus);
+}
+
+} // namespace JDWP
+
+} // namespace art