Merge "More debugger support." into dalvik-dev
diff --git a/src/debugger.cc b/src/debugger.cc
index 7c26bd0..7d6d344 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -18,8 +18,35 @@
#include <sys/uio.h>
+#include "thread_list.h"
+
namespace art {
+class ObjectRegistry {
+ public:
+ ObjectRegistry() : lock_("ObjectRegistry lock") {
+ }
+
+ JDWP::ObjectId Add(Object* o) {
+ if (o == NULL) {
+ return 0;
+ }
+ JDWP::ObjectId id = static_cast<JDWP::ObjectId>(reinterpret_cast<uintptr_t>(o));
+ MutexLock mu(lock_);
+ map_[id] = o;
+ return id;
+ }
+
+ bool Contains(JDWP::ObjectId id) {
+ MutexLock mu(lock_);
+ return map_.find(id) != map_.end();
+ }
+
+ private:
+ Mutex lock_;
+ std::map<JDWP::ObjectId, Object*> map_;
+};
+
// JDWP is allowed unless the Zygote forbids it.
static bool gJdwpAllowed = true;
@@ -34,6 +61,8 @@
static bool gDebuggerConnected; // debugger or DDMS is connected.
static bool gDebuggerActive; // debugger is making requests.
+static ObjectRegistry* gRegistry = NULL;
+
/*
* Handle one of the JDWP name/value pairs.
*
@@ -146,12 +175,16 @@
return;
}
+ CHECK(gRegistry == NULL);
+ gRegistry = new ObjectRegistry;
+
// Init JDWP if the debugger is enabled. This may connect out to a
// debugger, passively listen for a debugger, or block waiting for a
// debugger.
gJdwpState = JDWP::JdwpState::Create(&gJdwpOptions);
if (gJdwpState == NULL) {
LOG(WARNING) << "debugger thread failed to initialize";
+ return;
}
// If a debugger has already attached, send the "welcome" message.
@@ -166,6 +199,8 @@
void Dbg::StopJdwp() {
delete gJdwpState;
+ delete gRegistry;
+ gRegistry = NULL;
}
void Dbg::SetJdwpAllowed(bool allowed) {
@@ -173,8 +208,15 @@
}
DebugInvokeReq* Dbg::GetInvokeReq() {
- UNIMPLEMENTED(FATAL);
- return NULL;
+ return Thread::Current()->GetInvokeReq();
+}
+
+Thread* Dbg::GetDebugThread() {
+ return (gJdwpState != NULL) ? gJdwpState->GetDebugThread() : NULL;
+}
+
+void Dbg::ClearWaitForEventThread() {
+ gJdwpState->ClearWaitForEventThread();
}
void Dbg::Connected() {
@@ -472,16 +514,15 @@
}
JDWP::ObjectId Dbg::GetThreadSelfId() {
- UNIMPLEMENTED(FATAL);
- return 0;
+ return gRegistry->Add(Thread::Current()->GetPeer());
}
-void Dbg::SuspendVM(bool isEvent) {
- UNIMPLEMENTED(FATAL);
+void Dbg::SuspendVM() {
+ Runtime::Current()->GetThreadList()->SuspendAll(true);
}
void Dbg::ResumeVM() {
- UNIMPLEMENTED(FATAL);
+ Runtime::Current()->GetThreadList()->ResumeAll(true);
}
void Dbg::SuspendThread(JDWP::ObjectId threadId) {
@@ -493,7 +534,7 @@
}
void Dbg::SuspendSelf() {
- UNIMPLEMENTED(FATAL);
+ Runtime::Current()->GetThreadList()->SuspendSelfForDebugger();
}
bool Dbg::GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
diff --git a/src/debugger.h b/src/debugger.h
index fa870b1..6eabf33 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -36,26 +36,29 @@
* Invoke-during-breakpoint support.
*/
struct DebugInvokeReq {
+ DebugInvokeReq() : lock_("a DebugInvokeReq lock"), cond_("a DebugInvokeReq condition variable") {
+ }
+
/* boolean; only set when we're in the tail end of an event handler */
bool ready;
/* boolean; set if the JDWP thread wants this thread to do work */
- bool invokeNeeded;
+ bool invoke_needed;
/* request */
Object* obj; /* not used for ClassType.InvokeMethod */
Object* thread;
Class* class_;
Method* method;
- uint32_t numArgs;
- uint64_t* argArray; /* will be NULL if numArgs==0 */
+ uint32_t num_args;
+ uint64_t* arg_array; /* will be NULL if numArgs==0 */
uint32_t options;
/* result */
- JDWP::JdwpError err;
- uint8_t resultTag;
- JValue resultValue;
- JDWP::ObjectId exceptObj;
+ JDWP::JdwpError error;
+ uint8_t result_tag;
+ JValue result_value;
+ JDWP::ObjectId exception;
/* condition variable to wait on while the method executes */
Mutex lock_;
@@ -73,6 +76,9 @@
// Return the DebugInvokeReq for the current thread.
static DebugInvokeReq* GetInvokeReq();
+ static Thread* GetDebugThread();
+ static void ClearWaitForEventThread();
+
/*
* Enable/disable breakpoints and step modes. Used to provide a heads-up
* when the debugger attaches.
@@ -182,7 +188,7 @@
static bool GetThreadFrame(JDWP::ObjectId threadId, int num, JDWP::FrameId* pFrameId, JDWP::JdwpLocation* pLoc);
static JDWP::ObjectId GetThreadSelfId();
- static void SuspendVM(bool isEvent);
+ static void SuspendVM();
static void ResumeVM();
static void SuspendThread(JDWP::ObjectId threadId);
static void ResumeThread(JDWP::ObjectId threadId);
diff --git a/src/jdwp/jdwp.h b/src/jdwp/jdwp.h
index cd2bfa7..a590c27 100644
--- a/src/jdwp/jdwp.h
+++ b/src/jdwp/jdwp.h
@@ -31,6 +31,8 @@
namespace art {
+struct Thread;
+
namespace JDWP {
/*
@@ -118,11 +120,10 @@
*/
bool IsActive();
- /*
- * Return the debugger thread's handle, or 0 if the debugger thread isn't
- * running.
+ /**
+ * Returns the Thread* for the JDWP daemon thread.
*/
- pthread_t GetDebugThread();
+ Thread* GetDebugThread();
/*
* Get time, in milliseconds, since the last debugger activity.
@@ -247,7 +248,8 @@
ConditionVariable thread_start_cond_;
volatile int32_t debug_thread_started_;
- pthread_t debugThreadHandle;
+ pthread_t pthread_;
+ Thread* thread_;
public: // TODO: fix privacy
ObjectId debugThreadId;
private:
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
index 835b182..c6bdddb 100644
--- a/src/jdwp/jdwp_event.cc
+++ b/src/jdwp/jdwp_event.cc
@@ -539,7 +539,7 @@
}
if (suspendPolicy == SP_ALL) {
- Dbg::SuspendVM(true);
+ Dbg::SuspendVM();
} else {
CHECK_EQ(suspendPolicy, SP_EVENT_THREAD);
}
@@ -560,7 +560,7 @@
* 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) {
+ if (!pReq->invoke_needed) {
/*LOGD("suspendByPolicy: no invoke needed");*/
break;
}
@@ -568,14 +568,14 @@
/* grab this before posting/suspending again */
state->SetWaitForEventThread(Dbg::GetThreadSelfId());
- /* leave pReq->invokeNeeded raised so we can check reentrancy */
+ /* leave pReq->invoke_needed raised so we can check reentrancy */
LOG(VERBOSE) << "invoking method...";
Dbg::ExecuteMethod(pReq);
- pReq->err = ERR_NONE;
+ pReq->error = ERR_NONE;
/* clear this before signaling */
- pReq->invokeNeeded = false;
+ pReq->invoke_needed = false;
LOG(VERBOSE) << "invoke complete, signaling and self-suspending";
MutexLock mu(pReq->lock_);
@@ -587,12 +587,12 @@
* Determine if there is a method invocation in progress in the current
* thread.
*
- * We look at the "invokeNeeded" flag in the per-thread DebugInvokeReq
+ * We look at the "invoke_needed" 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;
+ return pReq->invoke_needed;
}
/*
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
index 5e566ae..7003151 100644
--- a/src/jdwp/jdwp_handler.cc
+++ b/src/jdwp/jdwp_handler.cc
@@ -310,7 +310,7 @@
* This needs to increment the "suspend count" on all threads.
*/
static JdwpError handleVM_Suspend(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
- Dbg::SuspendVM(false);
+ Dbg::SuspendVM();
return ERR_NONE;
}
diff --git a/src/jdwp/jdwp_main.cc b/src/jdwp/jdwp_main.cc
index a7857a1..1df0d66 100644
--- a/src/jdwp/jdwp_main.cc
+++ b/src/jdwp/jdwp_main.cc
@@ -151,7 +151,7 @@
* We have bound to a port, or are trying to connect outbound to a
* debugger. Create the JDWP thread and let it continue the mission.
*/
- CHECK_PTHREAD_CALL(pthread_create, (&state->debugThreadHandle, NULL, StartJdwpThread, state), "JDWP thread");
+ CHECK_PTHREAD_CALL(pthread_create, (&state->pthread_, NULL, StartJdwpThread, state), "JDWP thread");
/*
* Wait until the thread finishes basic initialization.
@@ -238,7 +238,7 @@
if (debug_thread_started_) {
run = false;
void* threadReturn;
- if (pthread_join(debugThreadHandle, &threadReturn) != 0) {
+ if (pthread_join(pthread_, &threadReturn) != 0) {
LOG(WARNING) << "JDWP thread join failed";
}
}
@@ -281,7 +281,7 @@
* Finish initializing, then notify the creating thread that
* we're running.
*/
- debugThreadHandle = pthread_self();
+ thread_ = Thread::Current();
run = true;
android_atomic_release_store(true, &debug_thread_started_);
@@ -387,8 +387,8 @@
runtime->DetachCurrentThread();
}
-pthread_t JdwpState::GetDebugThread() {
- return debugThreadHandle;
+Thread* JdwpState::GetDebugThread() {
+ return thread_;
}
/*
diff --git a/src/thread.cc b/src/thread.cc
index f4ca2ab..b8dbd6b 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -731,7 +731,8 @@
class_loader_override_(NULL),
long_jump_context_(NULL),
throwing_OutOfMemoryError_(false),
- pre_allocated_OutOfMemoryError_(NULL) {
+ pre_allocated_OutOfMemoryError_(NULL),
+ debug_invoke_req_(new DebugInvokeReq) {
CHECK_EQ((sizeof(Thread) % 4), 0U) << sizeof(Thread);
}
@@ -774,6 +775,8 @@
delete wait_mutex_;
delete long_jump_context_;
+
+ delete debug_invoke_req_;
}
void Thread::HandleUncaughtExceptions() {
diff --git a/src/thread.h b/src/thread.h
index 8edeaf2..d81fd9a 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -43,6 +43,7 @@
class ClassLinker;
class ClassLoader;
class Context;
+class DebugInvokeReq;
class Method;
class Monitor;
class Object;
@@ -455,6 +456,10 @@
void WalkStack(StackVisitor* visitor) const;
+ DebugInvokeReq* GetInvokeReq() {
+ return debug_invoke_req_;
+ }
+
private:
Thread();
~Thread();
@@ -583,6 +588,9 @@
Throwable* pre_allocated_OutOfMemoryError_;
+ // JDWP invoke-during-breakpoint support.
+ DebugInvokeReq* debug_invoke_req_;
+
// TLS key used to retrieve the VM thread object.
static pthread_key_t pthread_key_self_;
diff --git a/src/thread_list.cc b/src/thread_list.cc
index dfce2a2..f386547 100644
--- a/src/thread_list.cc
+++ b/src/thread_list.cc
@@ -18,6 +18,8 @@
#include <unistd.h>
+#include "debugger.h"
+
namespace art {
// TODO: merge with ThreadListLock?
@@ -113,23 +115,24 @@
}
}
-void ThreadList::SuspendAll() {
+void ThreadList::SuspendAll(bool for_debugger) {
Thread* self = Thread::Current();
// TODO: add another thread_suspend_lock_ to avoid GC/debugger races.
if (verbose_) {
- LOG(INFO) << *self << " SuspendAll starting...";
+ LOG(INFO) << *self << " SuspendAll starting..." << (for_debugger ? " (debugger)" : "");
}
ThreadListLocker locker(this);
+ Thread* debug_thread = Dbg::GetDebugThread();
{
// Increment everybody's suspend count (except our own).
MutexLock mu(thread_suspend_count_lock_);
for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
Thread* thread = *it;
- if (thread != self) {
+ if (thread != self && thread != debug_thread) {
if (verbose_) {
LOG(INFO) << "requesting thread suspend: " << *thread;
}
@@ -154,7 +157,7 @@
*/
for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
Thread* thread = *it;
- if (thread != self) {
+ if (thread != self && thread != debug_thread) {
thread->WaitUntilSuspended();
if (verbose_) {
LOG(INFO) << "thread suspended: " << *thread;
@@ -193,12 +196,56 @@
}
}
+void ThreadList::SuspendSelfForDebugger() {
+ Thread* self = Thread::Current();
-void ThreadList::ResumeAll() {
+ // The debugger thread must not suspend itself due to debugger activity!
+ Thread* debug_thread = Dbg::GetDebugThread();
+ CHECK(debug_thread != NULL);
+ CHECK(self != debug_thread);
+
+ // Collisions with other suspends aren't really interesting. We want
+ // to ensure that we're the only one fiddling with the suspend count
+ // though.
+ ThreadListLocker locker(this);
+ MutexLock mu(thread_suspend_count_lock_);
+ ++self->suspend_count_;
+
+ // Suspend ourselves.
+ CHECK_GT(self->suspend_count_, 0);
+ self->SetState(Thread::kSuspended);
+ if (verbose_) {
+ LOG(INFO) << *self << " self-suspending (dbg)";
+ }
+
+ // Tell JDWP that we've completed suspension. The JDWP thread can't
+ // tell us to resume before we're fully asleep because we hold the
+ // suspend count lock.
+ Dbg::ClearWaitForEventThread();
+
+ while (self->suspend_count_ != 0) {
+ thread_suspend_count_cond_.Wait(thread_suspend_count_lock_);
+ if (self->suspend_count_ != 0) {
+ // The condition was signaled but we're still suspended. This
+ // can happen if the debugger lets go while a SIGQUIT thread
+ // dump event is pending (assuming SignalCatcher was resumed for
+ // just long enough to try to grab the thread-suspend lock).
+ LOG(DEBUG) << *self << " still suspended after undo "
+ << "(suspend count=" << self->suspend_count_ << ")";
+ }
+ }
+ CHECK_EQ(self->suspend_count_, 0);
+ self->SetState(Thread::kRunnable);
+ if (verbose_) {
+ LOG(INFO) << *self << " self-reviving (dbg)";
+ }
+}
+
+void ThreadList::ResumeAll(bool for_debugger) {
Thread* self = Thread::Current();
if (verbose_) {
- LOG(INFO) << *self << " ResumeAll starting";
+ LOG(INFO) << *self << " ResumeAll starting" << (for_debugger ? " (debugger)" : "");
}
// Decrement the suspend counts for all threads. No need for atomic
@@ -206,10 +253,11 @@
// We do need to hold the thread list because of JNI attaches.
{
ThreadListLocker locker(this);
+ Thread* debug_thread = Dbg::GetDebugThread();
MutexLock mu(thread_suspend_count_lock_);
for (It it = list_.begin(), end = list_.end(); it != end; ++it) {
Thread* thread = *it;
- if (thread != self) {
+ if (thread != self && thread != debug_thread) {
if (thread->suspend_count_ > 0) {
--thread->suspend_count_;
} else {
diff --git a/src/thread_list.h b/src/thread_list.h
index 3a20405..baa7dad 100644
--- a/src/thread_list.h
+++ b/src/thread_list.h
@@ -36,8 +36,9 @@
// Thread suspension support.
void FullSuspendCheck(Thread* thread);
- void ResumeAll();
- void SuspendAll();
+ void ResumeAll(bool for_debugger = false);
+ void SuspendAll(bool for_debugger = false);
+ void SuspendSelfForDebugger();
void RunWhileSuspended(Thread* thread, void (*callback)(void*), void* arg);
void Register();