diff options
Diffstat (limited to 'runtime/jdwp/jdwp_event.cc')
-rw-r--r-- | runtime/jdwp/jdwp_event.cc | 1093 |
1 files changed, 1093 insertions, 0 deletions
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc new file mode 100644 index 0000000..77434e1 --- /dev/null +++ b/runtime/jdwp/jdwp_event.cc @@ -0,0 +1,1093 @@ +/* + * 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. + */ + +#include "jdwp/jdwp_event.h" + +#include <stddef.h> /* for offsetof() */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/stringprintf.h" +#include "debugger.h" +#include "jdwp/jdwp_constants.h" +#include "jdwp/jdwp_expand_buf.h" +#include "jdwp/jdwp_priv.h" +#include "thread-inl.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 { + ModBasket() : pLoc(NULL), threadId(0), classId(0), excepClassId(0), + caught(false), field(0), thisPtr(0) { } + + const JdwpLocation* pLoc; /* LocationOnly */ + std::string 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 */ +}; + +/* + * 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->suspend_policy << " modCount=" << pEvent->modCount; + + for (int i = 0; i < pEvent->modCount; i++) { + const JdwpEventMod* pMod = &pEvent->mods[i]; + LOG(INFO) << " " << 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 JdwpState::RegisterEvent(JdwpEvent* pEvent) { + 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); + JdwpError status = Dbg::ConfigureStep(pMod->step.threadId, size, depth); + if (status != ERR_NONE) { + return status; + } + } 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. + */ + MutexLock mu(Thread::Current(), event_list_lock_); + if (event_list_ != NULL) { + pEvent->next = event_list_; + event_list_->prev = pEvent; + } + event_list_ = pEvent; + ++event_list_size_; + + 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. + */ +void JdwpState::UnregisterEvent(JdwpEvent* pEvent) { + if (pEvent->prev == NULL) { + /* head of the list */ + CHECK(event_list_ == pEvent); + + event_list_ = 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); + } + } + + --event_list_size_; + CHECK(event_list_size_ != 0 || event_list_ == 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 JdwpState::UnregisterEventById(uint32_t requestId) { + MutexLock mu(Thread::Current(), event_list_lock_); + + JdwpEvent* pEvent = event_list_; + while (pEvent != NULL) { + if (pEvent->requestId == requestId) { + UnregisterEvent(pEvent); + EventFree(pEvent); + return; /* there can be only one with a given ID */ + } + + pEvent = pEvent->next; + } + + //LOGD("Odd: no match when removing event reqId=0x%04x", requestId); +} + +/* + * Remove all entries from the event list. + */ +void JdwpState::UnregisterAll() { + MutexLock mu(Thread::Current(), event_list_lock_); + + JdwpEvent* pEvent = event_list_; + while (pEvent != NULL) { + JdwpEvent* pNextEvent = pEvent->next; + + UnregisterEvent(pEvent); + EventFree(pEvent); + pEvent = pNextEvent; + } + + event_list_ = NULL; +} + +/* + * 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->event_list_ != 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(size_t event_count) { + return new JdwpEvent*[event_count]; +} + +/* + * Run through the list and remove any entries with an expired "count" mod + * from the event list, then free the match list. + */ +void JdwpState::CleanupMatchList(JdwpEvent** match_list, int match_count) { + JdwpEvent** ppEvent = match_list; + + while (match_count--) { + JdwpEvent* pEvent = *ppEvent; + + for (int i = 0; i < pEvent->modCount; i++) { + if (pEvent->mods[i].modKind == MK_COUNT && pEvent->mods[i].count.count == 0) { + VLOG(jdwp) << "##### Removing expired event"; + UnregisterEvent(pEvent); + EventFree(pEvent); + break; + } + } + + ppEvent++; + } + + delete[] match_list; +} + +/* + * 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 std::string& target) { + size_t patLen = strlen(pattern); + if (pattern[0] == '*') { + patLen--; + if (target.size() < patLen) { + return false; + } + return strcmp(pattern+1, target.c_str() + (target.size()-patLen)) == 0; + } else if (pattern[patLen-1] == '*') { + return strncmp(pattern, target.c_str(), patLen-1) == 0; + } else { + return strcmp(pattern, target.c_str()) == 0; + } +} + +/* + * 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(JdwpEvent* pEvent, ModBasket* basket) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + 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 (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(FATAL) << "unknown mod kind " << pMod->modKind; + 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 "match_list", 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. + */ +void JdwpState::FindMatchingEvents(JdwpEventKind eventKind, ModBasket* basket, + JdwpEvent** match_list, int* pMatchCount) { + /* start after the existing entries */ + match_list += *pMatchCount; + + JdwpEvent* pEvent = event_list_; + while (pEvent != NULL) { + if (pEvent->eventKind == eventKind && ModsMatch(pEvent, basket)) { + *match_list++ = pEvent; + (*pMatchCount)++; + } + + pEvent = pEvent->next; + } +} + +/* + * Scan through the list of matches and determine the most severe + * suspension policy. + */ +static JdwpSuspendPolicy scanSuspendPolicy(JdwpEvent** match_list, int match_count) { + JdwpSuspendPolicy policy = SP_NONE; + + while (match_count--) { + if ((*match_list)->suspend_policy > policy) { + policy = (*match_list)->suspend_policy; + } + match_list++; + } + + return policy; +} + +/* + * Three possibilities: + * SP_NONE - do nothing + * SP_EVENT_THREAD - suspend ourselves + * SP_ALL - suspend everybody except JDWP support thread + */ +void JdwpState::SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId thread_self_id) { + VLOG(jdwp) << "SuspendByPolicy(" << suspend_policy << ")"; + if (suspend_policy == SP_NONE) { + return; + } + + if (suspend_policy == SP_ALL) { + Dbg::SuspendVM(); + } else { + CHECK_EQ(suspend_policy, SP_EVENT_THREAD); + } + + /* this is rare but possible -- see CLASS_PREPARE handling */ + if (thread_self_id == debug_thread_id_) { + 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->invoke_needed_) { + /*LOGD("SuspendByPolicy: no invoke needed");*/ + break; + } + + /* grab this before posting/suspending again */ + SetWaitForEventThread(thread_self_id); + + /* leave pReq->invoke_needed_ raised so we can check reentrancy */ + Dbg::ExecuteMethod(pReq); + + pReq->error = ERR_NONE; + + /* clear this before signaling */ + pReq->invoke_needed_ = false; + + VLOG(jdwp) << "invoke complete, signaling and self-suspending"; + Thread* self = Thread::Current(); + MutexLock mu(self, pReq->lock_); + pReq->cond_.Signal(self); + } +} + +void JdwpState::SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy suspend_policy, + ObjectId threadId) { + Thread* self = Thread::Current(); + self->AssertThreadSuspensionIsAllowable(); + /* send request and possibly suspend ourselves */ + if (pReq != NULL) { + JDWP::ObjectId thread_self_id = Dbg::GetThreadSelfId(); + self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend); + if (suspend_policy != SP_NONE) { + SetWaitForEventThread(threadId); + } + EventFinish(pReq); + SuspendByPolicy(suspend_policy, thread_self_id); + self->TransitionFromSuspendedToRunnable(); + } +} + +/* + * Determine if there is a method invocation in progress in the current + * thread. + * + * We look at the "invoke_needed" flag in the per-thread DebugInvokeReq + * state. If set, we're in the process of invoking a method. + */ +bool JdwpState::InvokeInProgress() { + DebugInvokeReq* pReq = Dbg::GetInvokeReq(); + return pReq->invoke_needed_; +} + +/* + * 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 JdwpState::SetWaitForEventThread(ObjectId threadId) { + bool waited = false; + + /* this is held for very brief periods; contention is unlikely */ + Thread* self = Thread::Current(); + MutexLock mu(self, event_thread_lock_); + + /* + * If another thread is already doing stuff, wait for it. This can + * go to sleep indefinitely. + */ + while (event_thread_id_ != 0) { + VLOG(jdwp) << StringPrintf("event in progress (%#llx), %#llx sleeping", event_thread_id_, threadId); + waited = true; + event_thread_cond_.Wait(self); + } + + if (waited || threadId != 0) { + VLOG(jdwp) << StringPrintf("event token grabbed (%#llx)", threadId); + } + if (threadId != 0) { + event_thread_id_ = threadId; + } +} + +/* + * Clear the threadId and signal anybody waiting. + */ +void JdwpState::ClearWaitForEventThread() { + /* + * 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. + */ + Thread* self = Thread::Current(); + MutexLock mu(self, event_thread_lock_); + + CHECK_NE(event_thread_id_, 0U); + VLOG(jdwp) << StringPrintf("cleared event token (%#llx)", event_thread_id_); + + event_thread_id_ = 0; + + event_thread_cond_.Signal(self); +} + + +/* + * 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). + */ +void JdwpState::EventFinish(ExpandBuf* pReq) { + uint8_t* buf = expandBufGetBuffer(pReq); + + Set4BE(buf, expandBufGetLength(pReq)); + Set4BE(buf+4, NextRequestSerial()); + Set1(buf+8, 0); /* flags */ + Set1(buf+9, kJdwpEventCommandSet); + Set1(buf+10, kJdwpCompositeCommand); + + SendRequest(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 JdwpState::PostVMStart() { + JdwpSuspendPolicy suspend_policy; + ObjectId threadId = Dbg::GetThreadSelfId(); + + if (options_->suspend) { + suspend_policy = SP_ALL; + } else { + suspend_policy = SP_NONE; + } + + ExpandBuf* pReq = eventPrep(); + { + MutexLock mu(Thread::Current(), event_list_lock_); // probably don't need this here + + VLOG(jdwp) << "EVENT: " << EK_VM_START; + VLOG(jdwp) << " suspend_policy=" << suspend_policy; + + expandBufAdd1(pReq, suspend_policy); + expandBufAdd4BE(pReq, 1); + + expandBufAdd1(pReq, EK_VM_START); + expandBufAdd4BE(pReq, 0); /* requestId */ + expandBufAdd8BE(pReq, threadId); + } + + /* send request and possibly suspend ourselves */ + SendRequestAndPossiblySuspend(pReq, suspend_policy, threadId); + + return true; +} + +/* + * 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 JdwpState::PostLocationEvent(const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) { + ModBasket basket; + basket.pLoc = pLoc; + basket.classId = pLoc->class_id; + basket.thisPtr = thisPtr; + basket.threadId = Dbg::GetThreadSelfId(); + basket.className = Dbg::GetClassName(pLoc->class_id); + + /* + * 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 == debug_thread_id_) { + VLOG(jdwp) << "Ignoring location event in JDWP thread"; + 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()) { + VLOG(jdwp) << "Not checking breakpoints during invoke (" << basket.className << ")"; + return false; + } + + JdwpEvent** match_list = NULL; + int match_count = 0; + ExpandBuf* pReq = NULL; + JdwpSuspendPolicy suspend_policy = SP_NONE; + + { + MutexLock mu(Thread::Current(), event_list_lock_); + match_list = AllocMatchList(event_list_size_); + if ((eventFlags & Dbg::kBreakpoint) != 0) { + FindMatchingEvents(EK_BREAKPOINT, &basket, match_list, &match_count); + } + if ((eventFlags & Dbg::kSingleStep) != 0) { + FindMatchingEvents(EK_SINGLE_STEP, &basket, match_list, &match_count); + } + if ((eventFlags & Dbg::kMethodEntry) != 0) { + FindMatchingEvents(EK_METHOD_ENTRY, &basket, match_list, &match_count); + } + if ((eventFlags & Dbg::kMethodExit) != 0) { + FindMatchingEvents(EK_METHOD_EXIT, &basket, match_list, &match_count); + + // TODO: match EK_METHOD_EXIT_WITH_RETURN_VALUE too; we need to include the 'value', though. + //FindMatchingEvents(EK_METHOD_EXIT_WITH_RETURN_VALUE, &basket, match_list, &match_count); + } + if (match_count != 0) { + VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total) " + << basket.className << "." << Dbg::GetMethodName(pLoc->method_id) + << StringPrintf(" thread=%#llx dex_pc=%#llx)", basket.threadId, pLoc->dex_pc); + + suspend_policy = scanSuspendPolicy(match_list, match_count); + VLOG(jdwp) << " suspend_policy=" << suspend_policy; + + pReq = eventPrep(); + expandBufAdd1(pReq, suspend_policy); + expandBufAdd4BE(pReq, match_count); + + for (int i = 0; i < match_count; i++) { + expandBufAdd1(pReq, match_list[i]->eventKind); + expandBufAdd4BE(pReq, match_list[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + expandBufAddLocation(pReq, *pLoc); + } + } + + CleanupMatchList(match_list, match_count); + } + + SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId); + return match_count != 0; +} + +/* + * A thread is starting or stopping. + * + * Valid mods: + * Count, ThreadOnly + */ +bool JdwpState::PostThreadChange(ObjectId threadId, bool start) { + CHECK_EQ(threadId, Dbg::GetThreadSelfId()); + + /* + * I don't think this can happen. + */ + if (InvokeInProgress()) { + LOG(WARNING) << "Not posting thread change during invoke"; + return false; + } + + ModBasket basket; + basket.threadId = threadId; + + ExpandBuf* pReq = NULL; + JdwpSuspendPolicy suspend_policy = SP_NONE; + int match_count = 0; + { + // Don't allow the list to be updated while we scan it. + MutexLock mu(Thread::Current(), event_list_lock_); + JdwpEvent** match_list = AllocMatchList(event_list_size_); + + if (start) { + FindMatchingEvents(EK_THREAD_START, &basket, match_list, &match_count); + } else { + FindMatchingEvents(EK_THREAD_DEATH, &basket, match_list, &match_count); + } + + if (match_count != 0) { + VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total) " + << StringPrintf("thread=%#llx", basket.threadId) << ")"; + + suspend_policy = scanSuspendPolicy(match_list, match_count); + VLOG(jdwp) << " suspend_policy=" << suspend_policy; + + pReq = eventPrep(); + expandBufAdd1(pReq, suspend_policy); + expandBufAdd4BE(pReq, match_count); + + for (int i = 0; i < match_count; i++) { + expandBufAdd1(pReq, match_list[i]->eventKind); + expandBufAdd4BE(pReq, match_list[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + } + } + + CleanupMatchList(match_list, match_count); + } + + SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId); + + return match_count != 0; +} + +/* + * Send a polite "VM is dying" message to the debugger. + * + * Skips the usual "event token" stuff. + */ +bool JdwpState::PostVMDeath() { + VLOG(jdwp) << "EVENT: " << EK_VM_DEATH; + + ExpandBuf* pReq = eventPrep(); + expandBufAdd1(pReq, SP_NONE); + expandBufAdd4BE(pReq, 1); + + expandBufAdd1(pReq, EK_VM_DEATH); + expandBufAdd4BE(pReq, 0); + EventFinish(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 JdwpState::PostException(const JdwpLocation* pThrowLoc, + ObjectId exceptionId, RefTypeId exceptionClassId, + const JdwpLocation* pCatchLoc, ObjectId thisPtr) { + ModBasket basket; + + basket.pLoc = pThrowLoc; + basket.classId = pThrowLoc->class_id; + basket.threadId = Dbg::GetThreadSelfId(); + basket.className = Dbg::GetClassName(basket.classId); + basket.excepClassId = exceptionClassId; + basket.caught = (pCatchLoc->class_id != 0); + basket.thisPtr = thisPtr; + + /* don't try to post an exception caused by the debugger */ + if (InvokeInProgress()) { + VLOG(jdwp) << "Not posting exception hit during invoke (" << basket.className << ")"; + return false; + } + + JdwpEvent** match_list = NULL; + int match_count = 0; + ExpandBuf* pReq = NULL; + JdwpSuspendPolicy suspend_policy = SP_NONE; + { + MutexLock mu(Thread::Current(), event_list_lock_); + match_list = AllocMatchList(event_list_size_); + FindMatchingEvents(EK_EXCEPTION, &basket, match_list, &match_count); + if (match_count != 0) { + VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total)" + << StringPrintf(" thread=%#llx", basket.threadId) + << StringPrintf(" exceptId=%#llx", exceptionId) + << " caught=" << basket.caught << ")" + << " throw: " << *pThrowLoc; + if (pCatchLoc->class_id == 0) { + VLOG(jdwp) << " catch: (not caught)"; + } else { + VLOG(jdwp) << " catch: " << *pCatchLoc; + } + + suspend_policy = scanSuspendPolicy(match_list, match_count); + VLOG(jdwp) << " suspend_policy=" << suspend_policy; + + pReq = eventPrep(); + expandBufAdd1(pReq, suspend_policy); + expandBufAdd4BE(pReq, match_count); + + for (int i = 0; i < match_count; i++) { + expandBufAdd1(pReq, match_list[i]->eventKind); + expandBufAdd4BE(pReq, match_list[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + + expandBufAddLocation(pReq, *pThrowLoc); + expandBufAdd1(pReq, JT_OBJECT); + expandBufAdd8BE(pReq, exceptionId); + expandBufAddLocation(pReq, *pCatchLoc); + } + } + + CleanupMatchList(match_list, match_count); + } + + SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId); + + return match_count != 0; +} + +/* + * Announce that a class has been loaded. + * + * Valid mods: + * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude + */ +bool JdwpState::PostClassPrepare(JdwpTypeTag tag, RefTypeId refTypeId, const std::string& signature, + int status) { + ModBasket basket; + + basket.classId = refTypeId; + basket.threadId = Dbg::GetThreadSelfId(); + basket.className = Dbg::GetClassName(basket.classId); + + /* suppress class prep caused by debugger */ + if (InvokeInProgress()) { + VLOG(jdwp) << "Not posting class prep caused by invoke (" << basket.className << ")"; + return false; + } + + ExpandBuf* pReq = NULL; + JdwpSuspendPolicy suspend_policy = SP_NONE; + int match_count = 0; + { + MutexLock mu(Thread::Current(), event_list_lock_); + JdwpEvent** match_list = AllocMatchList(event_list_size_); + FindMatchingEvents(EK_CLASS_PREPARE, &basket, match_list, &match_count); + if (match_count != 0) { + VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total) " + << StringPrintf("thread=%#llx", basket.threadId) << ") " << signature; + + suspend_policy = scanSuspendPolicy(match_list, match_count); + VLOG(jdwp) << " suspend_policy=" << suspend_policy; + + if (basket.threadId == debug_thread_id_) { + /* + * 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. + */ + VLOG(jdwp) << " NOTE: class prepare in debugger thread!"; + basket.threadId = 0; + if (suspend_policy == SP_EVENT_THREAD) { + suspend_policy = SP_ALL; + } + } + + pReq = eventPrep(); + expandBufAdd1(pReq, suspend_policy); + expandBufAdd4BE(pReq, match_count); + + for (int i = 0; i < match_count; i++) { + expandBufAdd1(pReq, match_list[i]->eventKind); + expandBufAdd4BE(pReq, match_list[i]->requestId); + expandBufAdd8BE(pReq, basket.threadId); + + expandBufAdd1(pReq, tag); + expandBufAdd8BE(pReq, refTypeId); + expandBufAddUtf8String(pReq, signature); + expandBufAdd4BE(pReq, status); + } + } + CleanupMatchList(match_list, match_count); + } + + SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId); + + return match_count != 0; +} + +/* + * 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 JdwpState::DdmSendChunkV(uint32_t type, const iovec* iov, int iov_count) { + uint8_t header[kJDWPHeaderLen + 8]; + size_t dataLen = 0; + + CHECK(iov != NULL); + CHECK_GT(iov_count, 0); + CHECK_LT(iov_count, 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[iov_count+1]; + for (int i = 0; i < iov_count; 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()); + 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); + + // Try to avoid blocking GC during a send, but only safe when not using mutexes at a lower-level + // than mutator for lock ordering reasons. + Thread* self = Thread::Current(); + bool safe_to_release_mutator_lock_over_send = !Locks::mutator_lock_->IsExclusiveHeld(self); + if (safe_to_release_mutator_lock_over_send) { + for (size_t i=0; i < kMutatorLock; ++i) { + if (self->GetHeldMutex(static_cast<LockLevel>(i)) != NULL) { + safe_to_release_mutator_lock_over_send = false; + break; + } + } + } + if (safe_to_release_mutator_lock_over_send) { + // Change state to waiting to allow GC, ... while we're sending. + self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend); + SendBufferedRequest(type, wrapiov, iov_count + 1); + self->TransitionFromSuspendedToRunnable(); + } else { + // Send and possibly block GC... + SendBufferedRequest(type, wrapiov, iov_count + 1); + } +} + +} // namespace JDWP + +} // namespace art |