summaryrefslogtreecommitdiffstats
path: root/runtime/jdwp
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/jdwp')
-rw-r--r--runtime/jdwp/jdwp.h91
-rw-r--r--runtime/jdwp/jdwp_event.cc74
-rw-r--r--runtime/jdwp/jdwp_handler.cc60
-rw-r--r--runtime/jdwp/jdwp_main.cc16
4 files changed, 105 insertions, 136 deletions
diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
index 9f37998..e16221c 100644
--- a/runtime/jdwp/jdwp.h
+++ b/runtime/jdwp/jdwp.h
@@ -149,29 +149,19 @@ struct JdwpState {
void ExitAfterReplying(int exit_status);
- /*
- * When we hit a debugger event that requires suspension, it's important
- * that we wait for the thread to suspend itself before processing any
- * additional requests. (Otherwise, if the debugger immediately sends a
- * "resume thread" command, the resume might arrive before the thread has
- * suspended itself.)
- *
- * The thread should call the "set" function before sending the event to
- * the debugger. The main JDWP handler loop calls "get" before processing
- * an event, and will wait for thread suspension if it's set. Once the
- * thread has suspended itself, the JDWP handler calls "clear" and
- * continues processing the current event. This works in the suspend-all
- * case because the event thread doesn't suspend itself until everything
- * else has suspended.
- *
- * It's possible that multiple threads could encounter thread-suspending
- * events at the same time, so we grab a mutex in the "set" call, and
- * release it in the "clear" call.
- */
- // ObjectId GetWaitForEventThread();
- void SetWaitForEventThread(ObjectId threadId)
- LOCKS_EXCLUDED(event_thread_lock_, process_request_lock_);
- void ClearWaitForEventThread() LOCKS_EXCLUDED(event_thread_lock_);
+ // Acquires/releases the JDWP synchronization token for the debugger
+ // thread (command handler) so no event thread posts an event while
+ // it processes a command. This must be called only from the debugger
+ // thread.
+ void AcquireJdwpTokenForCommand() LOCKS_EXCLUDED(jdwp_token_lock_);
+ void ReleaseJdwpTokenForCommand() LOCKS_EXCLUDED(jdwp_token_lock_);
+
+ // Acquires/releases the JDWP synchronization token for the event thread
+ // so no other thread (debugger thread or event thread) interleaves with
+ // it when posting an event. This must NOT be called from the debugger
+ // thread, only event thread.
+ void AcquireJdwpTokenForEvent(ObjectId threadId) LOCKS_EXCLUDED(jdwp_token_lock_);
+ void ReleaseJdwpTokenForEvent() LOCKS_EXCLUDED(jdwp_token_lock_);
/*
* These notify the debug code that something interesting has happened. This
@@ -330,9 +320,37 @@ struct JdwpState {
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
void SendBufferedRequest(uint32_t type, const std::vector<iovec>& iov);
- void StartProcessingRequest() LOCKS_EXCLUDED(process_request_lock_);
- void EndProcessingRequest() LOCKS_EXCLUDED(process_request_lock_);
- void WaitForProcessingRequest() LOCKS_EXCLUDED(process_request_lock_);
+ /*
+ * When we hit a debugger event that requires suspension, it's important
+ * that we wait for the thread to suspend itself before processing any
+ * additional requests. Otherwise, if the debugger immediately sends a
+ * "resume thread" command, the resume might arrive before the thread has
+ * suspended itself.
+ *
+ * It's also important no event thread suspends while we process a command
+ * from the debugger. Otherwise we could post an event ("thread death")
+ * before sending the reply of the command being processed ("resume") and
+ * cause bad synchronization with the debugger.
+ *
+ * The thread wanting "exclusive" access to the JDWP world must call the
+ * SetWaitForJdwpToken method before processing a command from the
+ * debugger or sending an event to the debugger.
+ * Once the command is processed or the event thread has posted its event,
+ * it must call the ClearWaitForJdwpToken method to allow another thread
+ * to do JDWP stuff.
+ *
+ * Therefore the main JDWP handler loop will wait for the event thread
+ * suspension before processing the next command. Once the event thread
+ * has suspended itself and cleared the token, the JDWP handler continues
+ * processing commands. This works in the suspend-all case because the
+ * event thread doesn't suspend itself until everything else has suspended.
+ *
+ * It's possible that multiple threads could encounter thread-suspending
+ * events at the same time, so we grab a mutex in the SetWaitForJdwpToken
+ * call, and release it in the ClearWaitForJdwpToken call.
+ */
+ void SetWaitForJdwpToken(ObjectId threadId) LOCKS_EXCLUDED(jdwp_token_lock_);
+ void ClearWaitForJdwpToken() LOCKS_EXCLUDED(jdwp_token_lock_);
public: // TODO: fix privacy
const JdwpOptions* options_;
@@ -368,24 +386,21 @@ struct JdwpState {
// Linked list of events requested by the debugger (breakpoints, class prep, etc).
Mutex event_list_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_BEFORE(Locks::breakpoint_lock_);
-
JdwpEvent* event_list_ GUARDED_BY(event_list_lock_);
size_t event_list_size_ GUARDED_BY(event_list_lock_); // Number of elements in event_list_.
- // Used to synchronize suspension of the event thread (to avoid receiving "resume"
- // events before the thread has finished suspending itself).
- Mutex event_thread_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
- ConditionVariable event_thread_cond_ GUARDED_BY(event_thread_lock_);
- ObjectId event_thread_id_;
-
- // Used to synchronize request processing and event sending (to avoid sending an event before
- // sending the reply of a command being processed).
- Mutex process_request_lock_ ACQUIRED_AFTER(event_thread_lock_);
- ConditionVariable process_request_cond_ GUARDED_BY(process_request_lock_);
- bool processing_request_ GUARDED_BY(process_request_lock_);
+ // Used to synchronize JDWP command handler thread and event threads so only one
+ // thread does JDWP stuff at a time. This prevent from interleaving command handling
+ // and event notification. Otherwise we could receive a "resume" command for an
+ // event thread that is not suspended yet, or post a "thread death" or event "VM death"
+ // event before sending the reply of the "resume" command that caused it.
+ Mutex jdwp_token_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ ConditionVariable jdwp_token_cond_ GUARDED_BY(jdwp_token_lock_);
+ ObjectId jdwp_token_owner_thread_id_;
bool ddm_is_active_;
+ // Used for VirtualMachine.Exit command handling.
bool should_exit_;
int exit_status_;
};
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index a8eaa26..b71f6cd 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -612,7 +612,7 @@ void JdwpState::SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId
}
/* grab this before posting/suspending again */
- SetWaitForEventThread(thread_self_id);
+ AcquireJdwpTokenForEvent(thread_self_id);
/* leave pReq->invoke_needed_ raised so we can check reentrancy */
Dbg::ExecuteMethod(pReq);
@@ -630,7 +630,7 @@ void JdwpState::SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy
JDWP::ObjectId thread_self_id = Dbg::GetThreadSelfId();
self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend);
if (suspend_policy != SP_NONE) {
- SetWaitForEventThread(threadId);
+ AcquireJdwpTokenForEvent(threadId);
}
EventFinish(pReq);
SuspendByPolicy(suspend_policy, thread_self_id);
@@ -649,63 +649,82 @@ bool JdwpState::InvokeInProgress() {
return pReq->invoke_needed;
}
+void JdwpState::AcquireJdwpTokenForCommand() {
+ CHECK_EQ(Thread::Current(), GetDebugThread()) << "Expected debugger thread";
+ SetWaitForJdwpToken(debug_thread_id_);
+}
+
+void JdwpState::ReleaseJdwpTokenForCommand() {
+ CHECK_EQ(Thread::Current(), GetDebugThread()) << "Expected debugger thread";
+ ClearWaitForJdwpToken();
+}
+
+void JdwpState::AcquireJdwpTokenForEvent(ObjectId threadId) {
+ CHECK_NE(Thread::Current(), GetDebugThread()) << "Expected event thread";
+ CHECK_NE(debug_thread_id_, threadId) << "Not expected debug thread";
+ SetWaitForJdwpToken(threadId);
+}
+
+void JdwpState::ReleaseJdwpTokenForEvent() {
+ CHECK_NE(Thread::Current(), GetDebugThread()) << "Expected event thread";
+ ClearWaitForJdwpToken();
+}
+
/*
* 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) {
+void JdwpState::SetWaitForJdwpToken(ObjectId threadId) {
bool waited = false;
+ Thread* const self = Thread::Current();
+ CHECK_NE(threadId, 0u);
+ CHECK_NE(self->GetState(), kRunnable);
+ Locks::mutator_lock_->AssertNotHeld(self);
/* this is held for very brief periods; contention is unlikely */
- Thread* self = Thread::Current();
- MutexLock mu(self, event_thread_lock_);
+ MutexLock mu(self, jdwp_token_lock_);
+
+ CHECK_NE(jdwp_token_owner_thread_id_, threadId) << "Thread is already holding event thread lock";
/*
* If another thread is already doing stuff, wait for it. This can
* go to sleep indefinitely.
*/
- while (event_thread_id_ != 0) {
+ while (jdwp_token_owner_thread_id_ != 0) {
VLOG(jdwp) << StringPrintf("event in progress (%#" PRIx64 "), %#" PRIx64 " sleeping",
- event_thread_id_, threadId);
+ jdwp_token_owner_thread_id_, threadId);
waited = true;
- event_thread_cond_.Wait(self);
+ jdwp_token_cond_.Wait(self);
}
- if (waited || threadId != 0) {
+ if (waited || threadId != debug_thread_id_) {
VLOG(jdwp) << StringPrintf("event token grabbed (%#" PRIx64 ")", threadId);
}
- if (threadId != 0) {
- event_thread_id_ = threadId;
- }
+ jdwp_token_owner_thread_id_ = threadId;
}
/*
* Clear the threadId and signal anybody waiting.
*/
-void JdwpState::ClearWaitForEventThread() {
+void JdwpState::ClearWaitForJdwpToken() {
/*
* Grab the mutex. Don't try to go in/out of VMWAIT mode, as this
- * function is called by dvmSuspendSelf(), and the transition back
+ * function is called by Dbg::SuspendSelf(), 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 (%#" PRIx64 ")", event_thread_id_);
+ Thread* const self = Thread::Current();
+ MutexLock mu(self, jdwp_token_lock_);
- event_thread_id_ = 0;
+ CHECK_NE(jdwp_token_owner_thread_id_, 0U);
+ VLOG(jdwp) << StringPrintf("cleared event token (%#" PRIx64 ")", jdwp_token_owner_thread_id_);
- event_thread_cond_.Signal(self);
+ jdwp_token_owner_thread_id_ = 0;
+ jdwp_token_cond_.Signal(self);
}
-
/*
* Prep an event. Allocates storage for the message and leaves space for
* the header.
@@ -730,11 +749,6 @@ void JdwpState::EventFinish(ExpandBuf* pReq) {
Set1(buf + 9, kJdwpEventCommandSet);
Set1(buf + 10, kJdwpCompositeCommand);
- // Prevents from interleaving commands and events. Otherwise we could end up in sending an event
- // before sending the reply of the command being processed and would lead to bad synchronization
- // between the debugger and the debuggee.
- WaitForProcessingRequest();
-
SendRequest(pReq);
expandBufFree(pReq);
diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc
index a1d2a6c..0ce4de7 100644
--- a/runtime/jdwp/jdwp_handler.cc
+++ b/runtime/jdwp/jdwp_handler.cc
@@ -1633,27 +1633,15 @@ size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply) {
/*
* If a debugger event has fired in another thread, wait until the
- * initiating thread has suspended itself before processing messages
+ * initiating thread has suspended itself before processing commands
* from the debugger. Otherwise we (the JDWP thread) could be told to
* resume the thread before it has suspended.
*
- * We call with an argument of zero to wait for the current event
- * thread to finish, and then clear the block. Depending on the thread
- * suspend policy, this may allow events in other threads to fire,
- * but those events have no bearing on what the debugger has sent us
- * in the current request->
- *
* Note that we MUST clear the event token before waking the event
* thread up, or risk waiting for the thread to suspend after we've
* told it to resume.
*/
- SetWaitForEventThread(0);
-
- /*
- * We do not want events to be sent while we process a request-> Indicate the JDWP thread starts
- * to process a request so other threads wait for it to finish before sending an event.
- */
- StartProcessingRequest();
+ AcquireJdwpTokenForCommand();
/*
* Tell the VM that we're running and shouldn't be interrupted by GC.
@@ -1719,50 +1707,6 @@ size_t JdwpState::ProcessRequest(Request* request, ExpandBuf* pReply) {
return replyLength;
}
-/*
- * Indicates a request is about to be processed. If a thread wants to send an event in the meantime,
- * it will need to wait until we processed this request (see EndProcessingRequest).
- */
-void JdwpState::StartProcessingRequest() {
- Thread* self = Thread::Current();
- CHECK_EQ(self, GetDebugThread()) << "Requests are only processed by debug thread";
- MutexLock mu(self, process_request_lock_);
- CHECK_EQ(processing_request_, false);
- processing_request_ = true;
-}
-
-/*
- * Indicates a request has been processed (and we sent its reply). All threads waiting for us (see
- * WaitForProcessingRequest) are waken up so they can send events again.
- */
-void JdwpState::EndProcessingRequest() {
- Thread* self = Thread::Current();
- CHECK_EQ(self, GetDebugThread()) << "Requests are only processed by debug thread";
- MutexLock mu(self, process_request_lock_);
- CHECK_EQ(processing_request_, true);
- processing_request_ = false;
- process_request_cond_.Broadcast(self);
-}
-
-/*
- * Waits for any request being processed so we do not send an event in the meantime.
- */
-void JdwpState::WaitForProcessingRequest() {
- Thread* self = Thread::Current();
- CHECK_NE(self, GetDebugThread()) << "Events should not be posted by debug thread";
- MutexLock mu(self, process_request_lock_);
- bool waited = false;
- while (processing_request_) {
- VLOG(jdwp) << StringPrintf("wait for processing request");
- waited = true;
- process_request_cond_.Wait(self);
- }
- if (waited) {
- VLOG(jdwp) << StringPrintf("finished waiting for processing request");
- }
- CHECK_EQ(processing_request_, false);
-}
-
} // namespace JDWP
} // namespace art
diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc
index b04aa6e..b6fedd9 100644
--- a/runtime/jdwp/jdwp_main.cc
+++ b/runtime/jdwp/jdwp_main.cc
@@ -220,12 +220,9 @@ JdwpState::JdwpState(const JdwpOptions* options)
event_list_lock_("JDWP event list lock", kJdwpEventListLock),
event_list_(nullptr),
event_list_size_(0),
- event_thread_lock_("JDWP event thread lock"),
- event_thread_cond_("JDWP event thread condition variable", event_thread_lock_),
- event_thread_id_(0),
- process_request_lock_("JDWP process request lock"),
- process_request_cond_("JDWP process request condition variable", process_request_lock_),
- processing_request_(false),
+ jdwp_token_lock_("JDWP token lock"),
+ jdwp_token_cond_("JDWP token condition variable", jdwp_token_lock_),
+ jdwp_token_owner_thread_id_(0),
ddm_is_active_(false),
should_exit_(false),
exit_status_(0) {
@@ -331,7 +328,7 @@ void JdwpState::ResetState() {
* Should not have one of these in progress. If the debugger went away
* mid-request, though, we could see this.
*/
- if (event_thread_id_ != 0) {
+ if (jdwp_token_owner_thread_id_ != 0) {
LOG(WARNING) << "Resetting state while event in progress";
DCHECK(false);
}
@@ -382,10 +379,9 @@ bool JdwpState::HandlePacket() {
ssize_t cc = netStateBase->WritePacket(pReply, replyLength);
/*
- * We processed this request and sent its reply. Notify other threads waiting for us they can now
- * send events.
+ * We processed this request and sent its reply so we can release the JDWP token.
*/
- EndProcessingRequest();
+ ReleaseJdwpTokenForCommand();
if (cc != static_cast<ssize_t>(replyLength)) {
PLOG(ERROR) << "Failed sending reply to debugger";