summaryrefslogtreecommitdiff
path: root/runtime/jdwp/jdwp_handler.cc
diff options
context:
space:
mode:
author Brian Carlstrom <bdc@google.com> 2013-07-12 13:46:57 -0700
committer Brian Carlstrom <bdc@google.com> 2013-07-12 17:49:01 -0700
commit7940e44f4517de5e2634a7e07d58d0fb26160513 (patch)
treeac90242d96229a6942f6e24ab137bc1f8f2e0025 /runtime/jdwp/jdwp_handler.cc
parent5cd9e3b122f276f610980cbaf0d2ad6ed4cd9088 (diff)
Create separate Android.mk for main build targets
The runtime, compiler, dex2oat, and oatdump now are in seperate trees to prevent dependency creep. They can now be individually built without rebuilding the rest of the art projects. dalvikvm and jdwpspy were already this way. Builds in the art directory should behave as before, building everything including tests. Change-Id: Ic6b1151e5ed0f823c3dd301afd2b13eb2d8feb81
Diffstat (limited to 'runtime/jdwp/jdwp_handler.cc')
-rw-r--r--runtime/jdwp/jdwp_handler.cc1750
1 files changed, 1750 insertions, 0 deletions
diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc
new file mode 100644
index 0000000000..8ef146c096
--- /dev/null
+++ b/runtime/jdwp/jdwp_handler.cc
@@ -0,0 +1,1750 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "atomic.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stringprintf.h"
+#include "debugger.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "jdwp/jdwp_priv.h"
+#include "runtime.h"
+#include "thread-inl.h"
+#include "UniquePtr.h"
+
+namespace art {
+
+namespace JDWP {
+
+std::string DescribeField(const FieldId& field_id) {
+ return StringPrintf("%#x (%s)", field_id, Dbg::GetFieldName(field_id).c_str());
+}
+
+std::string DescribeMethod(const MethodId& method_id) {
+ return StringPrintf("%#x (%s)", method_id, Dbg::GetMethodName(method_id).c_str());
+}
+
+std::string DescribeRefTypeId(const RefTypeId& ref_type_id) {
+ std::string signature("unknown");
+ Dbg::GetSignature(ref_type_id, signature);
+ return StringPrintf("%#llx (%s)", ref_type_id, signature.c_str());
+}
+
+// Helper function: write a variable-width value into the output input buffer.
+static void WriteValue(ExpandBuf* pReply, int width, uint64_t value) {
+ switch (width) {
+ case 1: expandBufAdd1(pReply, value); break;
+ case 2: expandBufAdd2BE(pReply, value); break;
+ case 4: expandBufAdd4BE(pReply, value); break;
+ case 8: expandBufAdd8BE(pReply, value); break;
+ default: LOG(FATAL) << width; break;
+ }
+}
+
+static JdwpError WriteTaggedObject(ExpandBuf* reply, ObjectId object_id)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ uint8_t tag;
+ JdwpError rc = Dbg::GetObjectTag(object_id, tag);
+ if (rc == ERR_NONE) {
+ expandBufAdd1(reply, tag);
+ expandBufAddObjectId(reply, object_id);
+ }
+ return rc;
+}
+
+static JdwpError WriteTaggedObjectList(ExpandBuf* reply, const std::vector<ObjectId>& objects)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAdd4BE(reply, objects.size());
+ for (size_t i = 0; i < objects.size(); ++i) {
+ JdwpError rc = WriteTaggedObject(reply, objects[i]);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Common code for *_InvokeMethod requests.
+ *
+ * If "is_constructor" is set, this returns "object_id" rather than the
+ * expected-to-be-void return value of the called function.
+ */
+static JdwpError FinishInvoke(JdwpState*, Request& request, ExpandBuf* pReply,
+ ObjectId thread_id, ObjectId object_id,
+ RefTypeId class_id, MethodId method_id, bool is_constructor)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ CHECK(!is_constructor || object_id != 0);
+
+ int32_t arg_count = request.ReadSigned32("argument count");
+
+ VLOG(jdwp) << StringPrintf(" --> thread_id=%#llx object_id=%#llx", thread_id, object_id);
+ VLOG(jdwp) << StringPrintf(" class_id=%#llx method_id=%x %s.%s", class_id,
+ method_id, Dbg::GetClassName(class_id).c_str(),
+ Dbg::GetMethodName(method_id).c_str());
+ VLOG(jdwp) << StringPrintf(" %d args:", arg_count);
+
+ UniquePtr<JdwpTag[]> argTypes(arg_count > 0 ? new JdwpTag[arg_count] : NULL);
+ UniquePtr<uint64_t[]> argValues(arg_count > 0 ? new uint64_t[arg_count] : NULL);
+ for (int32_t i = 0; i < arg_count; ++i) {
+ argTypes[i] = request.ReadTag();
+ size_t width = Dbg::GetTagWidth(argTypes[i]);
+ argValues[i] = request.ReadValue(width);
+ VLOG(jdwp) << " " << argTypes[i] << StringPrintf("(%zd): %#llx", width, argValues[i]);
+ }
+
+ uint32_t options = request.ReadUnsigned32("InvokeOptions bit flags");
+ VLOG(jdwp) << StringPrintf(" options=0x%04x%s%s", options,
+ (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "",
+ (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");
+
+ JdwpTag resultTag;
+ uint64_t resultValue;
+ ObjectId exceptObjId;
+ JdwpError err = Dbg::InvokeMethod(thread_id, object_id, class_id, method_id, arg_count, argValues.get(), argTypes.get(), options, &resultTag, &resultValue, &exceptObjId);
+ if (err != ERR_NONE) {
+ return err;
+ }
+
+ if (err == ERR_NONE) {
+ if (is_constructor) {
+ // If we invoked a constructor (which actually returns void), return the receiver,
+ // unless we threw, in which case we return NULL.
+ resultTag = JT_OBJECT;
+ resultValue = (exceptObjId == 0) ? object_id : 0;
+ }
+
+ size_t width = Dbg::GetTagWidth(resultTag);
+ expandBufAdd1(pReply, resultTag);
+ if (width != 0) {
+ WriteValue(pReply, width, resultValue);
+ }
+ expandBufAdd1(pReply, JT_OBJECT);
+ expandBufAddObjectId(pReply, exceptObjId);
+
+ VLOG(jdwp) << " --> returned " << resultTag << StringPrintf(" %#llx (except=%#llx)", resultValue, exceptObjId);
+
+ /* show detailed debug output */
+ if (resultTag == JT_STRING && exceptObjId == 0) {
+ if (resultValue != 0) {
+ VLOG(jdwp) << " string '" << Dbg::StringToUtf8(resultValue) << "'";
+ } else {
+ VLOG(jdwp) << " string (null)";
+ }
+ }
+ }
+
+ return err;
+}
+
+static JdwpError VM_Version(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ // Text information on runtime version.
+ std::string version(StringPrintf("Android Runtime %s", Runtime::Current()->GetVersion()));
+ expandBufAddUtf8String(pReply, version);
+
+ // JDWP version numbers, major and minor.
+ expandBufAdd4BE(pReply, 1);
+ expandBufAdd4BE(pReply, 6);
+
+ // "java.version".
+ expandBufAddUtf8String(pReply, "1.6.0");
+
+ // "java.vm.name".
+ expandBufAddUtf8String(pReply, "Dalvik");
+
+ return ERR_NONE;
+}
+
+/*
+ * Given a class JNI signature (e.g. "Ljava/lang/Error;"), return the
+ * referenceTypeID. We need to send back more than one if the class has
+ * been loaded by multiple class loaders.
+ */
+static JdwpError VM_ClassesBySignature(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::string classDescriptor(request.ReadUtf8String());
+
+ std::vector<RefTypeId> ids;
+ Dbg::FindLoadedClassBySignature(classDescriptor.c_str(), ids);
+
+ expandBufAdd4BE(pReply, ids.size());
+
+ for (size_t i = 0; i < ids.size(); ++i) {
+ // Get class vs. interface and status flags.
+ JDWP::JdwpTypeTag type_tag;
+ uint32_t class_status;
+ JDWP::JdwpError status = Dbg::GetClassInfo(ids[i], &type_tag, &class_status, NULL);
+ if (status != ERR_NONE) {
+ return status;
+ }
+
+ expandBufAdd1(pReply, type_tag);
+ expandBufAddRefTypeId(pReply, ids[i]);
+ expandBufAdd4BE(pReply, class_status);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Handle request for the thread IDs of all running threads.
+ *
+ * We exclude ourselves from the list, because we don't allow ourselves
+ * to be suspended, and that violates some JDWP expectations.
+ */
+static JdwpError VM_AllThreads(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::vector<ObjectId> thread_ids;
+ Dbg::GetThreads(0, thread_ids);
+
+ expandBufAdd4BE(pReply, thread_ids.size());
+ for (uint32_t i = 0; i < thread_ids.size(); ++i) {
+ expandBufAddObjectId(pReply, thread_ids[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * List all thread groups that do not have a parent.
+ */
+static JdwpError VM_TopLevelThreadGroups(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ /*
+ * TODO: maintain a list of parentless thread groups in the VM.
+ *
+ * For now, just return "system". Application threads are created
+ * in "main", which is a child of "system".
+ */
+ uint32_t groups = 1;
+ expandBufAdd4BE(pReply, groups);
+ ObjectId thread_group_id = Dbg::GetSystemThreadGroupId();
+ expandBufAddObjectId(pReply, thread_group_id);
+
+ return ERR_NONE;
+}
+
+/*
+ * Respond with the sizes of the basic debugger types.
+ *
+ * All IDs are 8 bytes.
+ */
+static JdwpError VM_IDSizes(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAdd4BE(pReply, sizeof(FieldId));
+ expandBufAdd4BE(pReply, sizeof(MethodId));
+ expandBufAdd4BE(pReply, sizeof(ObjectId));
+ expandBufAdd4BE(pReply, sizeof(RefTypeId));
+ expandBufAdd4BE(pReply, sizeof(FrameId));
+ return ERR_NONE;
+}
+
+static JdwpError VM_Dispose(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Dbg::Disposed();
+ return ERR_NONE;
+}
+
+/*
+ * Suspend the execution of the application running in the VM (i.e. suspend
+ * all threads).
+ *
+ * This needs to increment the "suspend count" on all threads.
+ */
+static JdwpError VM_Suspend(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Thread* self = Thread::Current();
+ self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSuspension);
+ Dbg::SuspendVM();
+ self->TransitionFromSuspendedToRunnable();
+ return ERR_NONE;
+}
+
+/*
+ * Resume execution. Decrements the "suspend count" of all threads.
+ */
+static JdwpError VM_Resume(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Dbg::ResumeVM();
+ return ERR_NONE;
+}
+
+static JdwpError VM_Exit(JdwpState* state, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ uint32_t exit_status = request.ReadUnsigned32("exit_status");
+ state->ExitAfterReplying(exit_status);
+ return ERR_NONE;
+}
+
+/*
+ * Create a new string in the VM and return its ID.
+ *
+ * (Ctrl-Shift-I in Eclipse on an array of objects causes it to create the
+ * string "java.util.Arrays".)
+ */
+static JdwpError VM_CreateString(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::string str(request.ReadUtf8String());
+ ObjectId stringId = Dbg::CreateString(str);
+ if (stringId == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ expandBufAddObjectId(pReply, stringId);
+ return ERR_NONE;
+}
+
+static JdwpError VM_ClassPaths(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAddUtf8String(pReply, "/");
+
+ std::vector<std::string> class_path;
+ Split(Runtime::Current()->GetClassPathString(), ':', class_path);
+ expandBufAdd4BE(pReply, class_path.size());
+ for (size_t i = 0; i < class_path.size(); ++i) {
+ expandBufAddUtf8String(pReply, class_path[i]);
+ }
+
+ std::vector<std::string> boot_class_path;
+ Split(Runtime::Current()->GetBootClassPathString(), ':', boot_class_path);
+ expandBufAdd4BE(pReply, boot_class_path.size());
+ for (size_t i = 0; i < boot_class_path.size(); ++i) {
+ expandBufAddUtf8String(pReply, boot_class_path[i]);
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError VM_DisposeObjects(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ size_t object_count = request.ReadUnsigned32("object_count");
+ for (size_t i = 0; i < object_count; ++i) {
+ ObjectId object_id = request.ReadObjectId();
+ uint32_t reference_count = request.ReadUnsigned32("reference_count");
+ Dbg::DisposeObject(object_id, reference_count);
+ }
+ return ERR_NONE;
+}
+
+static JdwpError VM_Capabilities(JdwpState*, Request&, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAdd1(reply, false); // canWatchFieldModification
+ expandBufAdd1(reply, false); // canWatchFieldAccess
+ expandBufAdd1(reply, true); // canGetBytecodes
+ expandBufAdd1(reply, true); // canGetSyntheticAttribute
+ expandBufAdd1(reply, true); // canGetOwnedMonitorInfo
+ expandBufAdd1(reply, true); // canGetCurrentContendedMonitor
+ expandBufAdd1(reply, true); // canGetMonitorInfo
+ return ERR_NONE;
+}
+
+static JdwpError VM_CapabilitiesNew(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+
+ // The first few capabilities are the same as those reported by the older call.
+ VM_Capabilities(NULL, request, reply);
+
+ expandBufAdd1(reply, false); // canRedefineClasses
+ expandBufAdd1(reply, false); // canAddMethod
+ expandBufAdd1(reply, false); // canUnrestrictedlyRedefineClasses
+ expandBufAdd1(reply, false); // canPopFrames
+ expandBufAdd1(reply, false); // canUseInstanceFilters
+ expandBufAdd1(reply, false); // canGetSourceDebugExtension
+ expandBufAdd1(reply, false); // canRequestVMDeathEvent
+ expandBufAdd1(reply, false); // canSetDefaultStratum
+ expandBufAdd1(reply, true); // 1.6: canGetInstanceInfo
+ expandBufAdd1(reply, false); // 1.6: canRequestMonitorEvents
+ expandBufAdd1(reply, true); // 1.6: canGetMonitorFrameInfo
+ expandBufAdd1(reply, false); // 1.6: canUseSourceNameFilters
+ expandBufAdd1(reply, false); // 1.6: canGetConstantPool
+ expandBufAdd1(reply, false); // 1.6: canForceEarlyReturn
+
+ // Fill in reserved22 through reserved32; note count started at 1.
+ for (size_t i = 22; i <= 32; ++i) {
+ expandBufAdd1(reply, false);
+ }
+ return ERR_NONE;
+}
+
+static JdwpError VM_AllClassesImpl(ExpandBuf* pReply, bool descriptor_and_status, bool generic)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::vector<JDWP::RefTypeId> classes;
+ Dbg::GetClassList(classes);
+
+ expandBufAdd4BE(pReply, classes.size());
+
+ for (size_t i = 0; i < classes.size(); ++i) {
+ static const char genericSignature[1] = "";
+ JDWP::JdwpTypeTag type_tag;
+ std::string descriptor;
+ uint32_t class_status;
+ JDWP::JdwpError status = Dbg::GetClassInfo(classes[i], &type_tag, &class_status, &descriptor);
+ if (status != ERR_NONE) {
+ return status;
+ }
+
+ expandBufAdd1(pReply, type_tag);
+ expandBufAddRefTypeId(pReply, classes[i]);
+ if (descriptor_and_status) {
+ expandBufAddUtf8String(pReply, descriptor);
+ if (generic) {
+ expandBufAddUtf8String(pReply, genericSignature);
+ }
+ expandBufAdd4BE(pReply, class_status);
+ }
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError VM_AllClasses(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return VM_AllClassesImpl(pReply, true, false);
+}
+
+static JdwpError VM_AllClassesWithGeneric(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return VM_AllClassesImpl(pReply, true, true);
+}
+
+static JdwpError VM_InstanceCounts(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ int32_t class_count = request.ReadSigned32("class count");
+ if (class_count < 0) {
+ return ERR_ILLEGAL_ARGUMENT;
+ }
+ std::vector<RefTypeId> class_ids;
+ for (int32_t i = 0; i < class_count; ++i) {
+ class_ids.push_back(request.ReadRefTypeId());
+ }
+
+ std::vector<uint64_t> counts;
+ JdwpError rc = Dbg::GetInstanceCounts(class_ids, counts);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ expandBufAdd4BE(pReply, counts.size());
+ for (size_t i = 0; i < counts.size(); ++i) {
+ expandBufAdd8BE(pReply, counts[i]);
+ }
+ return ERR_NONE;
+}
+
+static JdwpError RT_Modifiers(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::GetModifiers(refTypeId, pReply);
+}
+
+/*
+ * Get values from static fields in a reference type.
+ */
+static JdwpError RT_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ int32_t field_count = request.ReadSigned32("field count");
+ expandBufAdd4BE(pReply, field_count);
+ for (int32_t i = 0; i < field_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+ JdwpError status = Dbg::GetStaticFieldValue(refTypeId, fieldId, pReply);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Get the name of the source file in which a reference type was declared.
+ */
+static JdwpError RT_SourceFile(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ std::string source_file;
+ JdwpError status = Dbg::GetSourceFile(refTypeId, source_file);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAddUtf8String(pReply, source_file);
+ return ERR_NONE;
+}
+
+/*
+ * Return the current status of the reference type.
+ */
+static JdwpError RT_Status(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ JDWP::JdwpTypeTag type_tag;
+ uint32_t class_status;
+ JDWP::JdwpError status = Dbg::GetClassInfo(refTypeId, &type_tag, &class_status, NULL);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAdd4BE(pReply, class_status);
+ return ERR_NONE;
+}
+
+/*
+ * Return interfaces implemented directly by this class.
+ */
+static JdwpError RT_Interfaces(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredInterfaces(refTypeId, pReply);
+}
+
+/*
+ * Return the class object corresponding to this type.
+ */
+static JdwpError RT_ClassObject(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ ObjectId class_object_id;
+ JdwpError status = Dbg::GetClassObject(refTypeId, class_object_id);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ VLOG(jdwp) << StringPrintf(" --> ObjectId %#llx", class_object_id);
+ expandBufAddObjectId(pReply, class_object_id);
+ return ERR_NONE;
+}
+
+/*
+ * Returns the value of the SourceDebugExtension attribute.
+ *
+ * JDB seems interested, but DEX files don't currently support this.
+ */
+static JdwpError RT_SourceDebugExtension(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ /* referenceTypeId in, string out */
+ return ERR_ABSENT_INFORMATION;
+}
+
+static JdwpError RT_Signature(JdwpState*, Request& request, ExpandBuf* pReply, bool with_generic)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+
+ std::string signature;
+ JdwpError status = Dbg::GetSignature(refTypeId, signature);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAddUtf8String(pReply, signature);
+ if (with_generic) {
+ expandBufAddUtf8String(pReply, "");
+ }
+ return ERR_NONE;
+}
+
+static JdwpError RT_Signature(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return RT_Signature(state, request, pReply, false);
+}
+
+static JdwpError RT_SignatureWithGeneric(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return RT_Signature(state, request, pReply, true);
+}
+
+/*
+ * Return the instance of java.lang.ClassLoader that loaded the specified
+ * reference type, or null if it was loaded by the system loader.
+ */
+static JdwpError RT_ClassLoader(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::GetClassLoader(refTypeId, pReply);
+}
+
+/*
+ * Given a referenceTypeId, return a block of stuff that describes the
+ * fields declared by a class.
+ */
+static JdwpError RT_FieldsWithGeneric(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredFields(refTypeId, true, pReply);
+}
+
+// Obsolete equivalent of FieldsWithGeneric, without the generic type information.
+static JdwpError RT_Fields(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredFields(refTypeId, false, pReply);
+}
+
+/*
+ * Given a referenceTypeID, return a block of goodies describing the
+ * methods declared by a class.
+ */
+static JdwpError RT_MethodsWithGeneric(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredMethods(refTypeId, true, pReply);
+}
+
+// Obsolete equivalent of MethodsWithGeneric, without the generic type information.
+static JdwpError RT_Methods(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredMethods(refTypeId, false, pReply);
+}
+
+static JdwpError RT_Instances(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ int32_t max_count = request.ReadSigned32("max count");
+ if (max_count < 0) {
+ return ERR_ILLEGAL_ARGUMENT;
+ }
+
+ std::vector<ObjectId> instances;
+ JdwpError rc = Dbg::GetInstances(class_id, max_count, instances);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ return WriteTaggedObjectList(reply, instances);
+}
+
+/*
+ * Return the immediate superclass of a class.
+ */
+static JdwpError CT_Superclass(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ RefTypeId superClassId;
+ JdwpError status = Dbg::GetSuperclass(class_id, superClassId);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAddRefTypeId(pReply, superClassId);
+ return ERR_NONE;
+}
+
+/*
+ * Set static class values.
+ */
+static JdwpError CT_SetValues(JdwpState* , Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ int32_t values_count = request.ReadSigned32("values count");
+
+ UNUSED(class_id);
+
+ for (int32_t i = 0; i < values_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+ JDWP::JdwpTag fieldTag = Dbg::GetStaticFieldBasicTag(fieldId);
+ size_t width = Dbg::GetTagWidth(fieldTag);
+ uint64_t value = request.ReadValue(width);
+
+ VLOG(jdwp) << " --> field=" << fieldId << " tag=" << fieldTag << " --> " << value;
+ JdwpError status = Dbg::SetStaticFieldValue(fieldId, value, width);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Invoke a static method.
+ *
+ * Example: Eclipse sometimes uses java/lang/Class.forName(String s) on
+ * values in the "variables" display.
+ */
+static JdwpError CT_InvokeMethod(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ ObjectId thread_id = request.ReadThreadId();
+ MethodId method_id = request.ReadMethodId();
+
+ return FinishInvoke(state, request, pReply, thread_id, 0, class_id, method_id, false);
+}
+
+/*
+ * Create a new object of the requested type, and invoke the specified
+ * constructor.
+ *
+ * Example: in IntelliJ, create a watch on "new String(myByteArray)" to
+ * see the contents of a byte[] as a string.
+ */
+static JdwpError CT_NewInstance(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ ObjectId thread_id = request.ReadThreadId();
+ MethodId method_id = request.ReadMethodId();
+
+ ObjectId object_id;
+ JdwpError status = Dbg::CreateObject(class_id, object_id);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ if (object_id == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ return FinishInvoke(state, request, pReply, thread_id, object_id, class_id, method_id, true);
+}
+
+/*
+ * Create a new array object of the requested type and length.
+ */
+static JdwpError AT_newInstance(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId arrayTypeId = request.ReadRefTypeId();
+ int32_t length = request.ReadSigned32("length");
+
+ ObjectId object_id;
+ JdwpError status = Dbg::CreateArrayObject(arrayTypeId, length, object_id);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ if (object_id == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ expandBufAdd1(pReply, JT_ARRAY);
+ expandBufAddObjectId(pReply, object_id);
+ return ERR_NONE;
+}
+
+/*
+ * Return line number information for the method, if present.
+ */
+static JdwpError M_LineTable(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ Dbg::OutputLineTable(refTypeId, method_id, pReply);
+
+ return ERR_NONE;
+}
+
+static JdwpError M_VariableTable(JdwpState*, Request& request, ExpandBuf* pReply,
+ bool generic)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ // We could return ERR_ABSENT_INFORMATION here if the DEX file was built without local variable
+ // information. That will cause Eclipse to make a best-effort attempt at displaying local
+ // variables anonymously. However, the attempt isn't very good, so we're probably better off just
+ // not showing anything.
+ Dbg::OutputVariableTable(class_id, method_id, generic, pReply);
+ return ERR_NONE;
+}
+
+static JdwpError M_VariableTable(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return M_VariableTable(state, request, pReply, false);
+}
+
+static JdwpError M_VariableTableWithGeneric(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return M_VariableTable(state, request, pReply, true);
+}
+
+static JdwpError M_Bytecodes(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ std::vector<uint8_t> bytecodes;
+ JdwpError rc = Dbg::GetBytecodes(class_id, method_id, bytecodes);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ expandBufAdd4BE(reply, bytecodes.size());
+ for (size_t i = 0; i < bytecodes.size(); ++i) {
+ expandBufAdd1(reply, bytecodes[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Given an object reference, return the runtime type of the object
+ * (class or array).
+ *
+ * This can get called on different things, e.g. thread_id gets
+ * passed in here.
+ */
+static JdwpError OR_ReferenceType(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::GetReferenceType(object_id, pReply);
+}
+
+/*
+ * Get values from the fields of an object.
+ */
+static JdwpError OR_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ int32_t field_count = request.ReadSigned32("field count");
+
+ expandBufAdd4BE(pReply, field_count);
+ for (int32_t i = 0; i < field_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+ JdwpError status = Dbg::GetFieldValue(object_id, fieldId, pReply);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set values in the fields of an object.
+ */
+static JdwpError OR_SetValues(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ int32_t field_count = request.ReadSigned32("field count");
+
+ for (int32_t i = 0; i < field_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+
+ JDWP::JdwpTag fieldTag = Dbg::GetFieldBasicTag(fieldId);
+ size_t width = Dbg::GetTagWidth(fieldTag);
+ uint64_t value = request.ReadValue(width);
+
+ VLOG(jdwp) << " --> fieldId=" << fieldId << " tag=" << fieldTag << "(" << width << ") value=" << value;
+ JdwpError status = Dbg::SetFieldValue(object_id, fieldId, value, width);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError OR_MonitorInfo(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::GetMonitorInfo(object_id, reply);
+}
+
+/*
+ * Invoke an instance method. The invocation must occur in the specified
+ * thread, which must have been suspended by an event.
+ *
+ * The call is synchronous. All threads in the VM are resumed, unless the
+ * SINGLE_THREADED flag is set.
+ *
+ * If you ask Eclipse to "inspect" an object (or ask JDB to "print" an
+ * object), it will try to invoke the object's toString() function. This
+ * feature becomes crucial when examining ArrayLists with Eclipse.
+ */
+static JdwpError OR_InvokeMethod(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ ObjectId thread_id = request.ReadThreadId();
+ RefTypeId class_id = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ return FinishInvoke(state, request, pReply, thread_id, object_id, class_id, method_id, false);
+}
+
+static JdwpError OR_DisableCollection(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::DisableCollection(object_id);
+}
+
+static JdwpError OR_EnableCollection(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::EnableCollection(object_id);
+}
+
+static JdwpError OR_IsCollected(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ bool is_collected;
+ JdwpError rc = Dbg::IsCollected(object_id, is_collected);
+ expandBufAdd1(pReply, is_collected ? 1 : 0);
+ return rc;
+}
+
+static JdwpError OR_ReferringObjects(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ int32_t max_count = request.ReadSigned32("max count");
+ if (max_count < 0) {
+ return ERR_ILLEGAL_ARGUMENT;
+ }
+
+ std::vector<ObjectId> referring_objects;
+ JdwpError rc = Dbg::GetReferringObjects(object_id, max_count, referring_objects);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ return WriteTaggedObjectList(reply, referring_objects);
+}
+
+/*
+ * Return the string value in a string object.
+ */
+static JdwpError SR_Value(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId stringObject = request.ReadObjectId();
+ std::string str(Dbg::StringToUtf8(stringObject));
+
+ VLOG(jdwp) << StringPrintf(" --> %s", PrintableString(str).c_str());
+
+ expandBufAddUtf8String(pReply, str);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return a thread's name.
+ */
+static JdwpError TR_Name(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ std::string name;
+ JdwpError error = Dbg::GetThreadName(thread_id, name);
+ if (error != ERR_NONE) {
+ return error;
+ }
+ VLOG(jdwp) << StringPrintf(" Name of thread %#llx is \"%s\"", thread_id, name.c_str());
+ expandBufAddUtf8String(pReply, name);
+
+ return ERR_NONE;
+}
+
+/*
+ * Suspend the specified thread.
+ *
+ * It's supposed to remain suspended even if interpreted code wants to
+ * resume it; only the JDI is allowed to resume it.
+ */
+static JdwpError TR_Suspend(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ if (thread_id == Dbg::GetThreadSelfId()) {
+ LOG(INFO) << " Warning: ignoring request to suspend self";
+ return ERR_THREAD_NOT_SUSPENDED;
+ }
+
+ Thread* self = Thread::Current();
+ self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend);
+ JdwpError result = Dbg::SuspendThread(thread_id);
+ self->TransitionFromSuspendedToRunnable();
+ return result;
+}
+
+/*
+ * Resume the specified thread.
+ */
+static JdwpError TR_Resume(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ if (thread_id == Dbg::GetThreadSelfId()) {
+ LOG(INFO) << " Warning: ignoring request to resume self";
+ return ERR_NONE;
+ }
+
+ Dbg::ResumeThread(thread_id);
+ return ERR_NONE;
+}
+
+/*
+ * Return status of specified thread.
+ */
+static JdwpError TR_Status(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ JDWP::JdwpThreadStatus threadStatus;
+ JDWP::JdwpSuspendStatus suspendStatus;
+ JdwpError error = Dbg::GetThreadStatus(thread_id, &threadStatus, &suspendStatus);
+ if (error != ERR_NONE) {
+ return error;
+ }
+
+ VLOG(jdwp) << " --> " << threadStatus << ", " << suspendStatus;
+
+ expandBufAdd4BE(pReply, threadStatus);
+ expandBufAdd4BE(pReply, suspendStatus);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the thread group that the specified thread is a member of.
+ */
+static JdwpError TR_ThreadGroup(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ return Dbg::GetThreadGroup(thread_id, pReply);
+}
+
+/*
+ * Return the current call stack of a suspended thread.
+ *
+ * If the thread isn't suspended, the error code isn't defined, but should
+ * be THREAD_NOT_SUSPENDED.
+ */
+static JdwpError TR_Frames(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ uint32_t start_frame = request.ReadUnsigned32("start frame");
+ uint32_t length = request.ReadUnsigned32("length");
+
+ size_t actual_frame_count;
+ JdwpError error = Dbg::GetThreadFrameCount(thread_id, actual_frame_count);
+ if (error != ERR_NONE) {
+ return error;
+ }
+
+ if (actual_frame_count <= 0) {
+ return ERR_THREAD_NOT_SUSPENDED; // 0 means no managed frames (which means "in native").
+ }
+
+ if (start_frame > actual_frame_count) {
+ return ERR_INVALID_INDEX;
+ }
+ if (length == static_cast<uint32_t>(-1)) {
+ length = actual_frame_count - start_frame;
+ }
+ if (start_frame + length > actual_frame_count) {
+ return ERR_INVALID_LENGTH;
+ }
+
+ return Dbg::GetThreadFrames(thread_id, start_frame, length, pReply);
+}
+
+/*
+ * Returns the #of frames on the specified thread, which must be suspended.
+ */
+static JdwpError TR_FrameCount(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ size_t frame_count;
+ JdwpError rc = Dbg::GetThreadFrameCount(thread_id, frame_count);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ expandBufAdd4BE(pReply, static_cast<uint32_t>(frame_count));
+
+ return ERR_NONE;
+}
+
+static JdwpError TR_OwnedMonitors(Request& request, ExpandBuf* reply, bool with_stack_depths)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ std::vector<ObjectId> monitors;
+ std::vector<uint32_t> stack_depths;
+ JdwpError rc = Dbg::GetOwnedMonitors(thread_id, monitors, stack_depths);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ expandBufAdd4BE(reply, monitors.size());
+ for (size_t i = 0; i < monitors.size(); ++i) {
+ rc = WriteTaggedObject(reply, monitors[i]);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ if (with_stack_depths) {
+ expandBufAdd4BE(reply, stack_depths[i]);
+ }
+ }
+ return ERR_NONE;
+}
+
+static JdwpError TR_OwnedMonitors(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return TR_OwnedMonitors(request, reply, false);
+}
+
+static JdwpError TR_OwnedMonitorsStackDepthInfo(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return TR_OwnedMonitors(request, reply, true);
+}
+
+static JdwpError TR_CurrentContendedMonitor(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ ObjectId contended_monitor;
+ JdwpError rc = Dbg::GetContendedMonitor(thread_id, contended_monitor);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ return WriteTaggedObject(reply, contended_monitor);
+}
+
+static JdwpError TR_Interrupt(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ return Dbg::Interrupt(thread_id);
+}
+
+/*
+ * Return the debug suspend count for the specified thread.
+ *
+ * (The thread *might* still be running -- it might not have examined
+ * its suspend count recently.)
+ */
+static JdwpError TR_DebugSuspendCount(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ return Dbg::GetThreadDebugSuspendCount(thread_id, pReply);
+}
+
+/*
+ * Return the name of a thread group.
+ *
+ * The Eclipse debugger recognizes "main" and "system" as special.
+ */
+static JdwpError TGR_Name(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_group_id = request.ReadThreadGroupId();
+
+ expandBufAddUtf8String(pReply, Dbg::GetThreadGroupName(thread_group_id));
+
+ return ERR_NONE;
+}
+
+/*
+ * Returns the thread group -- if any -- that contains the specified
+ * thread group.
+ */
+static JdwpError TGR_Parent(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_group_id = request.ReadThreadGroupId();
+
+ ObjectId parentGroup = Dbg::GetThreadGroupParent(thread_group_id);
+ expandBufAddObjectId(pReply, parentGroup);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the active threads and thread groups that are part of the
+ * specified thread group.
+ */
+static JdwpError TGR_Children(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_group_id = request.ReadThreadGroupId();
+
+ std::vector<ObjectId> thread_ids;
+ Dbg::GetThreads(thread_group_id, thread_ids);
+ expandBufAdd4BE(pReply, thread_ids.size());
+ for (uint32_t i = 0; i < thread_ids.size(); ++i) {
+ expandBufAddObjectId(pReply, thread_ids[i]);
+ }
+
+ std::vector<ObjectId> child_thread_groups_ids;
+ Dbg::GetChildThreadGroups(thread_group_id, child_thread_groups_ids);
+ expandBufAdd4BE(pReply, child_thread_groups_ids.size());
+ for (uint32_t i = 0; i < child_thread_groups_ids.size(); ++i) {
+ expandBufAddObjectId(pReply, child_thread_groups_ids[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the #of components in the array.
+ */
+static JdwpError AR_Length(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId array_id = request.ReadArrayId();
+
+ int length;
+ JdwpError status = Dbg::GetArrayLength(array_id, length);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ VLOG(jdwp) << " --> " << length;
+
+ expandBufAdd4BE(pReply, length);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the values from an array.
+ */
+static JdwpError AR_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId array_id = request.ReadArrayId();
+ uint32_t offset = request.ReadUnsigned32("offset");
+ uint32_t length = request.ReadUnsigned32("length");
+ return Dbg::OutputArray(array_id, offset, length, pReply);
+}
+
+/*
+ * Set values in an array.
+ */
+static JdwpError AR_SetValues(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId array_id = request.ReadArrayId();
+ uint32_t offset = request.ReadUnsigned32("offset");
+ uint32_t count = request.ReadUnsigned32("count");
+ return Dbg::SetArrayElements(array_id, offset, count, request);
+}
+
+static JdwpError CLR_VisibleClasses(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ request.ReadObjectId(); // classLoaderObject
+ // TODO: we should only return classes which have the given class loader as a defining or
+ // initiating loader. The former would be easy; the latter is hard, because we don't have
+ // any such notion.
+ return VM_AllClassesImpl(pReply, false, false);
+}
+
+/*
+ * Set an event trigger.
+ *
+ * Reply with a requestID.
+ */
+static JdwpError ER_Set(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ JdwpEventKind event_kind = request.ReadEnum1<JdwpEventKind>("event kind");
+ JdwpSuspendPolicy suspend_policy = request.ReadEnum1<JdwpSuspendPolicy>("suspend policy");
+ int32_t modifier_count = request.ReadSigned32("modifier count");
+
+ CHECK_LT(modifier_count, 256); /* reasonableness check */
+
+ JdwpEvent* pEvent = EventAlloc(modifier_count);
+ pEvent->eventKind = event_kind;
+ pEvent->suspend_policy = suspend_policy;
+ pEvent->modCount = modifier_count;
+
+ /*
+ * Read modifiers. Ordering may be significant (see explanation of Count
+ * mods in JDWP doc).
+ */
+ for (int32_t i = 0; i < modifier_count; ++i) {
+ JdwpEventMod& mod = pEvent->mods[i];
+ mod.modKind = request.ReadModKind();
+ switch (mod.modKind) {
+ case MK_COUNT:
+ {
+ // Report once, when "--count" reaches 0.
+ uint32_t count = request.ReadUnsigned32("count");
+ if (count == 0) {
+ return ERR_INVALID_COUNT;
+ }
+ mod.count.count = count;
+ }
+ break;
+ case MK_CONDITIONAL:
+ {
+ // Conditional on expression.
+ uint32_t exprId = request.ReadUnsigned32("expr id");
+ mod.conditional.exprId = exprId;
+ }
+ break;
+ case MK_THREAD_ONLY:
+ {
+ // Only report events in specified thread.
+ ObjectId thread_id = request.ReadThreadId();
+ mod.threadOnly.threadId = thread_id;
+ }
+ break;
+ case MK_CLASS_ONLY:
+ {
+ // For ClassPrepare, MethodEntry.
+ RefTypeId class_id = request.ReadRefTypeId();
+ mod.classOnly.refTypeId = class_id;
+ }
+ break;
+ case MK_CLASS_MATCH:
+ {
+ // Restrict events to matching classes.
+ // pattern is "java.foo.*", we want "java/foo/*".
+ std::string pattern(request.ReadUtf8String());
+ std::replace(pattern.begin(), pattern.end(), '.', '/');
+ mod.classMatch.classPattern = strdup(pattern.c_str());
+ }
+ break;
+ case MK_CLASS_EXCLUDE:
+ {
+ // Restrict events to non-matching classes.
+ // pattern is "java.foo.*", we want "java/foo/*".
+ std::string pattern(request.ReadUtf8String());
+ std::replace(pattern.begin(), pattern.end(), '.', '/');
+ mod.classExclude.classPattern = strdup(pattern.c_str());
+ }
+ break;
+ case MK_LOCATION_ONLY:
+ {
+ // Restrict certain events based on location.
+ JdwpLocation location = request.ReadLocation();
+ mod.locationOnly.loc = location;
+ }
+ break;
+ case MK_EXCEPTION_ONLY:
+ {
+ // Modifies EK_EXCEPTION events,
+ mod.exceptionOnly.refTypeId = request.ReadRefTypeId(); // null => all exceptions.
+ mod.exceptionOnly.caught = request.ReadEnum1<uint8_t>("caught");
+ mod.exceptionOnly.uncaught = request.ReadEnum1<uint8_t>("uncaught");
+ }
+ break;
+ case MK_FIELD_ONLY:
+ {
+ // For field access/modification events.
+ RefTypeId declaring = request.ReadRefTypeId();
+ FieldId fieldId = request.ReadFieldId();
+ mod.fieldOnly.refTypeId = declaring;
+ mod.fieldOnly.fieldId = fieldId;
+ }
+ break;
+ case MK_STEP:
+ {
+ // For use with EK_SINGLE_STEP.
+ ObjectId thread_id = request.ReadThreadId();
+ uint32_t size = request.ReadUnsigned32("step size");
+ uint32_t depth = request.ReadUnsigned32("step depth");
+ VLOG(jdwp) << StringPrintf(" Step: thread=%#llx", thread_id)
+ << " size=" << JdwpStepSize(size) << " depth=" << JdwpStepDepth(depth);
+
+ mod.step.threadId = thread_id;
+ mod.step.size = size;
+ mod.step.depth = depth;
+ }
+ break;
+ case MK_INSTANCE_ONLY:
+ {
+ // Report events related to a specific object.
+ ObjectId instance = request.ReadObjectId();
+ mod.instanceOnly.objectId = instance;
+ }
+ break;
+ default:
+ LOG(WARNING) << "GLITCH: unsupported modKind=" << mod.modKind;
+ break;
+ }
+ }
+
+ /*
+ * We reply with an integer "requestID".
+ */
+ uint32_t requestId = state->NextEventSerial();
+ expandBufAdd4BE(pReply, requestId);
+
+ pEvent->requestId = requestId;
+
+ VLOG(jdwp) << StringPrintf(" --> event requestId=%#x", requestId);
+
+ /* add it to the list */
+ JdwpError err = state->RegisterEvent(pEvent);
+ if (err != ERR_NONE) {
+ /* registration failed, probably because event is bogus */
+ EventFree(pEvent);
+ LOG(WARNING) << "WARNING: event request rejected";
+ }
+ return err;
+}
+
+static JdwpError ER_Clear(JdwpState* state, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ request.ReadEnum1<JdwpEventKind>("event kind");
+ uint32_t requestId = request.ReadUnsigned32("request id");
+
+ // Failure to find an event with a matching ID is a no-op
+ // and does not return an error.
+ state->UnregisterEventById(requestId);
+ return ERR_NONE;
+}
+
+/*
+ * Return the values of arguments and local variables.
+ */
+static JdwpError SF_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ FrameId frame_id = request.ReadFrameId();
+ int32_t slot_count = request.ReadSigned32("slot count");
+
+ expandBufAdd4BE(pReply, slot_count); /* "int values" */
+ for (int32_t i = 0; i < slot_count; ++i) {
+ uint32_t slot = request.ReadUnsigned32("slot");
+ JDWP::JdwpTag reqSigByte = request.ReadTag();
+
+ VLOG(jdwp) << " --> slot " << slot << " " << reqSigByte;
+
+ size_t width = Dbg::GetTagWidth(reqSigByte);
+ uint8_t* ptr = expandBufAddSpace(pReply, width+1);
+ Dbg::GetLocalValue(thread_id, frame_id, slot, reqSigByte, ptr, width);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set the values of arguments and local variables.
+ */
+static JdwpError SF_SetValues(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ FrameId frame_id = request.ReadFrameId();
+ int32_t slot_count = request.ReadSigned32("slot count");
+
+ for (int32_t i = 0; i < slot_count; ++i) {
+ uint32_t slot = request.ReadUnsigned32("slot");
+ JDWP::JdwpTag sigByte = request.ReadTag();
+ size_t width = Dbg::GetTagWidth(sigByte);
+ uint64_t value = request.ReadValue(width);
+
+ VLOG(jdwp) << " --> slot " << slot << " " << sigByte << " " << value;
+ Dbg::SetLocalValue(thread_id, frame_id, slot, sigByte, value, width);
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError SF_ThisObject(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ FrameId frame_id = request.ReadFrameId();
+
+ ObjectId object_id;
+ JdwpError rc = Dbg::GetThisObject(thread_id, frame_id, &object_id);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ return WriteTaggedObject(reply, object_id);
+}
+
+/*
+ * Return the reference type reflected by this class object.
+ *
+ * This appears to be required because ReferenceTypeId values are NEVER
+ * reused, whereas ClassIds can be recycled like any other object. (Either
+ * that, or I have no idea what this is for.)
+ */
+static JdwpError COR_ReflectedType(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_object_id = request.ReadRefTypeId();
+ return Dbg::GetReflectedType(class_object_id, pReply);
+}
+
+/*
+ * Handle a DDM packet with a single chunk in it.
+ */
+static JdwpError DDM_Chunk(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ state->NotifyDdmsActive();
+ uint8_t* replyBuf = NULL;
+ int replyLen = -1;
+ if (Dbg::DdmHandlePacket(request, &replyBuf, &replyLen)) {
+ // If they want to send something back, we copy it into the buffer.
+ // TODO: consider altering the JDWP stuff to hold the packet header
+ // in a separate buffer. That would allow us to writev() DDM traffic
+ // instead of copying it into the expanding buffer. The reduction in
+ // heap requirements is probably more valuable than the efficiency.
+ CHECK_GT(replyLen, 0);
+ CHECK_LT(replyLen, 1*1024*1024);
+ memcpy(expandBufAddSpace(pReply, replyLen), replyBuf, replyLen);
+ free(replyBuf);
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Handler map decl.
+ */
+typedef JdwpError (*JdwpRequestHandler)(JdwpState* state, Request& request, ExpandBuf* reply);
+
+struct JdwpHandlerMap {
+ uint8_t cmdSet;
+ uint8_t cmd;
+ JdwpRequestHandler func;
+ const char* name;
+};
+
+/*
+ * Map commands to functions.
+ *
+ * Command sets 0-63 are incoming requests, 64-127 are outbound requests,
+ * and 128-256 are vendor-defined.
+ */
+static const JdwpHandlerMap gHandlers[] = {
+ /* VirtualMachine command set (1) */
+ { 1, 1, VM_Version, "VirtualMachine.Version" },
+ { 1, 2, VM_ClassesBySignature, "VirtualMachine.ClassesBySignature" },
+ { 1, 3, VM_AllClasses, "VirtualMachine.AllClasses" },
+ { 1, 4, VM_AllThreads, "VirtualMachine.AllThreads" },
+ { 1, 5, VM_TopLevelThreadGroups, "VirtualMachine.TopLevelThreadGroups" },
+ { 1, 6, VM_Dispose, "VirtualMachine.Dispose" },
+ { 1, 7, VM_IDSizes, "VirtualMachine.IDSizes" },
+ { 1, 8, VM_Suspend, "VirtualMachine.Suspend" },
+ { 1, 9, VM_Resume, "VirtualMachine.Resume" },
+ { 1, 10, VM_Exit, "VirtualMachine.Exit" },
+ { 1, 11, VM_CreateString, "VirtualMachine.CreateString" },
+ { 1, 12, VM_Capabilities, "VirtualMachine.Capabilities" },
+ { 1, 13, VM_ClassPaths, "VirtualMachine.ClassPaths" },
+ { 1, 14, VM_DisposeObjects, "VirtualMachine.DisposeObjects" },
+ { 1, 15, NULL, "VirtualMachine.HoldEvents" },
+ { 1, 16, NULL, "VirtualMachine.ReleaseEvents" },
+ { 1, 17, VM_CapabilitiesNew, "VirtualMachine.CapabilitiesNew" },
+ { 1, 18, NULL, "VirtualMachine.RedefineClasses" },
+ { 1, 19, NULL, "VirtualMachine.SetDefaultStratum" },
+ { 1, 20, VM_AllClassesWithGeneric, "VirtualMachine.AllClassesWithGeneric" },
+ { 1, 21, VM_InstanceCounts, "VirtualMachine.InstanceCounts" },
+
+ /* ReferenceType command set (2) */
+ { 2, 1, RT_Signature, "ReferenceType.Signature" },
+ { 2, 2, RT_ClassLoader, "ReferenceType.ClassLoader" },
+ { 2, 3, RT_Modifiers, "ReferenceType.Modifiers" },
+ { 2, 4, RT_Fields, "ReferenceType.Fields" },
+ { 2, 5, RT_Methods, "ReferenceType.Methods" },
+ { 2, 6, RT_GetValues, "ReferenceType.GetValues" },
+ { 2, 7, RT_SourceFile, "ReferenceType.SourceFile" },
+ { 2, 8, NULL, "ReferenceType.NestedTypes" },
+ { 2, 9, RT_Status, "ReferenceType.Status" },
+ { 2, 10, RT_Interfaces, "ReferenceType.Interfaces" },
+ { 2, 11, RT_ClassObject, "ReferenceType.ClassObject" },
+ { 2, 12, RT_SourceDebugExtension, "ReferenceType.SourceDebugExtension" },
+ { 2, 13, RT_SignatureWithGeneric, "ReferenceType.SignatureWithGeneric" },
+ { 2, 14, RT_FieldsWithGeneric, "ReferenceType.FieldsWithGeneric" },
+ { 2, 15, RT_MethodsWithGeneric, "ReferenceType.MethodsWithGeneric" },
+ { 2, 16, RT_Instances, "ReferenceType.Instances" },
+ { 2, 17, NULL, "ReferenceType.ClassFileVersion" },
+ { 2, 18, NULL, "ReferenceType.ConstantPool" },
+
+ /* ClassType command set (3) */
+ { 3, 1, CT_Superclass, "ClassType.Superclass" },
+ { 3, 2, CT_SetValues, "ClassType.SetValues" },
+ { 3, 3, CT_InvokeMethod, "ClassType.InvokeMethod" },
+ { 3, 4, CT_NewInstance, "ClassType.NewInstance" },
+
+ /* ArrayType command set (4) */
+ { 4, 1, AT_newInstance, "ArrayType.NewInstance" },
+
+ /* InterfaceType command set (5) */
+
+ /* Method command set (6) */
+ { 6, 1, M_LineTable, "Method.LineTable" },
+ { 6, 2, M_VariableTable, "Method.VariableTable" },
+ { 6, 3, M_Bytecodes, "Method.Bytecodes" },
+ { 6, 4, NULL, "Method.IsObsolete" },
+ { 6, 5, M_VariableTableWithGeneric, "Method.VariableTableWithGeneric" },
+
+ /* Field command set (8) */
+
+ /* ObjectReference command set (9) */
+ { 9, 1, OR_ReferenceType, "ObjectReference.ReferenceType" },
+ { 9, 2, OR_GetValues, "ObjectReference.GetValues" },
+ { 9, 3, OR_SetValues, "ObjectReference.SetValues" },
+ { 9, 4, NULL, "ObjectReference.UNUSED" },
+ { 9, 5, OR_MonitorInfo, "ObjectReference.MonitorInfo" },
+ { 9, 6, OR_InvokeMethod, "ObjectReference.InvokeMethod" },
+ { 9, 7, OR_DisableCollection, "ObjectReference.DisableCollection" },
+ { 9, 8, OR_EnableCollection, "ObjectReference.EnableCollection" },
+ { 9, 9, OR_IsCollected, "ObjectReference.IsCollected" },
+ { 9, 10, OR_ReferringObjects, "ObjectReference.ReferringObjects" },
+
+ /* StringReference command set (10) */
+ { 10, 1, SR_Value, "StringReference.Value" },
+
+ /* ThreadReference command set (11) */
+ { 11, 1, TR_Name, "ThreadReference.Name" },
+ { 11, 2, TR_Suspend, "ThreadReference.Suspend" },
+ { 11, 3, TR_Resume, "ThreadReference.Resume" },
+ { 11, 4, TR_Status, "ThreadReference.Status" },
+ { 11, 5, TR_ThreadGroup, "ThreadReference.ThreadGroup" },
+ { 11, 6, TR_Frames, "ThreadReference.Frames" },
+ { 11, 7, TR_FrameCount, "ThreadReference.FrameCount" },
+ { 11, 8, TR_OwnedMonitors, "ThreadReference.OwnedMonitors" },
+ { 11, 9, TR_CurrentContendedMonitor, "ThreadReference.CurrentContendedMonitor" },
+ { 11, 10, NULL, "ThreadReference.Stop" },
+ { 11, 11, TR_Interrupt, "ThreadReference.Interrupt" },
+ { 11, 12, TR_DebugSuspendCount, "ThreadReference.SuspendCount" },
+ { 11, 13, TR_OwnedMonitorsStackDepthInfo, "ThreadReference.OwnedMonitorsStackDepthInfo" },
+ { 11, 14, NULL, "ThreadReference.ForceEarlyReturn" },
+
+ /* ThreadGroupReference command set (12) */
+ { 12, 1, TGR_Name, "ThreadGroupReference.Name" },
+ { 12, 2, TGR_Parent, "ThreadGroupReference.Parent" },
+ { 12, 3, TGR_Children, "ThreadGroupReference.Children" },
+
+ /* ArrayReference command set (13) */
+ { 13, 1, AR_Length, "ArrayReference.Length" },
+ { 13, 2, AR_GetValues, "ArrayReference.GetValues" },
+ { 13, 3, AR_SetValues, "ArrayReference.SetValues" },
+
+ /* ClassLoaderReference command set (14) */
+ { 14, 1, CLR_VisibleClasses, "ClassLoaderReference.VisibleClasses" },
+
+ /* EventRequest command set (15) */
+ { 15, 1, ER_Set, "EventRequest.Set" },
+ { 15, 2, ER_Clear, "EventRequest.Clear" },
+ { 15, 3, NULL, "EventRequest.ClearAllBreakpoints" },
+
+ /* StackFrame command set (16) */
+ { 16, 1, SF_GetValues, "StackFrame.GetValues" },
+ { 16, 2, SF_SetValues, "StackFrame.SetValues" },
+ { 16, 3, SF_ThisObject, "StackFrame.ThisObject" },
+ { 16, 4, NULL, "StackFrame.PopFrames" },
+
+ /* ClassObjectReference command set (17) */
+ { 17, 1, COR_ReflectedType, "ClassObjectReference.ReflectedType" },
+
+ /* Event command set (64) */
+ { 64, 100, NULL, "Event.Composite" }, // sent from VM to debugger, never received by VM
+
+ { 199, 1, DDM_Chunk, "DDM.Chunk" },
+};
+
+static const char* GetCommandName(Request& request) {
+ for (size_t i = 0; i < arraysize(gHandlers); ++i) {
+ if (gHandlers[i].cmdSet == request.GetCommandSet() && gHandlers[i].cmd == request.GetCommand()) {
+ return gHandlers[i].name;
+ }
+ }
+ return "?UNKNOWN?";
+}
+
+static std::string DescribeCommand(Request& request) {
+ std::string result;
+ result += "REQUEST: ";
+ result += GetCommandName(request);
+ result += StringPrintf(" (length=%d id=0x%06x)", request.GetLength(), request.GetId());
+ return result;
+}
+
+/*
+ * Process a request from the debugger.
+ *
+ * On entry, the JDWP thread is in VMWAIT.
+ */
+void JdwpState::ProcessRequest(Request& request, ExpandBuf* pReply) {
+ JdwpError result = ERR_NONE;
+
+ if (request.GetCommandSet() != kJDWPDdmCmdSet) {
+ /*
+ * Activity from a debugger, not merely ddms. Mark us as having an
+ * active debugger session, and zero out the last-activity timestamp
+ * so waitForDebugger() doesn't return if we stall for a bit here.
+ */
+ Dbg::GoActive();
+ QuasiAtomic::Write64(&last_activity_time_ms_, 0);
+ }
+
+ /*
+ * If a debugger event has fired in another thread, wait until the
+ * initiating thread has suspended itself before processing messages
+ * 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);
+
+ /*
+ * Tell the VM that we're running and shouldn't be interrupted by GC.
+ * Do this after anything that can stall indefinitely.
+ */
+ Thread* self = Thread::Current();
+ ThreadState old_state = self->TransitionFromSuspendedToRunnable();
+
+ expandBufAddSpace(pReply, kJDWPHeaderLen);
+
+ size_t i;
+ for (i = 0; i < arraysize(gHandlers); ++i) {
+ if (gHandlers[i].cmdSet == request.GetCommandSet() && gHandlers[i].cmd == request.GetCommand() && gHandlers[i].func != NULL) {
+ VLOG(jdwp) << DescribeCommand(request);
+ result = (*gHandlers[i].func)(this, request, pReply);
+ if (result == ERR_NONE) {
+ request.CheckConsumed();
+ }
+ break;
+ }
+ }
+ if (i == arraysize(gHandlers)) {
+ LOG(ERROR) << "Command not implemented: " << DescribeCommand(request);
+ LOG(ERROR) << HexDump(request.data(), request.size());
+ result = ERR_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * Set up the reply header.
+ *
+ * If we encountered an error, only send the header back.
+ */
+ uint8_t* replyBuf = expandBufGetBuffer(pReply);
+ Set4BE(replyBuf + 4, request.GetId());
+ Set1(replyBuf + 8, kJDWPFlagReply);
+ Set2BE(replyBuf + 9, result);
+ if (result == ERR_NONE) {
+ Set4BE(replyBuf + 0, expandBufGetLength(pReply));
+ } else {
+ Set4BE(replyBuf + 0, kJDWPHeaderLen);
+ }
+
+ CHECK_GT(expandBufGetLength(pReply), 0U) << GetCommandName(request) << " " << request.GetId();
+
+ size_t respLen = expandBufGetLength(pReply) - kJDWPHeaderLen;
+ VLOG(jdwp) << "REPLY: " << GetCommandName(request) << " " << result << " (length=" << respLen << ")";
+ if (false) {
+ VLOG(jdwp) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen);
+ }
+
+ VLOG(jdwp) << "----------";
+
+ /*
+ * Update last-activity timestamp. We really only need this during
+ * the initial setup. Only update if this is a non-DDMS packet.
+ */
+ if (request.GetCommandSet() != kJDWPDdmCmdSet) {
+ QuasiAtomic::Write64(&last_activity_time_ms_, MilliTime());
+ }
+
+ /* tell the VM that GC is okay again */
+ self->TransitionFromRunnableToSuspended(old_state);
+}
+
+} // namespace JDWP
+
+} // namespace art