Implement JDWP InvokeMethod and breakpoints on exception throwing.
Change-Id: I1142bee843104f0850fd7270752104d5d73a44f0
diff --git a/src/debugger.cc b/src/debugger.cc
index b2f9629..dea4339 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -716,8 +716,7 @@
}
bool Dbg::MatchType(JDWP::RefTypeId instClassId, JDWP::RefTypeId classId) {
- UNIMPLEMENTED(FATAL);
- return false;
+ return gRegistry->Get<Class*>(instClassId)->InstanceOf(gRegistry->Get<Class*>(classId));
}
JDWP::FieldId ToFieldId(Field* f) {
@@ -752,6 +751,14 @@
#endif
}
+void SetLocation(JDWP::JdwpLocation& location, Method* m, uintptr_t native_pc) {
+ Class* c = m->GetDeclaringClass();
+ location.typeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
+ location.classId = gRegistry->Add(c);
+ location.methodId = ToMethodId(m);
+ location.idx = m->IsNative() ? -1 : m->ToDexPC(native_pc);
+}
+
std::string Dbg::GetMethodName(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId) {
Method* m = FromMethodId(methodId);
return MethodHelper(m).GetName();
@@ -1119,8 +1126,7 @@
}
uint32_t Dbg::GetThreadSuspendCount(JDWP::ObjectId threadId) {
- UNIMPLEMENTED(FATAL);
- return 0;
+ return DecodeThread(threadId)->GetSuspendCount();
}
bool Dbg::ThreadExists(JDWP::ObjectId threadId) {
@@ -1131,8 +1137,6 @@
return DecodeThread(threadId)->IsSuspended();
}
-//void Dbg::WaitForSuspend(JDWP::ObjectId threadId);
-
void Dbg::GetThreadGroupThreadsImpl(Object* thread_group, JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount) {
struct ThreadListVisitor {
static void Visit(Thread* t, void* arg) {
@@ -1212,15 +1216,7 @@
if (depth == desired_frame_number) {
*pFrameId = reinterpret_cast<JDWP::FrameId>(f.GetSP());
-
- Method* m = f.GetMethod();
- Class* c = m->GetDeclaringClass();
-
- pLoc->typeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
- pLoc->classId = gRegistry->Add(c);
- pLoc->methodId = ToMethodId(m);
- pLoc->idx = m->IsNative() ? -1 : m->ToDexPC(pc);
-
+ SetLocation(*pLoc, f.GetMethod(), pc);
found = true;
}
++depth;
@@ -1276,15 +1272,15 @@
Runtime::Current()->GetThreadList()->SuspendSelfForDebugger();
}
-bool Dbg::GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
+bool Dbg::GetThisObject(JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
Method** sp = reinterpret_cast<Method**>(frameId);
Frame f;
f.SetSP(sp);
- uint16_t reg = DemangleSlot(0, f);
Method* m = f.GetMethod();
Object* o = NULL;
if (!m->IsNative() && !m->IsStatic()) {
+ uint16_t reg = DemangleSlot(0, f);
o = reinterpret_cast<Object*>(f.GetVReg(m, reg));
}
*pThisId = gRegistry->Add(o);
@@ -1436,8 +1432,29 @@
UNIMPLEMENTED(FATAL);
}
-void Dbg::PostException(void* throwFp, int throwRelPc, void* catchFp, int catchRelPc, Object* exception) {
- UNIMPLEMENTED(FATAL);
+void Dbg::PostException(Method** sp, Method* throwMethod, uintptr_t throwNativePc, Method* catchMethod, uintptr_t catchNativePc, Object* exception) {
+ JDWP::JdwpLocation throw_location;
+ SetLocation(throw_location, throwMethod, throwNativePc);
+ JDWP::JdwpLocation catch_location;
+ SetLocation(catch_location, catchMethod, catchNativePc);
+
+ // We need 'this' for InstanceOnly filters.
+ JDWP::ObjectId this_id;
+ GetThisObject(reinterpret_cast<JDWP::FrameId>(sp), &this_id);
+
+ /*
+ * Hand the event to the JDWP exception handler. Note we're using the
+ * "NoReg" objectID on the exception, which is not strictly correct --
+ * the exception object WILL be passed up to the debugger if the
+ * debugger is interested in the event. We do this because the current
+ * implementation of the debugger object registry never throws anything
+ * away, and some people were experiencing a fatal build up of exception
+ * objects when dealing with certain libraries.
+ */
+ JDWP::ObjectId exception_id = static_cast<JDWP::ObjectId>(reinterpret_cast<uintptr_t>(exception));
+ JDWP::RefTypeId exception_class_id = gRegistry->Add(exception->GetClass());
+
+ gJdwpState->PostException(&throw_location, exception_id, exception_class_id, &catch_location, this_id);
}
void Dbg::PostClassPrepare(Class* c) {
@@ -1462,17 +1479,182 @@
UNIMPLEMENTED(FATAL);
}
-JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj) {
- UNIMPLEMENTED(FATAL);
- return JDWP::ERR_NONE;
+JDWP::JdwpError Dbg::InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, JDWP::JdwpTag* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptionId) {
+ ThreadList* thread_list = Runtime::Current()->GetThreadList();
+
+ Thread* targetThread = NULL;
+ DebugInvokeReq* req = NULL;
+ {
+ ScopedThreadListLock thread_list_lock;
+ targetThread = DecodeThread(threadId);
+ if (targetThread == NULL) {
+ LOG(ERROR) << "InvokeMethod request for non-existent thread " << threadId;
+ return JDWP::ERR_INVALID_THREAD;
+ }
+ req = targetThread->GetInvokeReq();
+ if (!req->ready) {
+ LOG(ERROR) << "InvokeMethod request for thread not stopped by event: " << *targetThread;
+ return JDWP::ERR_INVALID_THREAD;
+ }
+
+ /*
+ * We currently have a bug where we don't successfully resume the
+ * target thread if the suspend count is too deep. We're expected to
+ * require one "resume" for each "suspend", but when asked to execute
+ * a method we have to resume fully and then re-suspend it back to the
+ * same level. (The easiest way to cause this is to type "suspend"
+ * multiple times in jdb.)
+ *
+ * It's unclear what this means when the event specifies "resume all"
+ * and some threads are suspended more deeply than others. This is
+ * a rare problem, so for now we just prevent it from hanging forever
+ * by rejecting the method invocation request. Without this, we will
+ * be stuck waiting on a suspended thread.
+ */
+ int suspend_count = targetThread->GetSuspendCount();
+ if (suspend_count > 1) {
+ LOG(ERROR) << *targetThread << " suspend count too deep for method invocation: " << suspend_count;
+ return JDWP::ERR_THREAD_SUSPENDED; // Probably not expected here.
+ }
+
+ /*
+ * TODO: ought to screen the various IDs, and verify that the argument
+ * list is valid.
+ */
+ req->receiver_ = gRegistry->Get<Object*>(objectId);
+ req->thread_ = gRegistry->Get<Object*>(threadId);
+ req->class_ = gRegistry->Get<Class*>(classId);
+ req->method_ = FromMethodId(methodId);
+ req->num_args_ = numArgs;
+ req->arg_array_ = argArray;
+ req->options_ = options;
+ req->invoke_needed_ = true;
+ }
+
+ // The fact that we've released the thread list lock is a bit risky --- if the thread goes
+ // away we're sitting high and dry -- but we must release this before the ResumeAllThreads
+ // call, and it's unwise to hold it during WaitForSuspend.
+
+ {
+ /*
+ * We change our (JDWP thread) status, which should be THREAD_RUNNING,
+ * so the VM can suspend for a GC if the invoke request causes us to
+ * run out of memory. It's also a good idea to change it before locking
+ * the invokeReq mutex, although that should never be held for long.
+ */
+ ScopedThreadStateChange tsc(Thread::Current(), Thread::kVmWait);
+
+ LOG(VERBOSE) << " Transferring control to event thread";
+ {
+ MutexLock mu(req->lock_);
+
+ if ((options & JDWP::INVOKE_SINGLE_THREADED) == 0) {
+ LOG(VERBOSE) << " Resuming all threads";
+ thread_list->ResumeAll(true);
+ } else {
+ LOG(VERBOSE) << " Resuming event thread only";
+ thread_list->Resume(targetThread, true);
+ }
+
+ // Wait for the request to finish executing.
+ while (req->invoke_needed_) {
+ req->cond_.Wait(req->lock_);
+ }
+ }
+ LOG(VERBOSE) << " Control has returned from event thread";
+
+ /* wait for thread to re-suspend itself */
+ targetThread->WaitUntilSuspended();
+ //dvmWaitForSuspend(targetThread);
+ }
+
+ /*
+ * Suspend the threads. We waited for the target thread to suspend
+ * itself, so all we need to do is suspend the others.
+ *
+ * The suspendAllThreads() call will double-suspend the event thread,
+ * so we want to resume the target thread once to keep the books straight.
+ */
+ if ((options & JDWP::INVOKE_SINGLE_THREADED) == 0) {
+ LOG(VERBOSE) << " Suspending all threads";
+ thread_list->SuspendAll(true);
+ LOG(VERBOSE) << " Resuming event thread to balance the count";
+ thread_list->Resume(targetThread, true);
+ }
+
+ // Copy the result.
+ *pResultTag = req->result_tag;
+ if (IsPrimitiveTag(req->result_tag)) {
+ *pResultValue = req->result_value.j;
+ } else {
+ *pResultValue = gRegistry->Add(req->result_value.l);
+ }
+ *pExceptionId = req->exception;
+ return req->error;
}
void Dbg::ExecuteMethod(DebugInvokeReq* pReq) {
- UNIMPLEMENTED(FATAL);
+ Thread* self = Thread::Current();
+
+ // We can be called while an exception is pending in the VM. We need
+ // to preserve that across the method invocation.
+ SirtRef<Throwable> old_exception(self->GetException());
+ self->ClearException();
+
+ ScopedThreadStateChange tsc(self, Thread::kRunnable);
+
+ // Translate the method through the vtable, unless the debugger wants to suppress it.
+ Method* m = pReq->method_;
+ if ((pReq->options_ & JDWP::INVOKE_NONVIRTUAL) == 0 && pReq->receiver_ != NULL) {
+ m = pReq->class_->FindVirtualMethodForVirtualOrInterface(pReq->method_);
+ }
+ CHECK(m != NULL);
+
+ CHECK_EQ(sizeof(jvalue), sizeof(uint64_t));
+
+ pReq->result_value = InvokeWithJValues(self, pReq->receiver_, m, reinterpret_cast<JValue*>(pReq->arg_array_));
+
+ pReq->exception = gRegistry->Add(self->GetException());
+ pReq->result_tag = BasicTagFromDescriptor(MethodHelper(m).GetShorty());
+ if (pReq->exception != 0) {
+ Object* exc = self->GetException();
+ LOG(VERBOSE) << " JDWP invocation returning with exception=" << exc << " " << PrettyTypeOf(exc);
+ self->ClearException();
+ pReq->result_value.j = 0;
+ } else if (pReq->result_tag == JDWP::JT_OBJECT) {
+ /* if no exception thrown, examine object result more closely */
+ JDWP::JdwpTag new_tag = TagFromObject(pReq->result_value.l);
+ if (new_tag != pReq->result_tag) {
+ LOG(VERBOSE) << " JDWP promoted result from " << pReq->result_tag << " to " << new_tag;
+ pReq->result_tag = new_tag;
+ }
+
+ /*
+ * Register the object. We don't actually need an ObjectId yet,
+ * but we do need to be sure that the GC won't move or discard the
+ * object when we switch out of RUNNING. The ObjectId conversion
+ * will add the object to the "do not touch" list.
+ *
+ * We can't use the "tracked allocation" mechanism here because
+ * the object is going to be handed off to a different thread.
+ */
+ gRegistry->Add(pReq->result_value.l);
+ }
+
+ if (old_exception.get() != NULL) {
+ self->SetException(old_exception.get());
+ }
}
+/*
+ * Register an object ID that might not have been registered previously.
+ *
+ * Normally this wouldn't happen -- the conversion to an ObjectId would
+ * have added the object to the registry -- but in some cases (e.g.
+ * throwing exceptions) we really want to do the registration late.
+ */
void Dbg::RegisterObjectId(JDWP::ObjectId id) {
- UNIMPLEMENTED(FATAL);
+ gRegistry->Add(reinterpret_cast<Object*>(id));
}
/*