Bring across the JDWP implementation.
This compiles and links, but does nothing until we fill out the 100 or so
unimplemented methods in "debugger.cc". Note that I also need to add the
extra command-line handling for the JDWP agent stuff, and add calls from
the runtime to the various "something interesting is going on" hooks.
Change-Id: I477cf3caf9e248c384ce1d739cbfadb60e2008bc
diff --git a/src/dalvik_system_VMDebug.cc b/src/dalvik_system_VMDebug.cc
index 0540631..4537a69 100644
--- a/src/dalvik_system_VMDebug.cc
+++ b/src/dalvik_system_VMDebug.cc
@@ -15,6 +15,7 @@
*/
#include "class_linker.h"
+#include "debugger.h"
#include "jni_internal.h"
#include "ScopedUtfChars.h"
#include "toStringArray.h"
@@ -112,18 +113,15 @@
}
jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
- UNIMPLEMENTED(WARNING);
- return JNI_FALSE; //dvmDbgIsDebuggerConnected();
+ return Dbg::IsDebuggerConnected();
}
jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
- UNIMPLEMENTED(WARNING);
- return JNI_FALSE; //return gDvm.jdwpConfigured;
+ return Dbg::IsDebuggingEnabled();
}
jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
- UNIMPLEMENTED(WARNING);
- return 0; //dvmDbgLastDebuggerActivity();
+ return Dbg::LastDebuggerActivity();
}
void VMDebug_startInstructionCounting(JNIEnv* env, jclass) {
diff --git a/src/debugger.cc b/src/debugger.cc
new file mode 100644
index 0000000..46644e7
--- /dev/null
+++ b/src/debugger.cc
@@ -0,0 +1,442 @@
+/*
+ * 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 "debugger.h"
+
+namespace art {
+
+bool Dbg::DebuggerStartup() {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::DebuggerShutdown() {
+ UNIMPLEMENTED(FATAL);
+}
+
+DebugInvokeReq* Dbg::GetInvokeReq() {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+void Dbg::Connected() {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::Active() {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::Disconnected() {
+ UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::IsDebuggerConnected() {
+ UNIMPLEMENTED(WARNING);
+ return false;
+}
+
+bool Dbg::IsDebuggingEnabled() {
+ UNIMPLEMENTED(WARNING);
+ return false; //return gDvm.jdwpConfigured;
+}
+
+int64_t Dbg::LastDebuggerActivity() {
+ UNIMPLEMENTED(WARNING);
+ return -1;
+}
+
+int Dbg::ThreadRunning() {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+int Dbg::ThreadWaiting() {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+int Dbg::ThreadContinuing(int status) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+void Dbg::UndoDebuggerSuspensions() {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::Exit(int status) {
+ UNIMPLEMENTED(FATAL);
+}
+
+const char* Dbg::GetClassDescriptor(JDWP::RefTypeId id) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+JDWP::ObjectId Dbg::GetClassObject(JDWP::RefTypeId id) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+JDWP::RefTypeId Dbg::GetSuperclass(JDWP::RefTypeId id) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+JDWP::ObjectId Dbg::GetClassLoader(JDWP::RefTypeId id) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+uint32_t Dbg::GetAccessFlags(JDWP::RefTypeId id) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+bool Dbg::IsInterface(JDWP::RefTypeId id) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::GetClassList(uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetVisibleClassList(JDWP::ObjectId classLoaderId, uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetClassInfo(JDWP::RefTypeId classId, uint8_t* pTypeTag, uint32_t* pStatus, const char** pSignature) {
+ UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::FindLoadedClassBySignature(const char* classDescriptor, JDWP::RefTypeId* pRefTypeId) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::GetObjectType(JDWP::ObjectId objectId, uint8_t* pRefTypeTag, JDWP::RefTypeId* pRefTypeId) {
+ UNIMPLEMENTED(FATAL);
+}
+
+uint8_t Dbg::GetClassObjectType(JDWP::RefTypeId refTypeId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+const char* Dbg::GetSignature(JDWP::RefTypeId refTypeId) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+const char* Dbg::GetSourceFile(JDWP::RefTypeId refTypeId) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+const char* Dbg::GetObjectTypeName(JDWP::ObjectId objectId) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+uint8_t Dbg::GetObjectTag(JDWP::ObjectId objectId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+int Dbg::GetTagWidth(int tag) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+int Dbg::GetArrayLength(JDWP::ObjectId arrayId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+uint8_t Dbg::GetArrayElementTag(JDWP::ObjectId arrayId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+bool Dbg::OutputArray(JDWP::ObjectId arrayId, int firstIndex, int count, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+bool Dbg::SetArrayElements(JDWP::ObjectId arrayId, int firstIndex, int count, const uint8_t* buf) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+JDWP::ObjectId Dbg::CreateString(const char* str) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+JDWP::ObjectId Dbg::CreateObject(JDWP::RefTypeId classId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+JDWP::ObjectId Dbg::CreateArrayObject(JDWP::RefTypeId arrayTypeId, uint32_t length) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+bool Dbg::MatchType(JDWP::RefTypeId instClassId, JDWP::RefTypeId classId) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+const char* Dbg::GetMethodName(JDWP::RefTypeId refTypeId, JDWP::MethodId id) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+void Dbg::OutputAllFields(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputAllMethods(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputAllInterfaces(JDWP::RefTypeId refTypeId, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputLineTable(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::OutputVariableTable(JDWP::RefTypeId refTypeId, JDWP::MethodId id, bool withGeneric, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+uint8_t Dbg::GetFieldBasicTag(JDWP::ObjectId objId, JDWP::FieldId fieldId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+uint8_t Dbg::GetStaticFieldBasicTag(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+void Dbg::GetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, uint64_t value, int width) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, uint64_t rawValue, int width) {
+ UNIMPLEMENTED(FATAL);
+}
+
+char* Dbg::StringToUtf8(JDWP::ObjectId strId) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+char* Dbg::GetThreadName(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+JDWP::ObjectId Dbg::GetThreadGroup(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+char* Dbg::GetThreadGroupName(JDWP::ObjectId threadGroupId) {
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+}
+
+JDWP::ObjectId Dbg::GetThreadGroupParent(JDWP::ObjectId threadGroupId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+JDWP::ObjectId Dbg::GetSystemThreadGroupId() {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+JDWP::ObjectId Dbg::GetMainThreadGroupId() {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+bool Dbg::GetThreadStatus(JDWP::ObjectId threadId, uint32_t* threadStatus, uint32_t* suspendStatus) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+uint32_t Dbg::GetThreadSuspendCount(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+bool Dbg::ThreadExists(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+bool Dbg::IsSuspended(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+//void Dbg::WaitForSuspend(JDWP::ObjectId threadId);
+
+void Dbg::GetThreadGroupThreads(JDWP::ObjectId threadGroupId, JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::GetAllThreads(JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount) {
+ UNIMPLEMENTED(FATAL);
+}
+
+int Dbg::GetThreadFrameCount(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+bool Dbg::GetThreadFrame(JDWP::ObjectId threadId, int num, JDWP::FrameId* pFrameId, JDWP::JdwpLocation* pLoc) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+JDWP::ObjectId Dbg::GetThreadSelfId() {
+ UNIMPLEMENTED(FATAL);
+ return 0;
+}
+
+void Dbg::SuspendVM(bool isEvent) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::ResumeVM() {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SuspendThread(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::ResumeThread(JDWP::ObjectId threadId) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SuspendSelf() {
+ UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint8_t* buf, int expectedLen) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint64_t value, int width) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::PostLocationEvent(const Method* method, int pcOffset, Object* thisPtr, int eventFlags) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::PostException(void* throwFp, int throwRelPc, void* catchFp, int catchRelPc, Object* exception) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::PostThreadStart(Thread* t) {
+ UNIMPLEMENTED(WARNING);
+}
+
+void Dbg::PostThreadDeath(Thread* t) {
+ UNIMPLEMENTED(WARNING);
+}
+
+void Dbg::PostClassPrepare(Class* c) {
+ UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::WatchLocation(const JDWP::JdwpLocation* pLoc) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::UnwatchLocation(const JDWP::JdwpLocation* pLoc) {
+ UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::ConfigureStep(JDWP::ObjectId threadId, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::UnconfigureStep(JDWP::ObjectId threadId) {
+ 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, uint8_t* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj) {
+ UNIMPLEMENTED(FATAL);
+ return JDWP::ERR_NONE;
+}
+
+void Dbg::ExecuteMethod(DebugInvokeReq* pReq) {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::RegisterObjectId(JDWP::ObjectId id) {
+ UNIMPLEMENTED(FATAL);
+}
+
+bool Dbg::DdmHandlePacket(const uint8_t* buf, int dataLen, uint8_t** pReplyBuf, int* pReplyLen) {
+ UNIMPLEMENTED(FATAL);
+ return false;
+}
+
+void Dbg::DdmConnected() {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::DdmDisconnected() {
+ UNIMPLEMENTED(FATAL);
+}
+
+void Dbg::DdmSendChunk(int type, size_t length, const uint8_t* buf) {
+ UNIMPLEMENTED(WARNING) << "DdmSendChunk(" << type << ", " << length << ", " << (void*) buf << ");";
+}
+
+void Dbg::DdmSendChunkV(int type, const struct iovec* iov, int iovcnt) {
+ UNIMPLEMENTED(FATAL);
+}
+
+} // namespace art
diff --git a/src/debugger.h b/src/debugger.h
new file mode 100644
index 0000000..b66fd11
--- /dev/null
+++ b/src/debugger.h
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+/*
+ * Dalvik-specific side of debugger support. (The JDWP code is intended to
+ * be relatively generic.)
+ */
+#ifndef ART_DEBUGGER_H_
+#define ART_DEBUGGER_H_
+
+#include <pthread.h>
+
+#include "object.h"
+#include "jdwp/jdwp.h"
+
+namespace art {
+
+struct Thread;
+
+/*
+ * Invoke-during-breakpoint support.
+ */
+struct DebugInvokeReq {
+ /* 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;
+
+ /* 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 options;
+
+ /* result */
+ JDWP::JdwpError err;
+ uint8_t resultTag;
+ JValue resultValue;
+ JDWP::ObjectId exceptObj;
+
+ /* condition variable to wait on while the method executes */
+ Mutex lock_;
+ ConditionVariable cond_;
+};
+
+class Dbg {
+public:
+ static bool DebuggerStartup();
+ static void DebuggerShutdown();
+
+ // Return the DebugInvokeReq for the current thread.
+ static DebugInvokeReq* GetInvokeReq();
+
+ /*
+ * Enable/disable breakpoints and step modes. Used to provide a heads-up
+ * when the debugger attaches.
+ */
+ static void Connected();
+ static void Active();
+ static void Disconnected();
+
+ /*
+ * Returns "true" if a debugger is connected. Returns "false" if it's
+ * just DDM.
+ */
+ static bool IsDebuggerConnected();
+
+ static bool IsDebuggingEnabled();
+
+ /*
+ * Time, in milliseconds, since the last debugger activity. Does not
+ * include DDMS activity. Returns -1 if there has been no activity.
+ * Returns 0 if we're in the middle of handling a debugger request.
+ */
+ static int64_t LastDebuggerActivity();
+
+ /*
+ * Block/allow GC depending on what we're doing. These return the old
+ * status, which can be fed to ThreadContinuing() to restore the previous
+ * mode.
+ */
+ static int ThreadRunning();
+ static int ThreadWaiting();
+ static int ThreadContinuing(int status);
+
+ static void UndoDebuggerSuspensions();
+
+ // The debugger wants the VM to exit.
+ static void Exit(int status);
+
+ /*
+ * Class, Object, Array
+ */
+ static const char* GetClassDescriptor(JDWP::RefTypeId id);
+ static JDWP::ObjectId GetClassObject(JDWP::RefTypeId id);
+ static JDWP::RefTypeId GetSuperclass(JDWP::RefTypeId id);
+ static JDWP::ObjectId GetClassLoader(JDWP::RefTypeId id);
+ static uint32_t GetAccessFlags(JDWP::RefTypeId id);
+ static bool IsInterface(JDWP::RefTypeId id);
+ static void GetClassList(uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf);
+ static void GetVisibleClassList(JDWP::ObjectId classLoaderId, uint32_t* pNumClasses, JDWP::RefTypeId** pClassRefBuf);
+ static void GetClassInfo(JDWP::RefTypeId classId, uint8_t* pTypeTag, uint32_t* pStatus, const char** pSignature);
+ static bool FindLoadedClassBySignature(const char* classDescriptor, JDWP::RefTypeId* pRefTypeId);
+ static void GetObjectType(JDWP::ObjectId objectId, uint8_t* pRefTypeTag, JDWP::RefTypeId* pRefTypeId);
+ static uint8_t GetClassObjectType(JDWP::RefTypeId refTypeId);
+ static const char* GetSignature(JDWP::RefTypeId refTypeId);
+ static const char* GetSourceFile(JDWP::RefTypeId refTypeId);
+ static const char* GetObjectTypeName(JDWP::ObjectId objectId);
+ static uint8_t GetObjectTag(JDWP::ObjectId objectId);
+ static int GetTagWidth(int tag);
+
+ static int GetArrayLength(JDWP::ObjectId arrayId);
+ static uint8_t GetArrayElementTag(JDWP::ObjectId arrayId);
+ static bool OutputArray(JDWP::ObjectId arrayId, int firstIndex, int count, JDWP::ExpandBuf* pReply);
+ static bool SetArrayElements(JDWP::ObjectId arrayId, int firstIndex, int count, const uint8_t* buf);
+
+ static JDWP::ObjectId CreateString(const char* str);
+ static JDWP::ObjectId CreateObject(JDWP::RefTypeId classId);
+ static JDWP::ObjectId CreateArrayObject(JDWP::RefTypeId arrayTypeId, uint32_t length);
+
+ static bool MatchType(JDWP::RefTypeId instClassId, JDWP::RefTypeId classId);
+
+ /*
+ * Method and Field
+ */
+ static const char* GetMethodName(JDWP::RefTypeId refTypeId, JDWP::MethodId id);
+ static void OutputAllFields(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply);
+ static void OutputAllMethods(JDWP::RefTypeId refTypeId, bool withGeneric, JDWP::ExpandBuf* pReply);
+ static void OutputAllInterfaces(JDWP::RefTypeId refTypeId, JDWP::ExpandBuf* pReply);
+ static void OutputLineTable(JDWP::RefTypeId refTypeId, JDWP::MethodId methodId, JDWP::ExpandBuf* pReply);
+ static void OutputVariableTable(JDWP::RefTypeId refTypeId, JDWP::MethodId id, bool withGeneric, JDWP::ExpandBuf* pReply);
+
+ static uint8_t GetFieldBasicTag(JDWP::ObjectId objId, JDWP::FieldId fieldId);
+ static uint8_t GetStaticFieldBasicTag(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId);
+ static void GetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply);
+ static void SetFieldValue(JDWP::ObjectId objectId, JDWP::FieldId fieldId, uint64_t value, int width);
+ static void GetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, JDWP::ExpandBuf* pReply);
+ static void SetStaticFieldValue(JDWP::RefTypeId refTypeId, JDWP::FieldId fieldId, uint64_t rawValue, int width);
+
+ static char* StringToUtf8(JDWP::ObjectId strId);
+
+ /*
+ * Thread, ThreadGroup, Frame
+ */
+ static char* GetThreadName(JDWP::ObjectId threadId);
+ static JDWP::ObjectId GetThreadGroup(JDWP::ObjectId threadId);
+ static char* GetThreadGroupName(JDWP::ObjectId threadGroupId);
+ static JDWP::ObjectId GetThreadGroupParent(JDWP::ObjectId threadGroupId);
+ static JDWP::ObjectId GetSystemThreadGroupId();
+ static JDWP::ObjectId GetMainThreadGroupId();
+
+ static bool GetThreadStatus(JDWP::ObjectId threadId, uint32_t* threadStatus, uint32_t* suspendStatus);
+ static uint32_t GetThreadSuspendCount(JDWP::ObjectId threadId);
+ static bool ThreadExists(JDWP::ObjectId threadId);
+ static bool IsSuspended(JDWP::ObjectId threadId);
+ //static void WaitForSuspend(JDWP::ObjectId threadId);
+ static void GetThreadGroupThreads(JDWP::ObjectId threadGroupId, JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount);
+ static void GetAllThreads(JDWP::ObjectId** ppThreadIds, uint32_t* pThreadCount);
+ static int GetThreadFrameCount(JDWP::ObjectId threadId);
+ 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 ResumeVM();
+ static void SuspendThread(JDWP::ObjectId threadId);
+ static void ResumeThread(JDWP::ObjectId threadId);
+ static void SuspendSelf();
+
+ static bool GetThisObject(JDWP::ObjectId threadId, JDWP::FrameId frameId, JDWP::ObjectId* pThisId);
+ static void GetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint8_t* buf, int expectedLen);
+ static void SetLocalValue(JDWP::ObjectId threadId, JDWP::FrameId frameId, int slot, uint8_t tag, uint64_t value, int width);
+
+ /*
+ * Debugger notification
+ */
+ enum {
+ kBreakPoint = 0x01,
+ kSingleStep = 0x02,
+ kMethodEntry = 0x04,
+ kMethodExit = 0x08,
+ };
+ static void PostLocationEvent(const Method* method, int pcOffset, Object* thisPtr, int eventFlags);
+ static void PostException(void* throwFp, int throwRelPc, void* catchFp, int catchRelPc, Object* exception);
+ static void PostThreadStart(Thread* t);
+ static void PostThreadDeath(Thread* t);
+ static void PostClassPrepare(Class* c);
+
+ static bool WatchLocation(const JDWP::JdwpLocation* pLoc);
+ static void UnwatchLocation(const JDWP::JdwpLocation* pLoc);
+ static bool ConfigureStep(JDWP::ObjectId threadId, JDWP::JdwpStepSize size, JDWP::JdwpStepDepth depth);
+ static void UnconfigureStep(JDWP::ObjectId threadId);
+
+ static JDWP::JdwpError InvokeMethod(JDWP::ObjectId threadId, JDWP::ObjectId objectId, JDWP::RefTypeId classId, JDWP::MethodId methodId, uint32_t numArgs, uint64_t* argArray, uint32_t options, uint8_t* pResultTag, uint64_t* pResultValue, JDWP::ObjectId* pExceptObj);
+ static void ExecuteMethod(DebugInvokeReq* pReq);
+
+ /* perform "late registration" of an object ID */
+ static void RegisterObjectId(JDWP::ObjectId id);
+
+ /*
+ * DDM support.
+ */
+ static bool DdmHandlePacket(const uint8_t* buf, int dataLen, uint8_t** pReplyBuf, int* pReplyLen);
+ static void DdmConnected();
+ static void DdmDisconnected();
+ static void DdmSendChunk(int type, size_t len, const uint8_t* buf);
+ static void DdmSendChunkV(int type, const struct iovec* iov, int iovcnt);
+};
+
+#define CHUNK_TYPE(_name) \
+ ((_name)[0] << 24 | (_name)[1] << 16 | (_name)[2] << 8 | (_name)[3])
+
+} // namespace art
+
+#endif // ART_DEBUGGER_H_
diff --git a/src/jdwp/README.txt b/src/jdwp/README.txt
new file mode 100644
index 0000000..da25fb1
--- /dev/null
+++ b/src/jdwp/README.txt
@@ -0,0 +1,11 @@
+Java Debug Wire Protocol support
+
+This is a reasonably complete implementation, but only messages that are
+actually generated by debuggers have been implemented. The reasoning
+behind this is that it's better to leave a call unimplemented than have
+something that appears implemented but has never been tested.
+
+An attempt has been made to keep the JDWP implementation distinct from the
+runtime, so that the code might be useful in other projects. Once you get
+multiple simultaneous events and debugger requests with thread suspension
+bouncing around, though, it's difficult to keep things "generic".
diff --git a/src/jdwp/jdwp.h b/src/jdwp/jdwp.h
new file mode 100644
index 0000000..1c205bc
--- /dev/null
+++ b/src/jdwp/jdwp.h
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_JDWP_JDWP_H_
+#define ART_JDWP_JDWP_H_
+
+#include "jdwp/jdwp_bits.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+
+#include <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+struct iovec;
+
+namespace art {
+
+namespace JDWP {
+
+struct JdwpState; /* opaque */
+
+/*
+ * Fundamental types.
+ *
+ * ObjectId and RefTypeId must be the same size.
+ */
+typedef uint32_t FieldId; /* static or instance field */
+typedef uint32_t MethodId; /* any kind of method, including constructors */
+typedef uint64_t ObjectId; /* any object (threadID, stringID, arrayID, etc) */
+typedef uint64_t RefTypeId; /* like ObjectID, but unique for Class objects */
+typedef uint64_t FrameId; /* short-lived stack frame ID */
+
+/*
+ * Match these with the type sizes. This way we don't have to pass
+ * a value and a length.
+ */
+static inline FieldId ReadFieldId(const uint8_t** pBuf) { return read4BE(pBuf); }
+static inline MethodId ReadMethodId(const uint8_t** pBuf) { return read4BE(pBuf); }
+static inline ObjectId ReadObjectId(const uint8_t** pBuf) { return read8BE(pBuf); }
+static inline RefTypeId ReadRefTypeId(const uint8_t** pBuf) { return read8BE(pBuf); }
+static inline FrameId ReadFrameId(const uint8_t** pBuf) { return read8BE(pBuf); }
+static inline void SetFieldId(uint8_t* buf, FieldId val) { return set4BE(buf, val); }
+static inline void SetMethodId(uint8_t* buf, MethodId val) { return set4BE(buf, val); }
+static inline void SetObjectId(uint8_t* buf, ObjectId val) { return set8BE(buf, val); }
+static inline void SetRefTypeId(uint8_t* buf, RefTypeId val) { return set8BE(buf, val); }
+static inline void SetFrameId(uint8_t* buf, FrameId val) { return set8BE(buf, val); }
+static inline void expandBufAddFieldId(ExpandBuf* pReply, FieldId id) { expandBufAdd4BE(pReply, id); }
+static inline void expandBufAddMethodId(ExpandBuf* pReply, MethodId id) { expandBufAdd4BE(pReply, id); }
+static inline void expandBufAddObjectId(ExpandBuf* pReply, ObjectId id) { expandBufAdd8BE(pReply, id); }
+static inline void expandBufAddRefTypeId(ExpandBuf* pReply, RefTypeId id) { expandBufAdd8BE(pReply, id); }
+static inline void expandBufAddFrameId(ExpandBuf* pReply, FrameId id) { expandBufAdd8BE(pReply, id); }
+
+
+/*
+ * Holds a JDWP "location".
+ */
+struct JdwpLocation {
+ uint8_t typeTag; /* class or interface? */
+ RefTypeId classId; /* method->clazz */
+ MethodId methodId; /* method in which "idx" resides */
+ uint64_t idx; /* relative index into code block */
+};
+
+/*
+ * How we talk to the debugger.
+ */
+enum JdwpTransportType {
+ kJdwpTransportUnknown = 0,
+ kJdwpTransportSocket, /* transport=dt_socket */
+ kJdwpTransportAndroidAdb, /* transport=dt_android_adb */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpTransportType& rhs);
+
+/*
+ * Holds collection of JDWP initialization parameters.
+ */
+struct JdwpStartupParams {
+ JdwpTransportType transport;
+ bool server;
+ bool suspend;
+ char host[64];
+ short port;
+ /* more will be here someday */
+};
+
+/*
+ * Perform one-time initialization.
+ *
+ * Among other things, this binds to a port to listen for a connection from
+ * the debugger.
+ *
+ * Returns a newly-allocated JdwpState struct on success, or NULL on failure.
+ */
+JdwpState* JdwpStartup(const JdwpStartupParams* params);
+
+/*
+ * Shut everything down.
+ */
+void JdwpShutdown(JdwpState* state);
+
+/*
+ * Returns "true" if a debugger or DDM is connected.
+ */
+bool JdwpIsActive(JdwpState* state);
+
+/*
+ * Return the debugger thread's handle, or 0 if the debugger thread isn't
+ * running.
+ */
+pthread_t GetDebugThread(JdwpState* state);
+
+/*
+ * Get time, in milliseconds, since the last debugger activity.
+ */
+int64_t LastDebuggerActivity(JdwpState* state);
+
+/*
+ * 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(JdwpState* state);
+void SetWaitForEventThread(JdwpState* state, ObjectId threadId);
+void ClearWaitForEventThread(JdwpState* state);
+
+/*
+ * These notify the debug code that something interesting has happened. This
+ * could be a thread starting or ending, an exception, or an opportunity
+ * for a breakpoint. These calls do not mean that an event the debugger
+ * is interested has happened, just that something has happened that the
+ * debugger *might* be interested in.
+ *
+ * The item of interest may trigger multiple events, some or all of which
+ * are grouped together in a single response.
+ *
+ * The event may cause the current thread or all threads (except the
+ * JDWP support thread) to be suspended.
+ */
+
+/*
+ * The VM has finished initializing. Only called when the debugger is
+ * connected at the time initialization completes.
+ */
+bool PostVMStart(JdwpState* state, bool suspend);
+
+/*
+ * A location of interest has been reached. This is used for breakpoints,
+ * single-stepping, and method entry/exit. (JDWP requires that these four
+ * events are grouped together in a single response.)
+ *
+ * In some cases "*pLoc" will just have a method and class name, e.g. when
+ * issuing a MethodEntry on a native method.
+ *
+ * "eventFlags" indicates the types of events that have occurred.
+ */
+bool PostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags);
+
+/*
+ * An exception has been thrown.
+ *
+ * Pass in a zeroed-out "*pCatchLoc" if the exception wasn't caught.
+ */
+bool PostException(JdwpState* state, const JdwpLocation* pThrowLoc,
+ ObjectId excepId, RefTypeId excepClassId, const JdwpLocation* pCatchLoc,
+ ObjectId thisPtr);
+
+/*
+ * A thread has started or stopped.
+ */
+bool PostThreadChange(JdwpState* state, ObjectId threadId, bool start);
+
+/*
+ * Class has been prepared.
+ */
+bool PostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId,
+ const char* signature, int status);
+
+/*
+ * The VM is about to stop.
+ */
+bool PostVMDeath(JdwpState* state);
+
+/*
+ * Send up a chunk of DDM data.
+ */
+void DdmSendChunkV(JdwpState* state, int type, const iovec* iov, int iovcnt);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWP_H_
diff --git a/src/jdwp/jdwp_adb.cc b/src/jdwp/jdwp_adb.cc
new file mode 100644
index 0000000..3a89f34
--- /dev/null
+++ b/src/jdwp/jdwp_adb.cc
@@ -0,0 +1,723 @@
+/*
+ * 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 "logging.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_handler.h"
+#include "stringprintf.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#ifdef HAVE_ANDROID_OS
+#include "cutils/sockets.h"
+#endif
+
+/*
+ * The JDWP <-> ADB transport protocol is explained in detail
+ * in system/core/adb/jdwp_service.c. Here's a summary.
+ *
+ * 1/ when the JDWP thread starts, it tries to connect to a Unix
+ * domain stream socket (@jdwp-control) that is opened by the
+ * ADB daemon.
+ *
+ * 2/ it then sends the current process PID as a string of 4 hexadecimal
+ * chars (no terminating zero)
+ *
+ * 3/ then, it uses recvmsg to receive file descriptors from the
+ * daemon. each incoming file descriptor is a pass-through to
+ * a given JDWP debugger, that can be used to read the usual
+ * JDWP-handshake, etc...
+ */
+
+#define kInputBufferSize 8192
+
+#define kMagicHandshake "JDWP-Handshake"
+#define kMagicHandshakeLen (sizeof(kMagicHandshake)-1)
+
+#define kJdwpControlName "\0jdwp-control"
+#define kJdwpControlNameLen (sizeof(kJdwpControlName)-1)
+
+namespace art {
+
+namespace JDWP {
+
+struct JdwpNetState : public JdwpNetStateBase {
+ int controlSock;
+ bool awaitingHandshake;
+ bool shuttingDown;
+ int wakeFds[2];
+
+ int inputCount;
+ unsigned char inputBuffer[kInputBufferSize];
+
+ socklen_t controlAddrLen;
+ union {
+ struct sockaddr_un controlAddrUn;
+ struct sockaddr controlAddrPlain;
+ } controlAddr;
+
+ JdwpNetState() {
+ controlSock = -1;
+ awaitingHandshake = false;
+ shuttingDown = false;
+ wakeFds[0] = -1;
+ wakeFds[1] = -1;
+
+ inputCount = 0;
+
+ controlAddr.controlAddrUn.sun_family = AF_UNIX;
+ controlAddrLen = sizeof(controlAddr.controlAddrUn.sun_family) + kJdwpControlNameLen;
+ memcpy(controlAddr.controlAddrUn.sun_path, kJdwpControlName, kJdwpControlNameLen);
+ }
+};
+
+static void adbStateFree(JdwpNetState* netState) {
+ if (netState == NULL) {
+ return;
+ }
+
+ if (netState->clientSock >= 0) {
+ shutdown(netState->clientSock, SHUT_RDWR);
+ close(netState->clientSock);
+ }
+ if (netState->controlSock >= 0) {
+ shutdown(netState->controlSock, SHUT_RDWR);
+ close(netState->controlSock);
+ }
+ if (netState->wakeFds[0] >= 0) {
+ close(netState->wakeFds[0]);
+ netState->wakeFds[0] = -1;
+ }
+ if (netState->wakeFds[1] >= 0) {
+ close(netState->wakeFds[1]);
+ netState->wakeFds[1] = -1;
+ }
+
+ delete netState;
+}
+
+/*
+ * Do initial prep work, e.g. binding to ports and opening files. This
+ * runs in the main thread, before the JDWP thread starts, so it shouldn't
+ * do anything that might block forever.
+ */
+static bool startup(JdwpState* state, const JdwpStartupParams* pParams) {
+ JdwpNetState* netState;
+
+ LOG(VERBOSE) << "ADB transport startup";
+
+ state->netState = netState = new JdwpNetState;
+ if (netState == NULL) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Receive a file descriptor from ADB. The fd can be used to communicate
+ * directly with a debugger or DDMS.
+ *
+ * Returns the file descriptor on success. On failure, returns -1 and
+ * closes netState->controlSock.
+ */
+static int receiveClientFd(JdwpNetState* netState) {
+ struct msghdr msg;
+ struct cmsghdr* cmsg;
+ iovec iov;
+ char dummy = '!';
+ union {
+ struct cmsghdr cm;
+ char buffer[CMSG_SPACE(sizeof(int))];
+ } cm_un;
+ int ret;
+
+ iov.iov_base = &dummy;
+ iov.iov_len = 1;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_flags = 0;
+ msg.msg_control = cm_un.buffer;
+ msg.msg_controllen = sizeof(cm_un.buffer);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = msg.msg_controllen;
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ ((int*)(void*)CMSG_DATA(cmsg))[0] = -1;
+
+ do {
+ ret = recvmsg(netState->controlSock, &msg, 0);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret <= 0) {
+ if (ret < 0) {
+ PLOG(WARNING) << "receiving file descriptor from ADB failed (socket " << netState->controlSock << ")";
+ }
+ close(netState->controlSock);
+ netState->controlSock = -1;
+ return -1;
+ }
+
+ return ((int*)(void*)CMSG_DATA(cmsg))[0];
+}
+
+/*
+ * Block forever, waiting for a debugger to connect to us. Called from the
+ * JDWP thread.
+ *
+ * This needs to un-block and return "false" if the VM is shutting down. It
+ * should return "true" when it successfully accepts a connection.
+ */
+static bool acceptConnection(JdwpState* state) {
+ JdwpNetState* netState = state->netState;
+ int retryCount = 0;
+
+ /* first, ensure that we get a connection to the ADB daemon */
+
+retry:
+ if (netState->shuttingDown) {
+ return false;
+ }
+
+ if (netState->controlSock < 0) {
+ int sleep_ms = 500;
+ const int sleep_max_ms = 2*1000;
+ char buff[5];
+
+ netState->controlSock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (netState->controlSock < 0) {
+ PLOG(ERROR) << "Could not create ADB control socket";
+ return false;
+ }
+
+ if (pipe(netState->wakeFds) < 0) {
+ PLOG(ERROR) << "pipe failed";
+ return false;
+ }
+
+ snprintf(buff, sizeof(buff), "%04x", getpid());
+ buff[4] = 0;
+
+ for (;;) {
+ /*
+ * If adbd isn't running, because USB debugging was disabled or
+ * perhaps the system is restarting it for "adb root", the
+ * connect() will fail. We loop here forever waiting for it
+ * to come back.
+ *
+ * Waking up and polling every couple of seconds is generally a
+ * bad thing to do, but we only do this if the application is
+ * debuggable *and* adbd isn't running. Still, for the sake
+ * of battery life, we should consider timing out and giving
+ * up after a few minutes in case somebody ships an app with
+ * the debuggable flag set.
+ */
+ int ret = connect(netState->controlSock, &netState->controlAddr.controlAddrPlain, netState->controlAddrLen);
+ if (!ret) {
+#ifdef HAVE_ANDROID_OS
+ if (!socket_peer_is_trusted(netState->controlSock)) {
+ if (shutdown(netState->controlSock, SHUT_RDWR)) {
+ PLOG(ERROR) << "trouble shutting down socket";
+ }
+ return false;
+ }
+#endif
+
+ /* now try to send our pid to the ADB daemon */
+ do {
+ ret = send( netState->controlSock, buff, 4, 0 );
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret >= 0) {
+ LOG(VERBOSE) << StringPrintf("PID sent as '%.*s' to ADB", 4, buff);
+ break;
+ }
+
+ PLOG(ERROR) << "Weird, can't send JDWP process pid to ADB";
+ return false;
+ }
+ PLOG(VERBOSE) << "Can't connect to ADB control socket";
+
+ usleep( sleep_ms*1000 );
+
+ sleep_ms += (sleep_ms >> 1);
+ if (sleep_ms > sleep_max_ms) {
+ sleep_ms = sleep_max_ms;
+ }
+ if (netState->shuttingDown) {
+ return false;
+ }
+ }
+ }
+
+ LOG(VERBOSE) << "trying to receive file descriptor from ADB";
+ /* now we can receive a client file descriptor */
+ netState->clientSock = receiveClientFd(netState);
+ if (netState->shuttingDown) {
+ return false; // suppress logs and additional activity
+ }
+ if (netState->clientSock < 0) {
+ if (++retryCount > 5) {
+ LOG(ERROR) << "adb connection max retries exceeded";
+ return false;
+ }
+ goto retry;
+ } else {
+ LOG(VERBOSE) << "received file descriptor " << netState->clientSock << " from ADB";
+ netState->awaitingHandshake = 1;
+ netState->inputCount = 0;
+ return true;
+ }
+}
+
+/*
+ * Connect out to a debugger (for server=n). Not required.
+ */
+static bool establishConnection(JdwpState* state) {
+ return false;
+}
+
+/*
+ * Close a connection from a debugger (which may have already dropped us).
+ * Only called from the JDWP thread.
+ */
+static void closeConnection(JdwpState* state) {
+ CHECK(state != NULL && state->netState != NULL);
+
+ JdwpNetState* netState = state->netState;
+ if (netState->clientSock < 0) {
+ return;
+ }
+
+ LOG(VERBOSE) << "+++ closed JDWP <-> ADB connection";
+
+ close(netState->clientSock);
+ netState->clientSock = -1;
+}
+
+/*
+ * Close all network stuff, including the socket we use to listen for
+ * new connections.
+ *
+ * May be called from a non-JDWP thread, e.g. when the VM is shutting down.
+ */
+static void adbStateShutdown(JdwpNetState* netState) {
+ int controlSock;
+ int clientSock;
+
+ if (netState == NULL) {
+ return;
+ }
+
+ netState->shuttingDown = true;
+
+ clientSock = netState->clientSock;
+ if (clientSock >= 0) {
+ shutdown(clientSock, SHUT_RDWR);
+ netState->clientSock = -1;
+ }
+
+ controlSock = netState->controlSock;
+ if (controlSock >= 0) {
+ shutdown(controlSock, SHUT_RDWR);
+ netState->controlSock = -1;
+ }
+
+ if (netState->wakeFds[1] >= 0) {
+ LOG(VERBOSE) << "+++ writing to wakePipe";
+ write(netState->wakeFds[1], "", 1);
+ }
+}
+
+static void netShutdown(JdwpState* state) {
+ adbStateShutdown(state->netState);
+}
+
+/*
+ * Free up anything we put in state->netState. This is called after
+ * "netShutdown", after the JDWP thread has stopped.
+ */
+static void netFree(JdwpState* state) {
+ JdwpNetState* netState = state->netState;
+ adbStateFree(netState);
+}
+
+/*
+ * Is a debugger connected to us?
+ */
+static bool isConnected(JdwpState* state) {
+ return (state->netState != NULL && state->netState->clientSock >= 0);
+}
+
+/*
+ * Are we still waiting for the JDWP handshake?
+ */
+static bool awaitingHandshake(JdwpState* state) {
+ return state->netState->awaitingHandshake;
+}
+
+/*
+ * Figure out if we have a full packet in the buffer.
+ */
+static bool haveFullPacket(JdwpNetState* netState) {
+ if (netState->awaitingHandshake) {
+ return (netState->inputCount >= (int) kMagicHandshakeLen);
+ }
+ if (netState->inputCount < 4) {
+ return false;
+ }
+ long length = get4BE(netState->inputBuffer);
+ return (netState->inputCount >= length);
+}
+
+/*
+ * Consume bytes from the buffer.
+ *
+ * This would be more efficient with a circular buffer. However, we're
+ * usually only going to find one packet, which is trivial to handle.
+ */
+static void consumeBytes(JdwpNetState* netState, int count) {
+ CHECK_GT(count, 0);
+ CHECK_LE(count, netState->inputCount);
+
+ if (count == netState->inputCount) {
+ netState->inputCount = 0;
+ return;
+ }
+
+ memmove(netState->inputBuffer, netState->inputBuffer + count, netState->inputCount - count);
+ netState->inputCount -= count;
+}
+
+/*
+ * Handle a packet. Returns "false" if we encounter a connection-fatal error.
+ */
+static bool handlePacket(JdwpState* state) {
+ JdwpNetState* netState = state->netState;
+ const unsigned char* buf = netState->inputBuffer;
+ JdwpReqHeader hdr;
+ uint32_t length, id;
+ uint8_t flags, cmdSet, cmd;
+ uint16_t error;
+ bool reply;
+ int dataLen;
+
+ cmd = cmdSet = 0; // shut up gcc
+
+ length = read4BE(&buf);
+ id = read4BE(&buf);
+ flags = read1(&buf);
+ if ((flags & kJDWPFlagReply) != 0) {
+ reply = true;
+ error = read2BE(&buf);
+ } else {
+ reply = false;
+ cmdSet = read1(&buf);
+ cmd = read1(&buf);
+ }
+
+ CHECK_LE((int) length, netState->inputCount);
+ dataLen = length - (buf - netState->inputBuffer);
+
+ if (!reply) {
+ ExpandBuf* pReply = expandBufAlloc();
+
+ hdr.length = length;
+ hdr.id = id;
+ hdr.cmdSet = cmdSet;
+ hdr.cmd = cmd;
+ ProcessRequest(state, &hdr, buf, dataLen, pReply);
+ if (expandBufGetLength(pReply) > 0) {
+ ssize_t cc = netState->writePacket(pReply);
+
+ if (cc != (ssize_t) expandBufGetLength(pReply)) {
+ PLOG(ERROR) << "Failed sending reply to debugger";
+ expandBufFree(pReply);
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "No reply created for set=" << cmdSet << " cmd=" << cmd;
+ }
+ expandBufFree(pReply);
+ } else {
+ LOG(FATAL) << "reply?!";
+ }
+
+ LOG(VERBOSE) << "----------";
+
+ consumeBytes(netState, length);
+ return true;
+}
+
+/*
+ * Process incoming data. If no data is available, this will block until
+ * some arrives.
+ *
+ * If we get a full packet, handle it.
+ *
+ * To take some of the mystery out of life, we want to reject incoming
+ * connections if we already have a debugger attached. If we don't, the
+ * debugger will just mysteriously hang until it times out. We could just
+ * close the listen socket, but there's a good chance we won't be able to
+ * bind to the same port again, which would confuse utilities.
+ *
+ * Returns "false" on error (indicating that the connection has been severed),
+ * "true" if things are still okay.
+ */
+static bool processIncoming(JdwpState* state) {
+ JdwpNetState* netState = state->netState;
+ int readCount;
+
+ CHECK_GE(netState->clientSock, 0);
+
+ if (!haveFullPacket(netState)) {
+ /* read some more, looping until we have data */
+ errno = 0;
+ while (1) {
+ int selCount;
+ fd_set readfds;
+ int maxfd = -1;
+ int fd;
+
+ FD_ZERO(&readfds);
+
+ /* configure fds; note these may get zapped by another thread */
+ fd = netState->controlSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ }
+ fd = netState->clientSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ }
+ fd = netState->wakeFds[0];
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ } else {
+ LOG(INFO) << "NOTE: entering select w/o wakepipe";
+ }
+
+ if (maxfd < 0) {
+ LOG(VERBOSE) << "+++ all fds are closed";
+ return false;
+ }
+
+ /*
+ * Select blocks until it sees activity on the file descriptors.
+ * Closing the local file descriptor does not count as activity,
+ * so we can't rely on that to wake us up (it works for read()
+ * and accept(), but not select()).
+ *
+ * We can do one of three things: (1) send a signal and catch
+ * EINTR, (2) open an additional fd ("wakePipe") and write to
+ * it when it's time to exit, or (3) time out periodically and
+ * re-issue the select. We're currently using #2, as it's more
+ * reliable than #1 and generally better than #3. Wastes two fds.
+ */
+ selCount = select(maxfd+1, &readfds, NULL, NULL, NULL);
+ if (selCount < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ PLOG(ERROR) << "select failed";
+ goto fail;
+ }
+
+ if (netState->wakeFds[0] >= 0 && FD_ISSET(netState->wakeFds[0], &readfds)) {
+ LOG(DEBUG) << "Got wake-up signal, bailing out of select";
+ goto fail;
+ }
+ if (netState->controlSock >= 0 && FD_ISSET(netState->controlSock, &readfds)) {
+ int sock = receiveClientFd(netState);
+ if (sock >= 0) {
+ LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
+ close(sock);
+ } else {
+ CHECK_LT(netState->controlSock, 0);
+ /*
+ * Remote side most likely went away, so our next read
+ * on netState->clientSock will fail and throw us out
+ * of the loop.
+ */
+ }
+ }
+ if (netState->clientSock >= 0 && FD_ISSET(netState->clientSock, &readfds)) {
+ readCount = read(netState->clientSock, netState->inputBuffer + netState->inputCount, sizeof(netState->inputBuffer) - netState->inputCount);
+ if (readCount < 0) {
+ /* read failed */
+ if (errno != EINTR) {
+ goto fail;
+ }
+ LOG(DEBUG) << "+++ EINTR hit";
+ return true;
+ } else if (readCount == 0) {
+ /* EOF hit -- far end went away */
+ LOG(VERBOSE) << "+++ peer disconnected";
+ goto fail;
+ } else {
+ break;
+ }
+ }
+ }
+
+ netState->inputCount += readCount;
+ if (!haveFullPacket(netState)) {
+ return true; /* still not there yet */
+ }
+ }
+
+ /*
+ * Special-case the initial handshake. For some bizarre reason we're
+ * expected to emulate bad tty settings by echoing the request back
+ * exactly as it was sent. Note the handshake is always initiated by
+ * the debugger, no matter who connects to whom.
+ *
+ * Other than this one case, the protocol [claims to be] stateless.
+ */
+ if (netState->awaitingHandshake) {
+ int cc;
+
+ if (memcmp(netState->inputBuffer, kMagicHandshake, kMagicHandshakeLen) != 0) {
+ LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", netState->inputBuffer);
+ goto fail;
+ }
+
+ errno = 0;
+ cc = write(netState->clientSock, netState->inputBuffer, kMagicHandshakeLen);
+ if (cc != kMagicHandshakeLen) {
+ PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
+ goto fail;
+ }
+
+ consumeBytes(netState, kMagicHandshakeLen);
+ netState->awaitingHandshake = false;
+ LOG(VERBOSE) << "+++ handshake complete";
+ return true;
+ }
+
+ /*
+ * Handle this packet.
+ */
+ return handlePacket(state);
+
+fail:
+ closeConnection(state);
+ return false;
+}
+
+/*
+ * Send a request.
+ *
+ * The entire packet must be sent with a single write() call to avoid
+ * threading issues.
+ *
+ * Returns "true" if it was sent successfully.
+ */
+static bool sendRequest(JdwpState* state, ExpandBuf* pReq) {
+ JdwpNetState* netState = state->netState;
+
+ if (netState->clientSock < 0) {
+ /* can happen with some DDMS events */
+ LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+ return false;
+ }
+
+ errno = 0;
+
+ ssize_t cc = netState->writePacket(pReq);
+
+ if (cc != (ssize_t) expandBufGetLength(pReq)) {
+ PLOG(ERROR) << "Failed sending req to debugger (" << cc << " of " << expandBufGetLength(pReq) << ")";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Send a request that was split into multiple buffers.
+ *
+ * The entire packet must be sent with a single writev() call to avoid
+ * threading issues.
+ *
+ * Returns "true" if it was sent successfully.
+ */
+static bool sendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+ JdwpNetState* netState = state->netState;
+
+ if (netState->clientSock < 0) {
+ /* can happen with some DDMS events */
+ LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+ return false;
+ }
+
+ size_t expected = 0;
+ int i;
+ for (i = 0; i < iovcnt; i++) {
+ expected += iov[i].iov_len;
+ }
+
+ ssize_t actual = netState->writeBufferedPacket(iov, iovcnt);
+ if ((size_t)actual != expected) {
+ PLOG(ERROR) << "Failed sending b-req to debugger (" << actual << " of " << expected << ")";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Our functions.
+ */
+static const JdwpTransport adbTransport = {
+ startup,
+ acceptConnection,
+ establishConnection,
+ closeConnection,
+ netShutdown,
+ netFree,
+ isConnected,
+ awaitingHandshake,
+ processIncoming,
+ sendRequest,
+ sendBufferedRequest
+};
+
+/*
+ * Return our set.
+ */
+const JdwpTransport* AndroidAdbTransport() {
+ return &adbTransport;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/jdwp/jdwp_bits.h b/src/jdwp/jdwp_bits.h
new file mode 100644
index 0000000..b0041f1
--- /dev/null
+++ b/src/jdwp/jdwp_bits.h
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_JDWP_BITS_H_
+#define ART_JDWP_BITS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace art {
+
+namespace JDWP {
+
+static inline uint32_t get4BE(unsigned char const* pSrc) {
+ return (pSrc[0] << 24) | (pSrc[1] << 16) | (pSrc[2] << 8) | pSrc[3];
+}
+
+static inline uint8_t read1(unsigned const char** ppSrc) {
+ return *(*ppSrc)++;
+}
+
+static inline uint16_t read2BE(unsigned char const** ppSrc) {
+ const unsigned char* pSrc = *ppSrc;
+ *ppSrc = pSrc + 2;
+ return pSrc[0] << 8 | pSrc[1];
+}
+
+static inline uint32_t read4BE(unsigned char const** ppSrc) {
+ const unsigned char* pSrc = *ppSrc;
+ uint32_t result = pSrc[0] << 24;
+ result |= pSrc[1] << 16;
+ result |= pSrc[2] << 8;
+ result |= pSrc[3];
+ *ppSrc = pSrc + 4;
+ return result;
+}
+
+static inline uint64_t read8BE(unsigned char const** ppSrc) {
+ const unsigned char* pSrc = *ppSrc;
+ uint32_t high = pSrc[0];
+ high = (high << 8) | pSrc[1];
+ high = (high << 8) | pSrc[2];
+ high = (high << 8) | pSrc[3];
+ uint32_t low = pSrc[4];
+ low = (low << 8) | pSrc[5];
+ low = (low << 8) | pSrc[6];
+ low = (low << 8) | pSrc[7];
+ *ppSrc = pSrc + 8;
+ return ((uint64_t) high << 32) | (uint64_t) low;
+}
+
+/*
+ * Read a UTF-8 string into newly-allocated storage, and null-terminate it.
+ *
+ * Returns the string and its length. (The latter is probably unnecessary
+ * for the way we're using UTF8.)
+ */
+static inline char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength) {
+ uint32_t length = read4BE(ppSrc);
+ char* buf = (char*) malloc(length+1);
+ memcpy(buf, *ppSrc, length);
+ buf[length] = '\0';
+ (*ppSrc) += length;
+ *pLength = length;
+ return buf;
+}
+
+static inline void set1(uint8_t* buf, uint8_t val) {
+ *buf = (uint8_t)(val);
+}
+
+static inline void set2BE(uint8_t* buf, uint16_t val) {
+ *buf++ = (uint8_t)(val >> 8);
+ *buf = (uint8_t)(val);
+}
+
+static inline void set4BE(uint8_t* buf, uint32_t val) {
+ *buf++ = (uint8_t)(val >> 24);
+ *buf++ = (uint8_t)(val >> 16);
+ *buf++ = (uint8_t)(val >> 8);
+ *buf = (uint8_t)(val);
+}
+
+static inline void set8BE(uint8_t* buf, uint64_t val) {
+ *buf++ = (uint8_t)(val >> 56);
+ *buf++ = (uint8_t)(val >> 48);
+ *buf++ = (uint8_t)(val >> 40);
+ *buf++ = (uint8_t)(val >> 32);
+ *buf++ = (uint8_t)(val >> 24);
+ *buf++ = (uint8_t)(val >> 16);
+ *buf++ = (uint8_t)(val >> 8);
+ *buf = (uint8_t)(val);
+}
+
+/*
+ * Stuff a UTF-8 string into the buffer.
+ */
+static inline void setUtf8String(uint8_t* buf, const uint8_t* str) {
+ uint32_t strLen = strlen((const char*)str);
+ set4BE(buf, strLen);
+ memcpy(buf + sizeof(uint32_t), str, strLen);
+}
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_BITS_H_
diff --git a/src/jdwp/jdwp_constants.cc b/src/jdwp/jdwp_constants.cc
new file mode 100644
index 0000000..7110eed
--- /dev/null
+++ b/src/jdwp/jdwp_constants.cc
@@ -0,0 +1,270 @@
+/*
+ * 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.
+ */
+/*
+ * String constants to go along with enumerated values. (Pity we don't
+ * have enumerated constant reflection in C.) These are only needed for
+ * making the output human-readable.
+ */
+#include "jdwp/jdwp_constants.h"
+
+#include <iostream>
+
+namespace art {
+
+namespace JDWP {
+
+const char* ErrorStr(JdwpError error) {
+ switch (error) {
+ case ERR_NONE:
+ return "NONE";
+ case ERR_INVALID_THREAD:
+ return "INVALID_THREAD";
+ case ERR_INVALID_THREAD_GROUP:
+ return "INVALID_THREAD_GROUP";
+ case ERR_INVALID_PRIORITY:
+ return "INVALID_PRIORITY";
+ case ERR_THREAD_NOT_SUSPENDED:
+ return "THREAD_NOT_SUSPENDED";
+ case ERR_THREAD_SUSPENDED:
+ return "THREAD_SUSPENDED";
+ case ERR_INVALID_OBJECT:
+ return "INVALID_OBJEC";
+ case ERR_INVALID_CLASS:
+ return "INVALID_CLASS";
+ case ERR_CLASS_NOT_PREPARED:
+ return "CLASS_NOT_PREPARED";
+ case ERR_INVALID_METHODID:
+ return "INVALID_METHODID";
+ case ERR_INVALID_LOCATION:
+ return "INVALID_LOCATION";
+ case ERR_INVALID_FIELDID:
+ return "INVALID_FIELDID";
+ case ERR_INVALID_FRAMEID:
+ return "INVALID_FRAMEID";
+ case ERR_NO_MORE_FRAMES:
+ return "NO_MORE_FRAMES";
+ case ERR_OPAQUE_FRAME:
+ return "OPAQUE_FRAME";
+ case ERR_NOT_CURRENT_FRAME:
+ return "NOT_CURRENT_FRAME";
+ case ERR_TYPE_MISMATCH:
+ return "TYPE_MISMATCH";
+ case ERR_INVALID_SLOT:
+ return "INVALID_SLOT";
+ case ERR_DUPLICATE:
+ return "DUPLICATE";
+ case ERR_NOT_FOUND:
+ return "NOT_FOUND";
+ case ERR_INVALID_MONITOR:
+ return "INVALID_MONITOR";
+ case ERR_NOT_MONITOR_OWNER:
+ return "NOT_MONITOR_OWNER";
+ case ERR_INTERRUPT:
+ return "INTERRUPT";
+ case ERR_INVALID_CLASS_FORMAT:
+ return "INVALID_CLASS_FORMAT";
+ case ERR_CIRCULAR_CLASS_DEFINITION:
+ return "CIRCULAR_CLASS_DEFINITION";
+ case ERR_FAILS_VERIFICATION:
+ return "FAILS_VERIFICATION";
+ case ERR_ADD_METHOD_NOT_IMPLEMENTED:
+ return "ADD_METHOD_NOT_IMPLEMENTED";
+ case ERR_SCHEMA_CHANGE_NOT_IMPLEMENTED:
+ return "SCHEMA_CHANGE_NOT_IMPLEMENTED";
+ case ERR_INVALID_TYPESTATE:
+ return "INVALID_TYPESTATE";
+ case ERR_HIERARCHY_CHANGE_NOT_IMPLEMENTED:
+ return "HIERARCHY_CHANGE_NOT_IMPLEMENTED";
+ case ERR_DELETE_METHOD_NOT_IMPLEMENTED:
+ return "DELETE_METHOD_NOT_IMPLEMENTED";
+ case ERR_UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case ERR_NAMES_DONT_MATCH:
+ return "NAMES_DONT_MATCH";
+ case ERR_CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED:
+ return "CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED";
+ case ERR_METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED:
+ return "METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED";
+ case ERR_NOT_IMPLEMENTED:
+ return "NOT_IMPLEMENTED";
+ case ERR_NULL_POINTER:
+ return "NULL_POINTER";
+ case ERR_ABSENT_INFORMATION:
+ return "ABSENT_INFORMATION";
+ case ERR_INVALID_EVENT_TYPE:
+ return "INVALID_EVENT_TYPE";
+ case ERR_ILLEGAL_ARGUMENT:
+ return "ILLEGAL_ARGUMENT";
+ case ERR_OUT_OF_MEMORY:
+ return "OUT_OF_MEMORY";
+ case ERR_ACCESS_DENIED:
+ return "ACCESS_DENIED";
+ case ERR_VM_DEAD:
+ return "VM_DEAD";
+ case ERR_INTERNAL:
+ return "INTERNAL";
+ case ERR_UNATTACHED_THREAD:
+ return "UNATTACHED_THREAD";
+ case ERR_INVALID_TAG:
+ return "INVALID_TAG";
+ case ERR_ALREADY_INVOKING:
+ return "ALREADY_INVOKING";
+ case ERR_INVALID_INDEX:
+ return "INVALID_INDEX";
+ case ERR_INVALID_LENGTH:
+ return "INVALID_LENGTH";
+ case ERR_INVALID_STRING:
+ return "INVALID_STRING";
+ case ERR_INVALID_CLASS_LOADER:
+ return "INVALID_CLASS_LOADER";
+ case ERR_INVALID_ARRAY:
+ return "INVALID_ARRAY";
+ case ERR_TRANSPORT_LOAD:
+ return "TRANSPORT_LOAD";
+ case ERR_TRANSPORT_INIT:
+ return "TRANSPORT_INIT";
+ case ERR_NATIVE_METHOD:
+ return "NATIVE_METHOD";
+ case ERR_INVALID_COUNT:
+ return "INVALID_COUNT";
+ default:
+ return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpError& value) {
+ os << ErrorStr(value);
+ return os;
+}
+
+const char* EventKindStr(JdwpEventKind kind) {
+ switch (kind) {
+ case EK_SINGLE_STEP: return "SINGLE_STEP";
+ case EK_BREAKPOINT: return "BREAKPOINT";
+ case EK_FRAME_POP: return "FRAME_POP";
+ case EK_EXCEPTION: return "EXCEPTION";
+ case EK_USER_DEFINED: return "USER_DEFINED";
+ case EK_THREAD_START: return "THREAD_START";
+ /*case EK_THREAD_END: return "THREAD_END";*/
+ case EK_CLASS_PREPARE: return "CLASS_PREPARE";
+ case EK_CLASS_UNLOAD: return "CLASS_UNLOAD";
+ case EK_CLASS_LOAD: return "CLASS_LOAD";
+ case EK_FIELD_ACCESS: return "FIELD_ACCESS";
+ case EK_FIELD_MODIFICATION: return "FIELD_MODIFICATION";
+ case EK_EXCEPTION_CATCH: return "EXCEPTION_CATCH";
+ case EK_METHOD_ENTRY: return "METHOD_ENTRY";
+ case EK_METHOD_EXIT: return "METHOD_EXIT";
+ case EK_VM_INIT: return "VM_INIT";
+ case EK_VM_DEATH: return "VM_DEATH";
+ case EK_VM_DISCONNECTED: return "VM_DISCONNECTED";
+ /*case EK_VM_START: return "VM_START";*/
+ case EK_THREAD_DEATH: return "THREAD_DEATH";
+ default: return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpEventKind& value) {
+ os << EventKindStr(value);
+ return os;
+}
+
+const char* ModKindStr(JdwpModKind kind) {
+ switch (kind) {
+ case MK_COUNT: return "COUNT";
+ case MK_CONDITIONAL: return "CONDITIONAL";
+ case MK_THREAD_ONLY: return "THREAD_ONLY";
+ case MK_CLASS_ONLY: return "CLASS_ONLY";
+ case MK_CLASS_MATCH: return "CLASS_MATCH";
+ case MK_CLASS_EXCLUDE: return "CLASS_EXCLUDE";
+ case MK_LOCATION_ONLY: return "LOCATION_ONLY";
+ case MK_EXCEPTION_ONLY: return "EXCEPTION_ONLY";
+ case MK_FIELD_ONLY: return "FIELD_ONLY";
+ case MK_STEP: return "STEP";
+ case MK_INSTANCE_ONLY: return "INSTANCE_ONLY";
+ default: return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpModKind& value) {
+ os << ModKindStr(value);
+ return os;
+}
+
+const char* StepDepthStr(JdwpStepDepth depth) {
+ switch (depth) {
+ case SD_INTO: return "INTO";
+ case SD_OVER: return "OVER";
+ case SD_OUT: return "OUT";
+ default: return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpStepDepth& value) {
+ os << StepDepthStr(value);
+ return os;
+}
+
+const char* StepSizeStr(JdwpStepSize size) {
+ switch (size) {
+ case SS_MIN: return "MIN";
+ case SS_LINE: return "LINE";
+ default: return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpStepSize& value) {
+ os << StepSizeStr(value);
+ return os;
+}
+
+const char* SuspendPolicyStr(JdwpSuspendPolicy policy) {
+ switch (policy) {
+ case SP_NONE: return "NONE";
+ case SP_EVENT_THREAD: return "EVENT_THREAD";
+ case SP_ALL: return "ALL";
+ default: return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendPolicy& value) {
+ os << SuspendPolicyStr(value);
+ return os;
+}
+
+const char* SuspendStatusStr(JdwpSuspendStatus status) {
+ switch (status) {
+ case SUSPEND_STATUS_NOT_SUSPENDED: return "Not SUSPENDED";
+ case SUSPEND_STATUS_SUSPENDED: return "SUSPENDED";
+ default: return "?UNKNOWN?";
+ }
+}
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendStatus& value) {
+ os << SuspendStatusStr(value);
+ return os;
+}
+
+const char* ThreadStatusStr(JdwpThreadStatus status) {
+ switch (status) {
+ case TS_ZOMBIE: return "ZOMBIE";
+ case TS_RUNNING: return "RUNNING";
+ case TS_SLEEPING: return "SLEEPING";
+ case TS_MONITOR: return "MONITOR";
+ case TS_WAIT: return "WAIT";
+ default: return "?UNKNOWN?";
+ }
+};
+std::ostream& operator<<(std::ostream& os, const JdwpThreadStatus& value) {
+ os << ThreadStatusStr(value);
+ return os;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/jdwp/jdwp_constants.h b/src/jdwp/jdwp_constants.h
new file mode 100644
index 0000000..dc6d1bc
--- /dev/null
+++ b/src/jdwp/jdwp_constants.h
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+/*
+ * These come out of the JDWP documentation.
+ */
+#ifndef ART_JDWP_JDWPCONSTANTS_H_
+#define ART_JDWP_JDWPCONSTANTS_H_
+
+#include <iosfwd>
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Error constants.
+ */
+enum JdwpError {
+ ERR_NONE = 0,
+ ERR_INVALID_THREAD = 10,
+ ERR_INVALID_THREAD_GROUP = 11,
+ ERR_INVALID_PRIORITY = 12,
+ ERR_THREAD_NOT_SUSPENDED = 13,
+ ERR_THREAD_SUSPENDED = 14,
+ ERR_INVALID_OBJECT = 20,
+ ERR_INVALID_CLASS = 21,
+ ERR_CLASS_NOT_PREPARED = 22,
+ ERR_INVALID_METHODID = 23,
+ ERR_INVALID_LOCATION = 24,
+ ERR_INVALID_FIELDID = 25,
+ ERR_INVALID_FRAMEID = 30,
+ ERR_NO_MORE_FRAMES = 31,
+ ERR_OPAQUE_FRAME = 32,
+ ERR_NOT_CURRENT_FRAME = 33,
+ ERR_TYPE_MISMATCH = 34,
+ ERR_INVALID_SLOT = 35,
+ ERR_DUPLICATE = 40,
+ ERR_NOT_FOUND = 41,
+ ERR_INVALID_MONITOR = 50,
+ ERR_NOT_MONITOR_OWNER = 51,
+ ERR_INTERRUPT = 52,
+ ERR_INVALID_CLASS_FORMAT = 60,
+ ERR_CIRCULAR_CLASS_DEFINITION = 61,
+ ERR_FAILS_VERIFICATION = 62,
+ ERR_ADD_METHOD_NOT_IMPLEMENTED = 63,
+ ERR_SCHEMA_CHANGE_NOT_IMPLEMENTED = 64,
+ ERR_INVALID_TYPESTATE = 65,
+ ERR_HIERARCHY_CHANGE_NOT_IMPLEMENTED = 66,
+ ERR_DELETE_METHOD_NOT_IMPLEMENTED = 67,
+ ERR_UNSUPPORTED_VERSION = 68,
+ ERR_NAMES_DONT_MATCH = 69,
+ ERR_CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED = 70,
+ ERR_METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED = 71,
+ ERR_NOT_IMPLEMENTED = 99,
+ ERR_NULL_POINTER = 100,
+ ERR_ABSENT_INFORMATION = 101,
+ ERR_INVALID_EVENT_TYPE = 102,
+ ERR_ILLEGAL_ARGUMENT = 103,
+ ERR_OUT_OF_MEMORY = 110,
+ ERR_ACCESS_DENIED = 111,
+ ERR_VM_DEAD = 112,
+ ERR_INTERNAL = 113,
+ ERR_UNATTACHED_THREAD = 115,
+ ERR_INVALID_TAG = 500,
+ ERR_ALREADY_INVOKING = 502,
+ ERR_INVALID_INDEX = 503,
+ ERR_INVALID_LENGTH = 504,
+ ERR_INVALID_STRING = 506,
+ ERR_INVALID_CLASS_LOADER = 507,
+ ERR_INVALID_ARRAY = 508,
+ ERR_TRANSPORT_LOAD = 509,
+ ERR_TRANSPORT_INIT = 510,
+ ERR_NATIVE_METHOD = 511,
+ ERR_INVALID_COUNT = 512,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpError& value);
+
+
+/*
+ * ClassStatus constants. These are bit flags that can be ORed together.
+ */
+enum JdwpClassStatus {
+ CS_VERIFIED = 0x01,
+ CS_PREPARED = 0x02,
+ CS_INITIALIZED = 0x04,
+ CS_ERROR = 0x08,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpClassStatus& value);
+
+/*
+ * EventKind constants.
+ */
+enum JdwpEventKind {
+ EK_SINGLE_STEP = 1,
+ EK_BREAKPOINT = 2,
+ EK_FRAME_POP = 3,
+ EK_EXCEPTION = 4,
+ EK_USER_DEFINED = 5,
+ EK_THREAD_START = 6,
+ EK_THREAD_END = 7,
+ EK_CLASS_PREPARE = 8,
+ EK_CLASS_UNLOAD = 9,
+ EK_CLASS_LOAD = 10,
+ EK_FIELD_ACCESS = 20,
+ EK_FIELD_MODIFICATION = 21,
+ EK_EXCEPTION_CATCH = 30,
+ EK_METHOD_ENTRY = 40,
+ EK_METHOD_EXIT = 41,
+ EK_VM_INIT = 90,
+ EK_VM_DEATH = 99,
+ EK_VM_DISCONNECTED = 100, /* "Never sent across JDWP */
+ EK_VM_START = EK_VM_INIT,
+ EK_THREAD_DEATH = EK_THREAD_END,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpEventKind& value);
+
+/*
+ * Values for "modKind" in EventRequest.Set.
+ */
+enum JdwpModKind {
+ MK_COUNT = 1,
+ MK_CONDITIONAL = 2,
+ MK_THREAD_ONLY = 3,
+ MK_CLASS_ONLY = 4,
+ MK_CLASS_MATCH = 5,
+ MK_CLASS_EXCLUDE = 6,
+ MK_LOCATION_ONLY = 7,
+ MK_EXCEPTION_ONLY = 8,
+ MK_FIELD_ONLY = 9,
+ MK_STEP = 10,
+ MK_INSTANCE_ONLY = 11,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpModKind& value);
+
+/*
+ * InvokeOptions constants (bit flags).
+ */
+enum JdwpInvokeOptions {
+ INVOKE_SINGLE_THREADED = 0x01,
+ INVOKE_NONVIRTUAL = 0x02,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpInvokeOptions& value);
+
+/*
+ * StepDepth constants.
+ */
+enum JdwpStepDepth {
+ SD_INTO = 0, /* step into method calls */
+ SD_OVER = 1, /* step over method calls */
+ SD_OUT = 2, /* step out of current method */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpStepDepth& value);
+
+/*
+ * StepSize constants.
+ */
+enum JdwpStepSize {
+ SS_MIN = 0, /* step by minimum (e.g. 1 bytecode inst) */
+ SS_LINE = 1, /* if possible, step to next line */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpStepSize& value);
+
+/*
+ * SuspendPolicy constants.
+ */
+enum JdwpSuspendPolicy {
+ SP_NONE = 0, /* suspend no threads */
+ SP_EVENT_THREAD = 1, /* suspend event thread */
+ SP_ALL = 2, /* suspend all threads */
+};
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendPolicy& value);
+
+/*
+ * SuspendStatus constants.
+ */
+enum JdwpSuspendStatus {
+ SUSPEND_STATUS_NOT_SUSPENDED = 0,
+ SUSPEND_STATUS_SUSPENDED = 1,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpSuspendStatus& value);
+
+/*
+ * ThreadStatus constants.
+ */
+enum JdwpThreadStatus {
+ TS_ZOMBIE = 0,
+ TS_RUNNING = 1, // RUNNING
+ TS_SLEEPING = 2, // (in Thread.sleep())
+ TS_MONITOR = 3, // WAITING (monitor wait)
+ TS_WAIT = 4, // (in Object.wait())
+};
+std::ostream& operator<<(std::ostream& os, const JdwpThreadStatus& value);
+
+/*
+ * TypeTag constants.
+ */
+enum JdwpTypeTag {
+ TT_CLASS = 1,
+ TT_INTERFACE = 2,
+ TT_ARRAY = 3,
+};
+std::ostream& operator<<(std::ostream& os, const JdwpTypeTag& value);
+
+/*
+ * Tag constants.
+ */
+enum JdwpType {
+ JT_ARRAY = '[',
+ JT_BYTE = 'B',
+ JT_CHAR = 'C',
+ JT_OBJECT = 'L',
+ JT_FLOAT = 'F',
+ JT_DOUBLE = 'D',
+ JT_INT = 'I',
+ JT_LONG = 'J',
+ JT_SHORT = 'S',
+ JT_VOID = 'V',
+ JT_BOOLEAN = 'Z',
+ JT_STRING = 's',
+ JT_THREAD = 't',
+ JT_THREAD_GROUP = 'g',
+ JT_CLASS_LOADER = 'l',
+ JT_CLASS_OBJECT = 'c',
+};
+std::ostream& operator<<(std::ostream& os, const JdwpType& value);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPCONSTANTS_H_
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
new file mode 100644
index 0000000..3a2d73e
--- /dev/null
+++ b/src/jdwp/jdwp_event.cc
@@ -0,0 +1,1211 @@
+/*
+ * 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.
+ */
+/*
+ * Send events to the debugger.
+ */
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_handler.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+#include "stringprintf.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h> /* for offsetof() */
+#include <unistd.h>
+
+/*
+General notes:
+
+The event add/remove stuff usually happens from the debugger thread,
+in response to requests from the debugger, but can also happen as the
+result of an event in an arbitrary thread (e.g. an event with a "count"
+mod expires). It's important to keep the event list locked when processing
+events.
+
+Event posting can happen from any thread. The JDWP thread will not usually
+post anything but VM start/death, but if a JDWP request causes a class
+to be loaded, the ClassPrepare event will come from the JDWP thread.
+
+
+We can have serialization issues when we post an event to the debugger.
+For example, a thread could send an "I hit a breakpoint and am suspending
+myself" message to the debugger. Before it manages to suspend itself, the
+debugger's response ("not interested, resume thread") arrives and is
+processed. We try to resume a thread that hasn't yet suspended.
+
+This means that, after posting an event to the debugger, we need to wait
+for the event thread to suspend itself (and, potentially, all other threads)
+before processing any additional requests from the debugger. While doing
+so we need to be aware that multiple threads may be hitting breakpoints
+or other events simultaneously, so we either need to wait for all of them
+or serialize the events with each other.
+
+The current mechanism works like this:
+ Event thread:
+ - If I'm going to suspend, grab the "I am posting an event" token. Wait
+ for it if it's not currently available.
+ - Post the event to the debugger.
+ - If appropriate, suspend others and then myself. As part of suspending
+ myself, release the "I am posting" token.
+ JDWP thread:
+ - When an event arrives, see if somebody is posting an event. If so,
+ sleep until we can acquire the "I am posting an event" token. Release
+ it immediately and continue processing -- the event we have already
+ received should not interfere with other events that haven't yet
+ been posted.
+
+Some care must be taken to avoid deadlock:
+
+ - thread A and thread B exit near-simultaneously, and post thread-death
+ events with a "suspend all" clause
+ - thread A gets the event token, thread B sits and waits for it
+ - thread A wants to suspend all other threads, but thread B is waiting
+ for the token and can't be suspended
+
+So we need to mark thread B in such a way that thread A doesn't wait for it.
+
+If we just bracket the "grab event token" call with a change to VMWAIT
+before sleeping, the switch back to RUNNING state when we get the token
+will cause thread B to suspend (remember, thread A's global suspend is
+still in force, even after it releases the token). Suspending while
+holding the event token is very bad, because it prevents the JDWP thread
+from processing incoming messages.
+
+We need to change to VMWAIT state at the *start* of posting an event,
+and stay there until we either finish posting the event or decide to
+put ourselves to sleep. That way we don't interfere with anyone else and
+don't allow anyone else to interfere with us.
+*/
+
+
+#define kJdwpEventCommandSet 64
+#define kJdwpCompositeCommand 100
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Stuff to compare against when deciding if a mod matches. Only the
+ * values for mods valid for the event being evaluated will be filled in.
+ * The rest will be zeroed.
+ */
+struct ModBasket {
+ const JdwpLocation* pLoc; /* LocationOnly */
+ const char* className; /* ClassMatch/ClassExclude */
+ ObjectId threadId; /* ThreadOnly */
+ RefTypeId classId; /* ClassOnly */
+ RefTypeId excepClassId; /* ExceptionOnly */
+ bool caught; /* ExceptionOnly */
+ FieldId field; /* FieldOnly */
+ ObjectId thisPtr; /* InstanceOnly */
+ /* nothing for StepOnly -- handled differently */
+};
+
+/*
+ * Get the next "request" serial number. We use this when sending
+ * packets to the debugger.
+ */
+uint32_t NextRequestSerial(JdwpState* state) {
+ MutexLock mu(state->serial_lock_);
+ return state->requestSerial++;
+}
+
+/*
+ * Get the next "event" serial number. We use this in the response to
+ * message type EventRequest.Set.
+ */
+uint32_t NextEventSerial(JdwpState* state) {
+ MutexLock mu(state->serial_lock_);
+ return state->eventSerial++;
+}
+
+/*
+ * Lock the "event" mutex, which guards the list of registered events.
+ */
+static void lockEventMutex(JdwpState* state) {
+ //Dbg::ThreadWaiting();
+ state->event_lock_.Lock();
+ //Dbg::ThreadRunning();
+}
+
+/*
+ * Unlock the "event" mutex.
+ */
+static void unlockEventMutex(JdwpState* state) {
+ state->event_lock_.Unlock();
+}
+
+/*
+ * Dump an event to the log file.
+ */
+static void dumpEvent(const JdwpEvent* pEvent) {
+ LOG(INFO) << StringPrintf("Event id=0x%4x %p (prev=%p next=%p):", pEvent->requestId, pEvent, pEvent->prev, pEvent->next);
+ LOG(INFO) << " kind=" << pEvent->eventKind << " susp=" << pEvent->suspendPolicy << " modCount=" << pEvent->modCount;
+
+ for (int i = 0; i < pEvent->modCount; i++) {
+ const JdwpEventMod* pMod = &pEvent->mods[i];
+ LOG(INFO) << " " << static_cast<JdwpModKind>(pMod->modKind);
+ /* TODO - show details */
+ }
+}
+
+/*
+ * Add an event to the list. Ordering is not important.
+ *
+ * If something prevents the event from being registered, e.g. it's a
+ * single-step request on a thread that doesn't exist, the event will
+ * not be added to the list, and an appropriate error will be returned.
+ */
+JdwpError RegisterEvent(JdwpState* state, JdwpEvent* pEvent) {
+ lockEventMutex(state);
+
+ CHECK(state != NULL);
+ CHECK(pEvent != NULL);
+ CHECK(pEvent->prev == NULL);
+ CHECK(pEvent->next == NULL);
+
+ /*
+ * If one or more "break"-type mods are used, register them with
+ * the interpreter.
+ */
+ for (int i = 0; i < pEvent->modCount; i++) {
+ const JdwpEventMod* pMod = &pEvent->mods[i];
+ if (pMod->modKind == MK_LOCATION_ONLY) {
+ /* should only be for Breakpoint, Step, and Exception */
+ Dbg::WatchLocation(&pMod->locationOnly.loc);
+ } else if (pMod->modKind == MK_STEP) {
+ /* should only be for EK_SINGLE_STEP; should only be one */
+ JdwpStepSize size = static_cast<JdwpStepSize>(pMod->step.size);
+ JdwpStepDepth depth = static_cast<JdwpStepDepth>(pMod->step.depth);
+ Dbg::ConfigureStep(pMod->step.threadId, size, depth);
+ } else if (pMod->modKind == MK_FIELD_ONLY) {
+ /* should be for EK_FIELD_ACCESS or EK_FIELD_MODIFICATION */
+ dumpEvent(pEvent); /* TODO - need for field watches */
+ }
+ }
+
+ /*
+ * Add to list.
+ */
+ if (state->eventList != NULL) {
+ pEvent->next = state->eventList;
+ state->eventList->prev = pEvent;
+ }
+ state->eventList = pEvent;
+ state->numEvents++;
+
+ unlockEventMutex(state);
+
+ return ERR_NONE;
+}
+
+/*
+ * Remove an event from the list. This will also remove the event from
+ * any optimization tables, e.g. breakpoints.
+ *
+ * Does not free the JdwpEvent.
+ *
+ * Grab the eventLock before calling here.
+ */
+static void unregisterEvent(JdwpState* state, JdwpEvent* pEvent) {
+ if (pEvent->prev == NULL) {
+ /* head of the list */
+ CHECK(state->eventList == pEvent);
+
+ state->eventList = pEvent->next;
+ } else {
+ pEvent->prev->next = pEvent->next;
+ }
+
+ if (pEvent->next != NULL) {
+ pEvent->next->prev = pEvent->prev;
+ pEvent->next = NULL;
+ }
+ pEvent->prev = NULL;
+
+ /*
+ * Unhook us from the interpreter, if necessary.
+ */
+ for (int i = 0; i < pEvent->modCount; i++) {
+ JdwpEventMod* pMod = &pEvent->mods[i];
+ if (pMod->modKind == MK_LOCATION_ONLY) {
+ /* should only be for Breakpoint, Step, and Exception */
+ Dbg::UnwatchLocation(&pMod->locationOnly.loc);
+ }
+ if (pMod->modKind == MK_STEP) {
+ /* should only be for EK_SINGLE_STEP; should only be one */
+ Dbg::UnconfigureStep(pMod->step.threadId);
+ }
+ }
+
+ state->numEvents--;
+ CHECK(state->numEvents != 0 || state->eventList == NULL);
+}
+
+/*
+ * Remove the event with the given ID from the list.
+ *
+ * Failure to find the event isn't really an error, but it is a little
+ * weird. (It looks like Eclipse will try to be extra careful and will
+ * explicitly remove one-off single-step events.)
+ */
+void UnregisterEventById(JdwpState* state, uint32_t requestId) {
+ lockEventMutex(state);
+
+ JdwpEvent* pEvent = state->eventList;
+ while (pEvent != NULL) {
+ if (pEvent->requestId == requestId) {
+ unregisterEvent(state, pEvent);
+ EventFree(pEvent);
+ goto done; /* there can be only one with a given ID */
+ }
+
+ pEvent = pEvent->next;
+ }
+
+ //LOGD("Odd: no match when removing event reqId=0x%04x", requestId);
+
+done:
+ unlockEventMutex(state);
+}
+
+/*
+ * Remove all entries from the event list.
+ */
+void UnregisterAll(JdwpState* state) {
+ lockEventMutex(state);
+
+ JdwpEvent* pEvent = state->eventList;
+ while (pEvent != NULL) {
+ JdwpEvent* pNextEvent = pEvent->next;
+
+ unregisterEvent(state, pEvent);
+ EventFree(pEvent);
+ pEvent = pNextEvent;
+ }
+
+ state->eventList = NULL;
+
+ unlockEventMutex(state);
+}
+
+/*
+ * Allocate a JdwpEvent struct with enough space to hold the specified
+ * number of mod records.
+ */
+JdwpEvent* EventAlloc(int numMods) {
+ JdwpEvent* newEvent;
+ int allocSize = offsetof(JdwpEvent, mods) + numMods * sizeof(newEvent->mods[0]);
+ newEvent = reinterpret_cast<JdwpEvent*>(malloc(allocSize));
+ memset(newEvent, 0, allocSize);
+ return newEvent;
+}
+
+/*
+ * Free a JdwpEvent.
+ *
+ * Do not call this until the event has been removed from the list.
+ */
+void EventFree(JdwpEvent* pEvent) {
+ if (pEvent == NULL) {
+ return;
+ }
+
+ /* make sure it was removed from the list */
+ CHECK(pEvent->prev == NULL);
+ CHECK(pEvent->next == NULL);
+ /* want to check state->eventList != pEvent */
+
+ /*
+ * Free any hairy bits in the mods.
+ */
+ for (int i = 0; i < pEvent->modCount; i++) {
+ if (pEvent->mods[i].modKind == MK_CLASS_MATCH) {
+ free(pEvent->mods[i].classMatch.classPattern);
+ pEvent->mods[i].classMatch.classPattern = NULL;
+ }
+ if (pEvent->mods[i].modKind == MK_CLASS_EXCLUDE) {
+ free(pEvent->mods[i].classExclude.classPattern);
+ pEvent->mods[i].classExclude.classPattern = NULL;
+ }
+ }
+
+ free(pEvent);
+}
+
+/*
+ * Allocate storage for matching events. To keep things simple we
+ * use an array with enough storage for the entire list.
+ *
+ * The state->eventLock should be held before calling.
+ */
+static JdwpEvent** allocMatchList(JdwpState* state) {
+ return (JdwpEvent**) malloc(sizeof(JdwpEvent*) * state->numEvents);
+}
+
+/*
+ * Run through the list and remove any entries with an expired "count" mod
+ * from the event list, then free the match list.
+ */
+static void cleanupMatchList(JdwpState* state, JdwpEvent** matchList, int matchCount) {
+ JdwpEvent** ppEvent = matchList;
+
+ while (matchCount--) {
+ JdwpEvent* pEvent = *ppEvent;
+
+ for (int i = 0; i < pEvent->modCount; i++) {
+ if (pEvent->mods[i].modKind == MK_COUNT && pEvent->mods[i].count.count == 0) {
+ LOG(VERBOSE) << "##### Removing expired event";
+ unregisterEvent(state, pEvent);
+ EventFree(pEvent);
+ break;
+ }
+ }
+
+ ppEvent++;
+ }
+
+ free(matchList);
+}
+
+/*
+ * Match a string against a "restricted regular expression", which is just
+ * a string that may start or end with '*' (e.g. "*.Foo" or "java.*").
+ *
+ * ("Restricted name globbing" might have been a better term.)
+ */
+static bool patternMatch(const char* pattern, const char* target) {
+ int patLen = strlen(pattern);
+
+ if (pattern[0] == '*') {
+ int targetLen = strlen(target);
+ patLen--;
+ // TODO: remove printf when we find a test case to verify this
+ LOG(ERROR) << ">>> comparing '" << (pattern + 1) << "' to '" << (target + (targetLen-patLen)) << "'";
+
+ if (targetLen < patLen) {
+ return false;
+ }
+ return strcmp(pattern+1, target + (targetLen-patLen)) == 0;
+ } else if (pattern[patLen-1] == '*') {
+ return strncmp(pattern, target, patLen-1) == 0;
+ } else {
+ return strcmp(pattern, target) == 0;
+ }
+}
+
+/*
+ * See if two locations are equal.
+ *
+ * It's tempting to do a bitwise compare ("struct ==" or memcmp), but if
+ * the storage wasn't zeroed out there could be undefined values in the
+ * padding. Besides, the odds of "idx" being equal while the others aren't
+ * is very small, so this is usually just a simple integer comparison.
+ */
+static inline bool locationMatch(const JdwpLocation* pLoc1, const JdwpLocation* pLoc2) {
+ return pLoc1->idx == pLoc2->idx &&
+ pLoc1->methodId == pLoc2->methodId &&
+ pLoc1->classId == pLoc2->classId &&
+ pLoc1->typeTag == pLoc2->typeTag;
+}
+
+/*
+ * See if the event's mods match up with the contents of "basket".
+ *
+ * If we find a Count mod before rejecting an event, we decrement it. We
+ * need to do this even if later mods cause us to ignore the event.
+ */
+static bool modsMatch(JdwpState* state, JdwpEvent* pEvent, ModBasket* basket) {
+ JdwpEventMod* pMod = pEvent->mods;
+
+ for (int i = pEvent->modCount; i > 0; i--, pMod++) {
+ switch (pMod->modKind) {
+ case MK_COUNT:
+ CHECK_GT(pMod->count.count, 0);
+ pMod->count.count--;
+ break;
+ case MK_CONDITIONAL:
+ CHECK(false); // should not be getting these
+ break;
+ case MK_THREAD_ONLY:
+ if (pMod->threadOnly.threadId != basket->threadId) {
+ return false;
+ }
+ break;
+ case MK_CLASS_ONLY:
+ if (!Dbg::MatchType(basket->classId, pMod->classOnly.refTypeId)) {
+ return false;
+ }
+ break;
+ case MK_CLASS_MATCH:
+ if (!patternMatch(pMod->classMatch.classPattern, basket->className)) {
+ return false;
+ }
+ break;
+ case MK_CLASS_EXCLUDE:
+ if (patternMatch(pMod->classMatch.classPattern, basket->className)) {
+ return false;
+ }
+ break;
+ case MK_LOCATION_ONLY:
+ if (!locationMatch(&pMod->locationOnly.loc, basket->pLoc)) {
+ return false;
+ }
+ break;
+ case MK_EXCEPTION_ONLY:
+ if (pMod->exceptionOnly.refTypeId != 0 && !Dbg::MatchType(basket->excepClassId, pMod->exceptionOnly.refTypeId)) {
+ return false;
+ }
+ if ((basket->caught && !pMod->exceptionOnly.caught) || (!basket->caught && !pMod->exceptionOnly.uncaught)) {
+ return false;
+ }
+ break;
+ case MK_FIELD_ONLY:
+ if (!Dbg::MatchType(basket->classId, pMod->fieldOnly.refTypeId) || pMod->fieldOnly.fieldId != basket->field) {
+ return false;
+ }
+ break;
+ case MK_STEP:
+ if (pMod->step.threadId != basket->threadId) {
+ return false;
+ }
+ break;
+ case MK_INSTANCE_ONLY:
+ if (pMod->instanceOnly.objectId != basket->thisPtr) {
+ return false;
+ }
+ break;
+ default:
+ LOG(ERROR) << "unhandled mod kind " << pMod->modKind;
+ CHECK(false);
+ break;
+ }
+ }
+ return true;
+}
+
+/*
+ * Find all events of type "eventKind" with mods that match up with the
+ * rest of the arguments.
+ *
+ * Found events are appended to "matchList", and "*pMatchCount" is advanced,
+ * so this may be called multiple times for grouped events.
+ *
+ * DO NOT call this multiple times for the same eventKind, as Count mods are
+ * decremented during the scan.
+ */
+static void findMatchingEvents(JdwpState* state, JdwpEventKind eventKind,
+ ModBasket* basket, JdwpEvent** matchList, int* pMatchCount) {
+ /* start after the existing entries */
+ matchList += *pMatchCount;
+
+ JdwpEvent* pEvent = state->eventList;
+ while (pEvent != NULL) {
+ if (pEvent->eventKind == eventKind && modsMatch(state, pEvent, basket)) {
+ *matchList++ = pEvent;
+ (*pMatchCount)++;
+ }
+
+ pEvent = pEvent->next;
+ }
+}
+
+/*
+ * Scan through the list of matches and determine the most severe
+ * suspension policy.
+ */
+static JdwpSuspendPolicy scanSuspendPolicy(JdwpEvent** matchList, int matchCount) {
+ JdwpSuspendPolicy policy = SP_NONE;
+
+ while (matchCount--) {
+ if ((*matchList)->suspendPolicy > policy) {
+ policy = (*matchList)->suspendPolicy;
+ }
+ matchList++;
+ }
+
+ return policy;
+}
+
+/*
+ * Three possibilities:
+ * SP_NONE - do nothing
+ * SP_EVENT_THREAD - suspend ourselves
+ * SP_ALL - suspend everybody except JDWP support thread
+ */
+static void suspendByPolicy(JdwpState* state, JdwpSuspendPolicy suspendPolicy) {
+ if (suspendPolicy == SP_NONE) {
+ return;
+ }
+
+ if (suspendPolicy == SP_ALL) {
+ Dbg::SuspendVM(true);
+ } else {
+ CHECK_EQ(suspendPolicy, SP_EVENT_THREAD);
+ }
+
+ /* this is rare but possible -- see CLASS_PREPARE handling */
+ if (Dbg::GetThreadSelfId() == state->debugThreadId) {
+ LOG(INFO) << "NOTE: suspendByPolicy not suspending JDWP thread";
+ return;
+ }
+
+ DebugInvokeReq* pReq = Dbg::GetInvokeReq();
+ while (true) {
+ pReq->ready = true;
+ Dbg::SuspendSelf();
+ pReq->ready = false;
+
+ /*
+ * The JDWP thread has told us (and possibly all other threads) to
+ * resume. See if it has left anything in our DebugInvokeReq mailbox.
+ */
+ if (!pReq->invokeNeeded) {
+ /*LOGD("suspendByPolicy: no invoke needed");*/
+ break;
+ }
+
+ /* grab this before posting/suspending again */
+ SetWaitForEventThread(state, Dbg::GetThreadSelfId());
+
+ /* leave pReq->invokeNeeded raised so we can check reentrancy */
+ LOG(VERBOSE) << "invoking method...";
+ Dbg::ExecuteMethod(pReq);
+
+ pReq->err = ERR_NONE;
+
+ /* clear this before signaling */
+ pReq->invokeNeeded = false;
+
+ LOG(VERBOSE) << "invoke complete, signaling and self-suspending";
+ MutexLock mu(pReq->lock_);
+ pReq->cond_.Signal();
+ }
+}
+
+/*
+ * Determine if there is a method invocation in progress in the current
+ * thread.
+ *
+ * We look at the "invokeNeeded" 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;
+}
+
+/*
+ * 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 SetWaitForEventThread(JdwpState* state, ObjectId threadId) {
+ bool waited = false;
+
+ /* this is held for very brief periods; contention is unlikely */
+ MutexLock mu(state->event_thread_lock_);
+
+ /*
+ * If another thread is already doing stuff, wait for it. This can
+ * go to sleep indefinitely.
+ */
+ while (state->eventThreadId != 0) {
+ LOG(VERBOSE) << StringPrintf("event in progress (0x%llx), 0x%llx sleeping", state->eventThreadId, threadId);
+ waited = true;
+ state->event_thread_cond_.Wait(state->event_thread_lock_);
+ }
+
+ if (waited || threadId != 0) {
+ LOG(VERBOSE) << StringPrintf("event token grabbed (0x%llx)", threadId);
+ }
+ if (threadId != 0) {
+ state->eventThreadId = threadId;
+ }
+}
+
+/*
+ * Clear the threadId and signal anybody waiting.
+ */
+void ClearWaitForEventThread(JdwpState* state) {
+ /*
+ * Grab the mutex. Don't try to go in/out of VMWAIT mode, as this
+ * function is called by dvmSuspendSelf(), and the transition back
+ * to RUNNING would confuse it.
+ */
+ MutexLock mu(state->event_thread_lock_);
+
+ CHECK_NE(state->eventThreadId, 0U);
+ LOG(VERBOSE) << StringPrintf("cleared event token (0x%llx)", state->eventThreadId);
+
+ state->eventThreadId = 0;
+
+ state->event_thread_cond_.Signal();
+}
+
+
+/*
+ * Prep an event. Allocates storage for the message and leaves space for
+ * the header.
+ */
+static ExpandBuf* eventPrep() {
+ ExpandBuf* pReq = expandBufAlloc();
+ expandBufAddSpace(pReq, kJDWPHeaderLen);
+ return pReq;
+}
+
+/*
+ * Write the header into the buffer and send the packet off to the debugger.
+ *
+ * Takes ownership of "pReq" (currently discards it).
+ */
+static void eventFinish(JdwpState* state, ExpandBuf* pReq) {
+ uint8_t* buf = expandBufGetBuffer(pReq);
+
+ set4BE(buf, expandBufGetLength(pReq));
+ set4BE(buf+4, NextRequestSerial(state));
+ set1(buf+8, 0); /* flags */
+ set1(buf+9, kJdwpEventCommandSet);
+ set1(buf+10, kJdwpCompositeCommand);
+
+ SendRequest(state, pReq);
+
+ expandBufFree(pReq);
+}
+
+
+/*
+ * Tell the debugger that we have finished initializing. This is always
+ * sent, even if the debugger hasn't requested it.
+ *
+ * This should be sent "before the main thread is started and before
+ * any application code has been executed". The thread ID in the message
+ * must be for the main thread.
+ */
+bool PostVMStart(JdwpState* state, bool suspend) {
+ JdwpSuspendPolicy suspendPolicy;
+ ObjectId threadId = Dbg::GetThreadSelfId();
+
+ if (suspend) {
+ suspendPolicy = SP_ALL;
+ } else {
+ suspendPolicy = SP_NONE;
+ }
+
+ /* probably don't need this here */
+ lockEventMutex(state);
+
+ ExpandBuf* pReq = NULL;
+ if (true) {
+ LOG(VERBOSE) << "EVENT: " << EK_VM_START;
+ LOG(VERBOSE) << " suspendPolicy=" << suspendPolicy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspendPolicy);
+ expandBufAdd4BE(pReq, 1);
+
+ expandBufAdd1(pReq, EK_VM_START);
+ expandBufAdd4BE(pReq, 0); /* requestId */
+ expandBufAdd8BE(pReq, threadId);
+ }
+
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, threadId);
+ }
+
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ return true;
+}
+
+// TODO: This doesn't behave like the real dvmDescriptorToName.
+// I'm hoping this isn't used to communicate with the debugger, and we can just use descriptors.
+char* dvmDescriptorToName(const char* descriptor) {
+ return strdup(descriptor);
+}
+
+/*
+ * A location of interest has been reached. This handles:
+ * Breakpoint
+ * SingleStep
+ * MethodEntry
+ * MethodExit
+ * These four types must be grouped together in a single response. The
+ * "eventFlags" indicates the type of event(s) that have happened.
+ *
+ * Valid mods:
+ * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, InstanceOnly
+ * LocationOnly (for breakpoint/step only)
+ * Step (for step only)
+ *
+ * Interesting test cases:
+ * - Put a breakpoint on a native method. Eclipse creates METHOD_ENTRY
+ * and METHOD_EXIT events with a ClassOnly mod on the method's class.
+ * - Use "run to line". Eclipse creates a BREAKPOINT with Count=1.
+ * - Single-step to a line with a breakpoint. Should get a single
+ * event message with both events in it.
+ */
+bool PostLocationEvent(JdwpState* state, const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) {
+ ModBasket basket;
+ char* nameAlloc = NULL;
+
+ memset(&basket, 0, sizeof(basket));
+ basket.pLoc = pLoc;
+ basket.classId = pLoc->classId;
+ basket.thisPtr = thisPtr;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(pLoc->classId));
+
+ /*
+ * On rare occasions we may need to execute interpreted code in the VM
+ * while handling a request from the debugger. Don't fire breakpoints
+ * while doing so. (I don't think we currently do this at all, so
+ * this is mostly paranoia.)
+ */
+ if (basket.threadId == state->debugThreadId) {
+ LOG(VERBOSE) << "Ignoring location event in JDWP thread";
+ free(nameAlloc);
+ return false;
+ }
+
+ /*
+ * The debugger variable display tab may invoke the interpreter to format
+ * complex objects. We want to ignore breakpoints and method entry/exit
+ * traps while working on behalf of the debugger.
+ *
+ * If we don't ignore them, the VM will get hung up, because we'll
+ * suspend on a breakpoint while the debugger is still waiting for its
+ * method invocation to complete.
+ */
+ if (invokeInProgress(state)) {
+ LOG(VERBOSE) << "Not checking breakpoints during invoke (" << basket.className << ")";
+ free(nameAlloc);
+ return false;
+ }
+
+ /* don't allow the list to be updated while we scan it */
+ lockEventMutex(state);
+
+ JdwpEvent** matchList = allocMatchList(state);
+ int matchCount = 0;
+
+ if ((eventFlags & Dbg::kBreakPoint) != 0) {
+ findMatchingEvents(state, EK_BREAKPOINT, &basket, matchList, &matchCount);
+ }
+ if ((eventFlags & Dbg::kSingleStep) != 0) {
+ findMatchingEvents(state, EK_SINGLE_STEP, &basket, matchList, &matchCount);
+ }
+ if ((eventFlags & Dbg::kMethodEntry) != 0) {
+ findMatchingEvents(state, EK_METHOD_ENTRY, &basket, matchList, &matchCount);
+ }
+ if ((eventFlags & Dbg::kMethodExit) != 0) {
+ findMatchingEvents(state, EK_METHOD_EXIT, &basket, matchList, &matchCount);
+ }
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+ << basket.className << "." << Dbg::GetMethodName(pLoc->classId, pLoc->methodId)
+ << " thread=" << (void*) basket.threadId << " code=" << (void*) pLoc->idx << ")";
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << suspendPolicy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspendPolicy);
+ expandBufAdd4BE(pReq, matchCount);
+
+ for (int i = 0; i < matchCount; i++) {
+ expandBufAdd1(pReq, matchList[i]->eventKind);
+ expandBufAdd4BE(pReq, matchList[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+ AddLocation(pReq, pLoc);
+ }
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ free(nameAlloc);
+ return matchCount != 0;
+}
+
+/*
+ * A thread is starting or stopping.
+ *
+ * Valid mods:
+ * Count, ThreadOnly
+ */
+bool PostThreadChange(JdwpState* state, ObjectId threadId, bool start) {
+ CHECK_EQ(threadId, Dbg::GetThreadSelfId());
+
+ /*
+ * I don't think this can happen.
+ */
+ if (invokeInProgress(state)) {
+ LOG(WARNING) << "Not posting thread change during invoke";
+ return false;
+ }
+
+ ModBasket basket;
+ memset(&basket, 0, sizeof(basket));
+ basket.threadId = threadId;
+
+ /* don't allow the list to be updated while we scan it */
+ lockEventMutex(state);
+
+ JdwpEvent** matchList = allocMatchList(state);
+ int matchCount = 0;
+
+ if (start) {
+ findMatchingEvents(state, EK_THREAD_START, &basket, matchList, &matchCount);
+ } else {
+ findMatchingEvents(state, EK_THREAD_DEATH, &basket, matchList, &matchCount);
+ }
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+ << "thread=" << (void*) basket.threadId << ")";
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << suspendPolicy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspendPolicy);
+ expandBufAdd4BE(pReq, matchCount);
+
+ for (int i = 0; i < matchCount; i++) {
+ expandBufAdd1(pReq, matchList[i]->eventKind);
+ expandBufAdd4BE(pReq, matchList[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+ }
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ return matchCount != 0;
+}
+
+/*
+ * Send a polite "VM is dying" message to the debugger.
+ *
+ * Skips the usual "event token" stuff.
+ */
+bool PostVMDeath(JdwpState* state) {
+ LOG(VERBOSE) << "EVENT: " << EK_VM_DEATH;
+
+ ExpandBuf* pReq = eventPrep();
+ expandBufAdd1(pReq, SP_NONE);
+ expandBufAdd4BE(pReq, 1);
+
+ expandBufAdd1(pReq, EK_VM_DEATH);
+ expandBufAdd4BE(pReq, 0);
+ eventFinish(state, pReq);
+ return true;
+}
+
+/*
+ * An exception has been thrown. It may or may not have been caught.
+ *
+ * Valid mods:
+ * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly,
+ * ExceptionOnly, InstanceOnly
+ *
+ * The "exceptionId" has not been added to the GC-visible object registry,
+ * because there's a pretty good chance that we're not going to send it
+ * up the debugger.
+ */
+bool PostException(JdwpState* state, const JdwpLocation* pThrowLoc,
+ ObjectId exceptionId, RefTypeId exceptionClassId,
+ const JdwpLocation* pCatchLoc, ObjectId thisPtr)
+{
+ ModBasket basket;
+ char* nameAlloc = NULL;
+
+ memset(&basket, 0, sizeof(basket));
+ basket.pLoc = pThrowLoc;
+ basket.classId = pThrowLoc->classId;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(basket.classId));
+ basket.excepClassId = exceptionClassId;
+ basket.caught = (pCatchLoc->classId != 0);
+ basket.thisPtr = thisPtr;
+
+ /* don't try to post an exception caused by the debugger */
+ if (invokeInProgress(state)) {
+ LOG(VERBOSE) << "Not posting exception hit during invoke (" << basket.className << ")";
+ free(nameAlloc);
+ return false;
+ }
+
+ /* don't allow the list to be updated while we scan it */
+ lockEventMutex(state);
+
+ JdwpEvent** matchList = allocMatchList(state);
+ int matchCount = 0;
+
+ findMatchingEvents(state, EK_EXCEPTION, &basket, matchList, &matchCount);
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total)"
+ << " thread=" << (void*) basket.threadId
+ << " exceptId=" << (void*) exceptionId
+ << " caught=" << basket.caught << ")";
+ LOG(VERBOSE) << StringPrintf(" throw: %d %llx %x %lld (%s.%s)", pThrowLoc->typeTag,
+ pThrowLoc->classId, pThrowLoc->methodId, pThrowLoc->idx,
+ Dbg::GetClassDescriptor(pThrowLoc->classId),
+ Dbg::GetMethodName(pThrowLoc->classId, pThrowLoc->methodId));
+ if (pCatchLoc->classId == 0) {
+ LOG(VERBOSE) << " catch: (not caught)";
+ } else {
+ LOG(VERBOSE) << StringPrintf(" catch: %d %llx %x %lld (%s.%s)", pCatchLoc->typeTag,
+ pCatchLoc->classId, pCatchLoc->methodId, pCatchLoc->idx,
+ Dbg::GetClassDescriptor(pCatchLoc->classId),
+ Dbg::GetMethodName(pCatchLoc->classId, pCatchLoc->methodId));
+ }
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << suspendPolicy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspendPolicy);
+ expandBufAdd4BE(pReq, matchCount);
+
+ for (int i = 0; i < matchCount; i++) {
+ expandBufAdd1(pReq, matchList[i]->eventKind);
+ expandBufAdd4BE(pReq, matchList[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+
+ AddLocation(pReq, pThrowLoc);
+ expandBufAdd1(pReq, JT_OBJECT);
+ expandBufAdd8BE(pReq, exceptionId);
+ AddLocation(pReq, pCatchLoc);
+ }
+
+ /* don't let the GC discard it */
+ Dbg::RegisterObjectId(exceptionId);
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ free(nameAlloc);
+ return matchCount != 0;
+}
+
+/*
+ * Announce that a class has been loaded.
+ *
+ * Valid mods:
+ * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude
+ */
+bool PostClassPrepare(JdwpState* state, int tag, RefTypeId refTypeId, const char* signature, int status) {
+ ModBasket basket;
+ char* nameAlloc = NULL;
+
+ memset(&basket, 0, sizeof(basket));
+ basket.classId = refTypeId;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = nameAlloc = dvmDescriptorToName(Dbg::GetClassDescriptor(basket.classId));
+
+ /* suppress class prep caused by debugger */
+ if (invokeInProgress(state)) {
+ LOG(VERBOSE) << "Not posting class prep caused by invoke (" << basket.className << ")";
+ free(nameAlloc);
+ return false;
+ }
+
+ /* don't allow the list to be updated while we scan it */
+ lockEventMutex(state);
+
+ JdwpEvent** matchList = allocMatchList(state);
+ int matchCount = 0;
+
+ findMatchingEvents(state, EK_CLASS_PREPARE, &basket, matchList, &matchCount);
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspendPolicy = SP_NONE;
+ if (matchCount != 0) {
+ LOG(VERBOSE) << "EVENT: " << matchList[0]->eventKind << "(" << matchCount << " total) "
+ << "thread=" << (void*) basket.threadId << ")";
+
+ suspendPolicy = scanSuspendPolicy(matchList, matchCount);
+ LOG(VERBOSE) << " suspendPolicy=" << suspendPolicy;
+
+ if (basket.threadId == state->debugThreadId) {
+ /*
+ * JDWP says that, for a class prep in the debugger thread, we
+ * should set threadId to null and if any threads were supposed
+ * to be suspended then we suspend all other threads.
+ */
+ LOG(VERBOSE) << " NOTE: class prepare in debugger thread!";
+ basket.threadId = 0;
+ if (suspendPolicy == SP_EVENT_THREAD) {
+ suspendPolicy = SP_ALL;
+ }
+ }
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspendPolicy);
+ expandBufAdd4BE(pReq, matchCount);
+
+ for (int i = 0; i < matchCount; i++) {
+ expandBufAdd1(pReq, matchList[i]->eventKind);
+ expandBufAdd4BE(pReq, matchList[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+
+ expandBufAdd1(pReq, tag);
+ expandBufAdd8BE(pReq, refTypeId);
+ expandBufAddUtf8String(pReq, (const uint8_t*) signature);
+ expandBufAdd4BE(pReq, status);
+ }
+ }
+
+ cleanupMatchList(state, matchList, matchCount);
+
+ unlockEventMutex(state);
+
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ int oldStatus = Dbg::ThreadWaiting();
+ if (suspendPolicy != SP_NONE) {
+ SetWaitForEventThread(state, basket.threadId);
+ }
+ eventFinish(state, pReq);
+
+ suspendByPolicy(state, suspendPolicy);
+ Dbg::ThreadContinuing(oldStatus);
+ }
+
+ free(nameAlloc);
+ return matchCount != 0;
+}
+
+bool SendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+ return (*state->transport->sendBufferedRequest)(state, iov, iovcnt);
+}
+
+/*
+ * Send up a chunk of DDM data.
+ *
+ * While this takes the form of a JDWP "event", it doesn't interact with
+ * other debugger traffic, and can't suspend the VM, so we skip all of
+ * the fun event token gymnastics.
+ */
+void DdmSendChunkV(JdwpState* state, int type, const iovec* iov, int iovcnt) {
+ uint8_t header[kJDWPHeaderLen + 8];
+ size_t dataLen = 0;
+
+ CHECK(iov != NULL);
+ CHECK(iovcnt > 0 && iovcnt < 10);
+
+ /*
+ * "Wrap" the contents of the iovec with a JDWP/DDMS header. We do
+ * this by creating a new copy of the vector with space for the header.
+ */
+ iovec wrapiov[iovcnt+1];
+ for (int i = 0; i < iovcnt; i++) {
+ wrapiov[i+1].iov_base = iov[i].iov_base;
+ wrapiov[i+1].iov_len = iov[i].iov_len;
+ dataLen += iov[i].iov_len;
+ }
+
+ /* form the header (JDWP plus DDMS) */
+ set4BE(header, sizeof(header) + dataLen);
+ set4BE(header+4, NextRequestSerial(state));
+ set1(header+8, 0); /* flags */
+ set1(header+9, kJDWPDdmCmdSet);
+ set1(header+10, kJDWPDdmCmd);
+ set4BE(header+11, type);
+ set4BE(header+15, dataLen);
+
+ wrapiov[0].iov_base = header;
+ wrapiov[0].iov_len = sizeof(header);
+
+ /*
+ * Make sure we're in VMWAIT in case the write blocks.
+ */
+ int oldStatus = Dbg::ThreadWaiting();
+ SendBufferedRequest(state, wrapiov, iovcnt+1);
+ Dbg::ThreadContinuing(oldStatus);
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/jdwp/jdwp_event.h b/src/jdwp/jdwp_event.h
new file mode 100644
index 0000000..2b957fb
--- /dev/null
+++ b/src/jdwp/jdwp_event.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+/*
+ * Handle registration of events, and debugger event notification.
+ */
+#ifndef ART_JDWP_JDWPEVENT_H_
+#define ART_JDWP_JDWPEVENT_H_
+
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Event modifiers. A JdwpEvent may have zero or more of these.
+ */
+union JdwpEventMod {
+ uint8_t modKind; /* JdwpModKind */
+ struct {
+ uint8_t modKind;
+ int count;
+ } count;
+ struct {
+ uint8_t modKind;
+ uint32_t exprId;
+ } conditional;
+ struct {
+ uint8_t modKind;
+ ObjectId threadId;
+ } threadOnly;
+ struct {
+ uint8_t modKind;
+ RefTypeId refTypeId;
+ } classOnly;
+ struct {
+ uint8_t modKind;
+ char* classPattern;
+ } classMatch;
+ struct {
+ uint8_t modKind;
+ char* classPattern;
+ } classExclude;
+ struct {
+ uint8_t modKind;
+ JdwpLocation loc;
+ } locationOnly;
+ struct {
+ uint8_t modKind;
+ uint8_t caught;
+ uint8_t uncaught;
+ RefTypeId refTypeId;
+ } exceptionOnly;
+ struct {
+ uint8_t modKind;
+ RefTypeId refTypeId;
+ FieldId fieldId;
+ } fieldOnly;
+ struct {
+ uint8_t modKind;
+ ObjectId threadId;
+ int size; /* JdwpStepSize */
+ int depth; /* JdwpStepDepth */
+ } step;
+ struct {
+ uint8_t modKind;
+ ObjectId objectId;
+ } instanceOnly;
+};
+
+/*
+ * One of these for every registered event.
+ *
+ * We over-allocate the struct to hold the modifiers.
+ */
+struct JdwpEvent {
+ JdwpEvent* prev; /* linked list */
+ JdwpEvent* next;
+
+ JdwpEventKind eventKind; /* what kind of event is this? */
+ JdwpSuspendPolicy suspendPolicy; /* suspend all, none, or self? */
+ int modCount; /* #of entries in mods[] */
+ uint32_t requestId; /* serial#, reported to debugger */
+
+ JdwpEventMod mods[1]; /* MUST be last field in struct */
+};
+
+/*
+ * Allocate an event structure with enough space.
+ */
+JdwpEvent* EventAlloc(int numMods);
+void EventFree(JdwpEvent* pEvent);
+
+/*
+ * Register an event by adding it to the event list.
+ *
+ * "*pEvent" must be storage allocated with jdwpEventAlloc(). The caller
+ * may discard its pointer after calling this.
+ */
+JdwpError RegisterEvent(JdwpState* state, JdwpEvent* pEvent);
+
+/*
+ * Unregister an event, given the requestId.
+ */
+void UnregisterEventById(JdwpState* state, uint32_t requestId);
+
+/*
+ * Unregister all events.
+ */
+void UnregisterAll(JdwpState* state);
+
+/*
+ * Send an event, formatted into "pReq", to the debugger.
+ *
+ * (Messages are sent asynchronously, and do not receive a reply.)
+ */
+bool SendRequest(JdwpState* state, ExpandBuf* pReq);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPEVENT_H_
diff --git a/src/jdwp/jdwp_expand_buf.cc b/src/jdwp/jdwp_expand_buf.cc
new file mode 100644
index 0000000..f1675fd
--- /dev/null
+++ b/src/jdwp/jdwp_expand_buf.cc
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+/*
+ * Implementation of an expandable byte buffer. Designed for serializing
+ * primitive values, e.g. JDWP replies.
+ */
+
+#include "jdwp/jdwp_bits.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Data structure used to track buffer use.
+ */
+struct ExpandBuf {
+ uint8_t* storage;
+ int curLen;
+ int maxLen;
+};
+
+#define kInitialStorage 64
+
+/*
+ * Allocate a JdwpBuf and some initial storage.
+ */
+ExpandBuf* expandBufAlloc() {
+ ExpandBuf* newBuf;
+
+ newBuf = (ExpandBuf*) malloc(sizeof(*newBuf));
+ newBuf->storage = (uint8_t*) malloc(kInitialStorage);
+ newBuf->curLen = 0;
+ newBuf->maxLen = kInitialStorage;
+
+ return newBuf;
+}
+
+/*
+ * Free a JdwpBuf and associated storage.
+ */
+void expandBufFree(ExpandBuf* pBuf) {
+ if (pBuf == NULL) {
+ return;
+ }
+
+ free(pBuf->storage);
+ free(pBuf);
+}
+
+/*
+ * Get a pointer to the start of the buffer.
+ */
+uint8_t* expandBufGetBuffer(ExpandBuf* pBuf) {
+ return pBuf->storage;
+}
+
+/*
+ * Get the amount of data currently in the buffer.
+ */
+size_t expandBufGetLength(ExpandBuf* pBuf) {
+ return pBuf->curLen;
+}
+
+
+/*
+ * Ensure that the buffer has enough space to hold incoming data. If it
+ * doesn't, resize the buffer.
+ */
+static void ensureSpace(ExpandBuf* pBuf, int newCount) {
+ if (pBuf->curLen + newCount <= pBuf->maxLen) {
+ return;
+ }
+
+ while (pBuf->curLen + newCount > pBuf->maxLen) {
+ pBuf->maxLen *= 2;
+ }
+
+ uint8_t* newPtr = (uint8_t*) realloc(pBuf->storage, pBuf->maxLen);
+ if (newPtr == NULL) {
+ LOG(ERROR) << "realloc(" << pBuf->maxLen << ") failed";
+ abort();
+ }
+
+ pBuf->storage = newPtr;
+}
+
+/*
+ * Allocate some space in the buffer.
+ */
+uint8_t* expandBufAddSpace(ExpandBuf* pBuf, int gapSize) {
+ uint8_t* gapStart;
+
+ ensureSpace(pBuf, gapSize);
+ gapStart = pBuf->storage + pBuf->curLen;
+ /* do we want to garbage-fill the gap for debugging? */
+ pBuf->curLen += gapSize;
+
+ return gapStart;
+}
+
+/*
+ * Append a byte.
+ */
+void expandBufAdd1(ExpandBuf* pBuf, uint8_t val) {
+ ensureSpace(pBuf, sizeof(val));
+ *(pBuf->storage + pBuf->curLen) = val;
+ pBuf->curLen++;
+}
+
+/*
+ * Append two big-endian bytes.
+ */
+void expandBufAdd2BE(ExpandBuf* pBuf, uint16_t val) {
+ ensureSpace(pBuf, sizeof(val));
+ set2BE(pBuf->storage + pBuf->curLen, val);
+ pBuf->curLen += sizeof(val);
+}
+
+/*
+ * Append four big-endian bytes.
+ */
+void expandBufAdd4BE(ExpandBuf* pBuf, uint32_t val) {
+ ensureSpace(pBuf, sizeof(val));
+ set4BE(pBuf->storage + pBuf->curLen, val);
+ pBuf->curLen += sizeof(val);
+}
+
+/*
+ * Append eight big-endian bytes.
+ */
+void expandBufAdd8BE(ExpandBuf* pBuf, uint64_t val) {
+ ensureSpace(pBuf, sizeof(val));
+ set8BE(pBuf->storage + pBuf->curLen, val);
+ pBuf->curLen += sizeof(val);
+}
+
+/*
+ * Add a UTF8 string as a 4-byte length followed by a non-NULL-terminated
+ * string.
+ *
+ * Because these strings are coming out of the VM, it's safe to assume that
+ * they can be null-terminated (either they don't have null bytes or they
+ * have stored null bytes in a multi-byte encoding).
+ */
+void expandBufAddUtf8String(ExpandBuf* pBuf, const uint8_t* str) {
+ int strLen = strlen((const char*)str);
+
+ ensureSpace(pBuf, sizeof(uint32_t) + strLen);
+ setUtf8String(pBuf->storage + pBuf->curLen, str);
+ pBuf->curLen += sizeof(uint32_t) + strLen;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/jdwp/jdwp_expand_buf.h b/src/jdwp/jdwp_expand_buf.h
new file mode 100644
index 0000000..2c19f54
--- /dev/null
+++ b/src/jdwp/jdwp_expand_buf.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+/*
+ * Expanding byte buffer, with primitives for appending basic data types.
+ */
+#ifndef ART_JDWP_EXPANDBUF_H_
+#define ART_JDWP_EXPANDBUF_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace art {
+
+namespace JDWP {
+
+struct ExpandBuf; /* private */
+
+/* create a new struct */
+ExpandBuf* expandBufAlloc();
+/* free storage */
+void expandBufFree(ExpandBuf* pBuf);
+
+/*
+ * Accessors. The buffer pointer and length will only be valid until more
+ * data is added.
+ */
+uint8_t* expandBufGetBuffer(ExpandBuf* pBuf);
+size_t expandBufGetLength(ExpandBuf* pBuf);
+
+/*
+ * The "add" operations allocate additional storage and append the data.
+ *
+ * There are no "get" operations included with this "class", other than
+ * GetBuffer(). If you want to get or set data from a position other
+ * than the end, get a pointer to the buffer and use the inline functions
+ * defined elsewhere.
+ *
+ * expandBufAddSpace() returns a pointer to the *start* of the region
+ * added.
+ */
+uint8_t* expandBufAddSpace(ExpandBuf* pBuf, int gapSize);
+void expandBufAdd1(ExpandBuf* pBuf, uint8_t val);
+void expandBufAdd2BE(ExpandBuf* pBuf, uint16_t val);
+void expandBufAdd4BE(ExpandBuf* pBuf, uint32_t val);
+void expandBufAdd8BE(ExpandBuf* pBuf, uint64_t val);
+void expandBufAddUtf8String(ExpandBuf* pBuf, const uint8_t* str);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_EXPANDBUF_H_
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
new file mode 100644
index 0000000..8fe3c3f
--- /dev/null
+++ b/src/jdwp/jdwp_handler.cc
@@ -0,0 +1,1818 @@
+/*
+ * 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.
+ */
+
+/*
+ * Handle messages from debugger.
+ *
+ * GENERAL NOTE: we're not currently testing the message length for
+ * correctness. This is usually a bad idea, but here we can probably
+ * get away with it so long as the debugger isn't broken. We can
+ * change the "read" macros to use "dataLen" to avoid wandering into
+ * bad territory, and have a single "is dataLen correct" check at the
+ * end of each function. Not needed at this time.
+ */
+
+#include "atomic.h"
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_handler.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "logging.h"
+#include "macros.h"
+#include "stringprintf.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Helper function: read a "location" from an input buffer.
+ */
+static void jdwpReadLocation(const uint8_t** pBuf, JdwpLocation* pLoc) {
+ memset(pLoc, 0, sizeof(*pLoc)); /* allows memcmp() later */
+ pLoc->typeTag = read1(pBuf);
+ pLoc->classId = ReadObjectId(pBuf);
+ pLoc->methodId = ReadMethodId(pBuf);
+ pLoc->idx = read8BE(pBuf);
+}
+
+/*
+ * Helper function: write a "location" into the reply buffer.
+ */
+void AddLocation(ExpandBuf* pReply, const JdwpLocation* pLoc) {
+ expandBufAdd1(pReply, pLoc->typeTag);
+ expandBufAddObjectId(pReply, pLoc->classId);
+ expandBufAddMethodId(pReply, pLoc->methodId);
+ expandBufAdd8BE(pReply, pLoc->idx);
+}
+
+/*
+ * Helper function: read a variable-width value from the input buffer.
+ */
+static uint64_t jdwpReadValue(const uint8_t** pBuf, int width) {
+ uint64_t value = -1;
+ switch (width) {
+ case 1: value = read1(pBuf); break;
+ case 2: value = read2BE(pBuf); break;
+ case 4: value = read4BE(pBuf); break;
+ case 8: value = read8BE(pBuf); break;
+ default: LOG(FATAL) << width; break;
+ }
+ return value;
+}
+
+/*
+ * Helper function: write a variable-width value into the output input buffer.
+ */
+static void jdwpWriteValue(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;
+ }
+}
+
+/*
+ * Common code for *_InvokeMethod requests.
+ *
+ * If "isConstructor" is set, this returns "objectId" rather than the
+ * expected-to-be-void return value of the called function.
+ */
+static JdwpError finishInvoke(JdwpState* state,
+ const uint8_t* buf, int dataLen, ExpandBuf* pReply,
+ ObjectId threadId, ObjectId objectId, RefTypeId classId, MethodId methodId,
+ bool isConstructor)
+{
+ CHECK(!isConstructor || objectId != 0);
+
+ uint32_t numArgs = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" --> threadId=%llx objectId=%llx", threadId, objectId);
+ LOG(VERBOSE) << StringPrintf(" classId=%llx methodId=%x %s.%s", classId, methodId, Dbg::GetClassDescriptor(classId), Dbg::GetMethodName(classId, methodId));
+ LOG(VERBOSE) << StringPrintf(" %d args:", numArgs);
+
+ uint64_t* argArray = NULL;
+ if (numArgs > 0) {
+ argArray = (ObjectId*) malloc(sizeof(ObjectId) * numArgs);
+ }
+
+ for (uint32_t i = 0; i < numArgs; i++) {
+ uint8_t typeTag = read1(&buf);
+ int width = Dbg::GetTagWidth(typeTag);
+ uint64_t value = jdwpReadValue(&buf, width);
+
+ LOG(VERBOSE) << StringPrintf(" '%c'(%d): 0x%llx", typeTag, width, value);
+ argArray[i] = value;
+ }
+
+ uint32_t options = read4BE(&buf); /* enum InvokeOptions bit flags */
+ LOG(VERBOSE) << StringPrintf(" options=0x%04x%s%s", options, (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "", (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");
+
+ uint8_t resultTag;
+ uint64_t resultValue;
+ ObjectId exceptObjId;
+ JdwpError err = Dbg::InvokeMethod(threadId, objectId, classId, methodId, numArgs, argArray, options, &resultTag, &resultValue, &exceptObjId);
+ if (err != ERR_NONE) {
+ goto bail;
+ }
+
+ if (err == ERR_NONE) {
+ if (isConstructor) {
+ expandBufAdd1(pReply, JT_OBJECT);
+ expandBufAddObjectId(pReply, objectId);
+ } else {
+ int width = Dbg::GetTagWidth(resultTag);
+
+ expandBufAdd1(pReply, resultTag);
+ if (width != 0) {
+ jdwpWriteValue(pReply, width, resultValue);
+ }
+ }
+ expandBufAdd1(pReply, JT_OBJECT);
+ expandBufAddObjectId(pReply, exceptObjId);
+
+ LOG(VERBOSE) << StringPrintf(" --> returned '%c' 0x%llx (except=%08llx)", resultTag, resultValue, exceptObjId);
+
+ /* show detailed debug output */
+ if (resultTag == JT_STRING && exceptObjId == 0) {
+ if (resultValue != 0) {
+ char* str = Dbg::StringToUtf8(resultValue);
+ LOG(VERBOSE) << StringPrintf(" string '%s'", str);
+ free(str);
+ } else {
+ LOG(VERBOSE) << " string (null)";
+ }
+ }
+ }
+
+bail:
+ free(argArray);
+ return err;
+}
+
+
+/*
+ * Request for version info.
+ */
+static JdwpError handleVM_Version(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ /* text information on runtime version */
+ std::string version(StringPrintf("Android Runtime %s", Runtime::Current()->GetVersion()));
+ expandBufAddUtf8String(pReply, (const uint8_t*) version.c_str());
+ /* JDWP version numbers */
+ expandBufAdd4BE(pReply, 1); // major
+ expandBufAdd4BE(pReply, 5); // minor
+ /* VM JRE version */
+ expandBufAddUtf8String(pReply, (const uint8_t*) "1.6.0"); /* e.g. 1.6.0_22 */
+ /* target VM name */
+ expandBufAddUtf8String(pReply, (const uint8_t*) "DalvikVM");
+
+ 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 handleVM_ClassesBySignature(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ size_t strLen;
+ char* classDescriptor = readNewUtf8String(&buf, &strLen);
+ LOG(VERBOSE) << " Req for class by signature '" << classDescriptor << "'";
+
+ /*
+ * TODO: if a class with the same name has been loaded multiple times
+ * (by different class loaders), we're supposed to return each of them.
+ *
+ * NOTE: this may mangle "className".
+ */
+ uint32_t numClasses;
+ RefTypeId refTypeId;
+ if (!Dbg::FindLoadedClassBySignature(classDescriptor, &refTypeId)) {
+ /* not currently loaded */
+ LOG(VERBOSE) << " --> no match!";
+ numClasses = 0;
+ } else {
+ /* just the one */
+ numClasses = 1;
+ }
+
+ expandBufAdd4BE(pReply, numClasses);
+
+ if (numClasses > 0) {
+ uint8_t typeTag;
+ uint32_t status;
+
+ /* get class vs. interface and status flags */
+ Dbg::GetClassInfo(refTypeId, &typeTag, &status, NULL);
+
+ expandBufAdd1(pReply, typeTag);
+ expandBufAddRefTypeId(pReply, refTypeId);
+ expandBufAdd4BE(pReply, status);
+ }
+
+ free(classDescriptor);
+
+ 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 handleVM_AllThreads(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId* pThreadIds;
+ uint32_t threadCount;
+ Dbg::GetAllThreads(&pThreadIds, &threadCount);
+
+ expandBufAdd4BE(pReply, threadCount);
+
+ ObjectId* walker = pThreadIds;
+ for (uint32_t i = 0; i < threadCount; i++) {
+ expandBufAddObjectId(pReply, *walker++);
+ }
+
+ free(pThreadIds);
+
+ return ERR_NONE;
+}
+
+/*
+ * List all thread groups that do not have a parent.
+ */
+static JdwpError handleVM_TopLevelThreadGroups(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ /*
+ * 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);
+ //threadGroupId = debugGetMainThreadGroup();
+ //expandBufAdd8BE(pReply, threadGroupId);
+ ObjectId threadGroupId = Dbg::GetSystemThreadGroupId();
+ expandBufAddObjectId(pReply, threadGroupId);
+
+ return ERR_NONE;
+}
+
+/*
+ * Respond with the sizes of the basic debugger types.
+ *
+ * All IDs are 8 bytes.
+ */
+static JdwpError handleVM_IDSizes(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ expandBufAdd4BE(pReply, sizeof(FieldId));
+ expandBufAdd4BE(pReply, sizeof(MethodId));
+ expandBufAdd4BE(pReply, sizeof(ObjectId));
+ expandBufAdd4BE(pReply, sizeof(RefTypeId));
+ expandBufAdd4BE(pReply, sizeof(FrameId));
+ return ERR_NONE;
+}
+
+/*
+ * The debugger is politely asking to disconnect. We're good with that.
+ *
+ * We could resume threads and clean up pinned references, but we can do
+ * that when the TCP connection drops.
+ */
+static JdwpError handleVM_Dispose(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ 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 handleVM_Suspend(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ Dbg::SuspendVM(false);
+ return ERR_NONE;
+}
+
+/*
+ * Resume execution. Decrements the "suspend count" of all threads.
+ */
+static JdwpError handleVM_Resume(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ Dbg::ResumeVM();
+ return ERR_NONE;
+}
+
+/*
+ * The debugger wants the entire VM to exit.
+ */
+static JdwpError handleVM_Exit(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ uint32_t exitCode = get4BE(buf);
+
+ LOG(WARNING) << "Debugger is telling the VM to exit with code=" << exitCode;
+
+ Dbg::Exit(exitCode);
+ return ERR_NOT_IMPLEMENTED; // shouldn't get here
+}
+
+/*
+ * 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 handleVM_CreateString(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ size_t strLen;
+ char* str = readNewUtf8String(&buf, &strLen);
+
+ LOG(VERBOSE) << " Req to create string '" << str << "'";
+
+ ObjectId stringId = Dbg::CreateString(str);
+ if (stringId == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ expandBufAddObjectId(pReply, stringId);
+ return ERR_NONE;
+}
+
+/*
+ * Tell the debugger what we are capable of.
+ */
+static JdwpError handleVM_Capabilities(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ expandBufAdd1(pReply, false); /* canWatchFieldModification */
+ expandBufAdd1(pReply, false); /* canWatchFieldAccess */
+ expandBufAdd1(pReply, false); /* canGetBytecodes */
+ expandBufAdd1(pReply, true); /* canGetSyntheticAttribute */
+ expandBufAdd1(pReply, false); /* canGetOwnedMonitorInfo */
+ expandBufAdd1(pReply, false); /* canGetCurrentContendedMonitor */
+ expandBufAdd1(pReply, false); /* canGetMonitorInfo */
+ return ERR_NONE;
+}
+
+/*
+ * Return classpath and bootclasspath.
+ */
+static JdwpError handleVM_ClassPaths(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ char baseDir[2] = "/";
+
+ /*
+ * TODO: make this real. Not important for remote debugging, but
+ * might be useful for local debugging.
+ */
+ uint32_t classPaths = 1;
+ uint32_t bootClassPaths = 0;
+
+ expandBufAddUtf8String(pReply, (const uint8_t*) baseDir);
+ expandBufAdd4BE(pReply, classPaths);
+ for (uint32_t i = 0; i < classPaths; i++) {
+ expandBufAddUtf8String(pReply, (const uint8_t*) ".");
+ }
+
+ expandBufAdd4BE(pReply, bootClassPaths);
+ for (uint32_t i = 0; i < classPaths; i++) {
+ /* add bootclasspath components as strings */
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Release a list of object IDs. (Seen in jdb.)
+ *
+ * Currently does nothing.
+ */
+static JdwpError HandleVM_DisposeObjects(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ return ERR_NONE;
+}
+
+/*
+ * Tell the debugger what we are capable of.
+ */
+static JdwpError handleVM_CapabilitiesNew(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ expandBufAdd1(pReply, false); /* canWatchFieldModification */
+ expandBufAdd1(pReply, false); /* canWatchFieldAccess */
+ expandBufAdd1(pReply, false); /* canGetBytecodes */
+ expandBufAdd1(pReply, true); /* canGetSyntheticAttribute */
+ expandBufAdd1(pReply, false); /* canGetOwnedMonitorInfo */
+ expandBufAdd1(pReply, false); /* canGetCurrentContendedMonitor */
+ expandBufAdd1(pReply, false); /* canGetMonitorInfo */
+ expandBufAdd1(pReply, false); /* canRedefineClasses */
+ expandBufAdd1(pReply, false); /* canAddMethod */
+ expandBufAdd1(pReply, false); /* canUnrestrictedlyRedefineClasses */
+ expandBufAdd1(pReply, false); /* canPopFrames */
+ expandBufAdd1(pReply, false); /* canUseInstanceFilters */
+ expandBufAdd1(pReply, false); /* canGetSourceDebugExtension */
+ expandBufAdd1(pReply, false); /* canRequestVMDeathEvent */
+ expandBufAdd1(pReply, false); /* canSetDefaultStratum */
+ expandBufAdd1(pReply, false); /* 1.6: canGetInstanceInfo */
+ expandBufAdd1(pReply, false); /* 1.6: canRequestMonitorEvents */
+ expandBufAdd1(pReply, false); /* 1.6: canGetMonitorFrameInfo */
+ expandBufAdd1(pReply, false); /* 1.6: canUseSourceNameFilters */
+ expandBufAdd1(pReply, false); /* 1.6: canGetConstantPool */
+ expandBufAdd1(pReply, false); /* 1.6: canForceEarlyReturn */
+
+ /* fill in reserved22 through reserved32; note count started at 1 */
+ for (int i = 22; i <= 32; i++) {
+ expandBufAdd1(pReply, false); /* reservedN */
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Cough up the complete list of classes.
+ */
+static JdwpError handleVM_AllClassesWithGeneric(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ uint32_t numClasses = 0;
+ RefTypeId* classRefBuf = NULL;
+
+ Dbg::GetClassList(&numClasses, &classRefBuf);
+
+ expandBufAdd4BE(pReply, numClasses);
+
+ for (uint32_t i = 0; i < numClasses; i++) {
+ static const uint8_t genericSignature[1] = "";
+ uint8_t refTypeTag;
+ const char* signature;
+ uint32_t status;
+
+ Dbg::GetClassInfo(classRefBuf[i], &refTypeTag, &status, &signature);
+
+ expandBufAdd1(pReply, refTypeTag);
+ expandBufAddRefTypeId(pReply, classRefBuf[i]);
+ expandBufAddUtf8String(pReply, (const uint8_t*) signature);
+ expandBufAddUtf8String(pReply, genericSignature);
+ expandBufAdd4BE(pReply, status);
+ }
+
+ free(classRefBuf);
+
+ return ERR_NONE;
+}
+
+/*
+ * Given a referenceTypeID, return a string with the JNI reference type
+ * signature (e.g. "Ljava/lang/Error;").
+ */
+static JdwpError handleRT_Signature(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for signature of refTypeId=0x%llx", refTypeId);
+ const char* signature = Dbg::GetSignature(refTypeId);
+ expandBufAddUtf8String(pReply, (const uint8_t*) signature);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the modifiers (a/k/a access flags) for a reference type.
+ */
+static JdwpError handleRT_Modifiers(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+ uint32_t modBits = Dbg::GetAccessFlags(refTypeId);
+ expandBufAdd4BE(pReply, modBits);
+ return ERR_NONE;
+}
+
+/*
+ * Get values from static fields in a reference type.
+ */
+static JdwpError handleRT_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+ uint32_t numFields = read4BE(&buf);
+
+ LOG(VERBOSE) << " RT_GetValues " << numFields << ":";
+
+ expandBufAdd4BE(pReply, numFields);
+ for (uint32_t i = 0; i < numFields; i++) {
+ FieldId fieldId = ReadFieldId(&buf);
+ Dbg::GetStaticFieldValue(refTypeId, fieldId, pReply);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Get the name of the source file in which a reference type was declared.
+ */
+static JdwpError handleRT_SourceFile(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ const char* fileName = Dbg::GetSourceFile(refTypeId);
+ if (fileName != NULL) {
+ expandBufAddUtf8String(pReply, (const uint8_t*) fileName);
+ return ERR_NONE;
+ } else {
+ return ERR_ABSENT_INFORMATION;
+ }
+}
+
+/*
+ * Return the current status of the reference type.
+ */
+static JdwpError handleRT_Status(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ /* get status flags */
+ uint8_t typeTag;
+ uint32_t status;
+ Dbg::GetClassInfo(refTypeId, &typeTag, &status, NULL);
+ expandBufAdd4BE(pReply, status);
+ return ERR_NONE;
+}
+
+/*
+ * Return interfaces implemented directly by this class.
+ */
+static JdwpError handleRT_Interfaces(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for interfaces in %llx (%s)", refTypeId,
+ Dbg::GetClassDescriptor(refTypeId));
+
+ Dbg::OutputAllInterfaces(refTypeId, pReply);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the class object corresponding to this type.
+ */
+static JdwpError handleRT_ClassObject(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+ ObjectId classObjId = Dbg::GetClassObject(refTypeId);
+
+ LOG(VERBOSE) << StringPrintf(" RefTypeId %llx -> ObjectId %llx", refTypeId, classObjId);
+
+ expandBufAddObjectId(pReply, classObjId);
+
+ return ERR_NONE;
+}
+
+/*
+ * Returns the value of the SourceDebugExtension attribute.
+ *
+ * JDB seems interested, but DEX files don't currently support this.
+ */
+static JdwpError handleRT_SourceDebugExtension(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ /* referenceTypeId in, string out */
+ return ERR_ABSENT_INFORMATION;
+}
+
+/*
+ * Like RT_Signature but with the possibility of a "generic signature".
+ */
+static JdwpError handleRT_SignatureWithGeneric(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ static const uint8_t genericSignature[1] = "";
+
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for signature of refTypeId=0x%llx", refTypeId);
+ const char* signature = Dbg::GetSignature(refTypeId);
+ if (signature != NULL) {
+ expandBufAddUtf8String(pReply, (const uint8_t*) signature);
+ } else {
+ LOG(WARNING) << StringPrintf("No signature for refTypeId=0x%llx", refTypeId);
+ expandBufAddUtf8String(pReply, (const uint8_t*) "Lunknown;");
+ }
+ expandBufAddUtf8String(pReply, genericSignature);
+
+ return ERR_NONE;
+}
+
+/*
+ * 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 handleRT_ClassLoader(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ expandBufAddObjectId(pReply, Dbg::GetClassLoader(refTypeId));
+
+ return ERR_NONE;
+}
+
+/*
+ * Given a referenceTypeId, return a block of stuff that describes the
+ * fields declared by a class.
+ */
+static JdwpError handleRT_FieldsWithGeneric(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+ LOG(VERBOSE) << StringPrintf(" Req for fields in refTypeId=0x%llx", refTypeId);
+ LOG(VERBOSE) << StringPrintf(" --> '%s'", Dbg::GetSignature(refTypeId));
+ Dbg::OutputAllFields(refTypeId, true, pReply);
+ return ERR_NONE;
+}
+
+/*
+ * Given a referenceTypeID, return a block of goodies describing the
+ * methods declared by a class.
+ */
+static JdwpError handleRT_MethodsWithGeneric(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for methods in refTypeId=0x%llx", refTypeId);
+ LOG(VERBOSE) << StringPrintf(" --> '%s'", Dbg::GetSignature(refTypeId));
+
+ Dbg::OutputAllMethods(refTypeId, true, pReply);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the immediate superclass of a class.
+ */
+static JdwpError handleCT_Superclass(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId classId = ReadRefTypeId(&buf);
+
+ RefTypeId superClassId = Dbg::GetSuperclass(classId);
+
+ expandBufAddRefTypeId(pReply, superClassId);
+
+ return ERR_NONE;
+}
+
+/*
+ * Set static class values.
+ */
+static JdwpError handleCT_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId classId = ReadRefTypeId(&buf);
+ uint32_t values = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req to set %d values in classId=%llx", values, classId);
+
+ for (uint32_t i = 0; i < values; i++) {
+ FieldId fieldId = ReadFieldId(&buf);
+ uint8_t fieldTag = Dbg::GetStaticFieldBasicTag(classId, fieldId);
+ int width = Dbg::GetTagWidth(fieldTag);
+ uint64_t value = jdwpReadValue(&buf, width);
+
+ LOG(VERBOSE) << StringPrintf(" --> field=%x tag=%c -> %lld", fieldId, fieldTag, value);
+ Dbg::SetStaticFieldValue(classId, fieldId, value, width);
+ }
+
+ 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 handleCT_InvokeMethod(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId classId = ReadRefTypeId(&buf);
+ ObjectId threadId = ReadObjectId(&buf);
+ MethodId methodId = ReadMethodId(&buf);
+
+ return finishInvoke(state, buf, dataLen, pReply, threadId, 0, classId, methodId, 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 handleCT_NewInstance(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId classId = ReadRefTypeId(&buf);
+ ObjectId threadId = ReadObjectId(&buf);
+ MethodId methodId = ReadMethodId(&buf);
+
+ LOG(VERBOSE) << "Creating instance of " << Dbg::GetClassDescriptor(classId);
+ ObjectId objectId = Dbg::CreateObject(classId);
+ if (objectId == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ return finishInvoke(state, buf, dataLen, pReply, threadId, objectId, classId, methodId, true);
+}
+
+/*
+ * Create a new array object of the requested type and length.
+ */
+static JdwpError handleAT_newInstance(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId arrayTypeId = ReadRefTypeId(&buf);
+ uint32_t length = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf("Creating array %s[%u]", Dbg::GetClassDescriptor(arrayTypeId), length);
+ ObjectId objectId = Dbg::CreateArrayObject(arrayTypeId, length);
+ if (objectId == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ expandBufAdd1(pReply, JT_ARRAY);
+ expandBufAddObjectId(pReply, objectId);
+ return ERR_NONE;
+}
+
+/*
+ * Return line number information for the method, if present.
+ */
+static JdwpError handleM_LineTable(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId refTypeId = ReadRefTypeId(&buf);
+ MethodId methodId = ReadMethodId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for line table in %s.%s", Dbg::GetClassDescriptor(refTypeId), Dbg::GetMethodName(refTypeId,methodId));
+
+ Dbg::OutputLineTable(refTypeId, methodId, pReply);
+
+ return ERR_NONE;
+}
+
+/*
+ * Pull out the LocalVariableTable goodies.
+ */
+static JdwpError handleM_VariableTableWithGeneric(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId classId = ReadRefTypeId(&buf);
+ MethodId methodId = ReadMethodId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for LocalVarTab in class=%s method=%s",
+ Dbg::GetClassDescriptor(classId),
+ Dbg::GetMethodName(classId, methodId));
+
+ /*
+ * 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(classId, methodId, true, pReply);
+ 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. threadId gets
+ * passed in here.
+ */
+static JdwpError handleOR_ReferenceType(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId objectId = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" Req for type of objectId=0x%llx", objectId);
+
+ uint8_t refTypeTag;
+ RefTypeId typeId;
+ Dbg::GetObjectType(objectId, &refTypeTag, &typeId);
+
+ expandBufAdd1(pReply, refTypeTag);
+ expandBufAddRefTypeId(pReply, typeId);
+
+ return ERR_NONE;
+}
+
+/*
+ * Get values from the fields of an object.
+ */
+static JdwpError handleOR_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId objectId = ReadObjectId(&buf);
+ uint32_t numFields = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for %d fields from objectId=0x%llx", numFields, objectId);
+
+ expandBufAdd4BE(pReply, numFields);
+
+ for (uint32_t i = 0; i < numFields; i++) {
+ FieldId fieldId = ReadFieldId(&buf);
+ Dbg::GetFieldValue(objectId, fieldId, pReply);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set values in the fields of an object.
+ */
+static JdwpError handleOR_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId objectId = ReadObjectId(&buf);
+ uint32_t numFields = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req to set %d fields in objectId=0x%llx", numFields, objectId);
+
+ for (uint32_t i = 0; i < numFields; i++) {
+ FieldId fieldId = ReadFieldId(&buf);
+
+ uint8_t fieldTag = Dbg::GetFieldBasicTag(objectId, fieldId);
+ int width = Dbg::GetTagWidth(fieldTag);
+ uint64_t value = jdwpReadValue(&buf, width);
+
+ LOG(VERBOSE) << StringPrintf(" --> fieldId=%x tag='%c'(%d) value=%lld", fieldId, fieldTag, width, value);
+
+ Dbg::SetFieldValue(objectId, fieldId, value, width);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * 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 handleOR_InvokeMethod(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId objectId = ReadObjectId(&buf);
+ ObjectId threadId = ReadObjectId(&buf);
+ RefTypeId classId = ReadRefTypeId(&buf);
+ MethodId methodId = ReadMethodId(&buf);
+
+ return finishInvoke(state, buf, dataLen, pReply, threadId, objectId, classId, methodId, false);
+}
+
+/*
+ * Disable garbage collection of the specified object.
+ */
+static JdwpError handleOR_DisableCollection(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ // this is currently a no-op
+ return ERR_NONE;
+}
+
+/*
+ * Enable garbage collection of the specified object.
+ */
+static JdwpError handleOR_EnableCollection(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ // this is currently a no-op
+ return ERR_NONE;
+}
+
+/*
+ * Determine whether an object has been garbage collected.
+ */
+static JdwpError handleOR_IsCollected(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId objectId;
+
+ objectId = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" Req IsCollected(0x%llx)", objectId);
+
+ // TODO: currently returning false; must integrate with GC
+ expandBufAdd1(pReply, 0);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the string value in a string object.
+ */
+static JdwpError handleSR_Value(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId stringObject = ReadObjectId(&buf);
+ char* str = Dbg::StringToUtf8(stringObject);
+
+ LOG(VERBOSE) << StringPrintf(" Req for str %llx --> '%s'", stringObject, str);
+
+ expandBufAddUtf8String(pReply, (uint8_t*) str);
+ free(str);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return a thread's name.
+ */
+static JdwpError handleTR_Name(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for name of thread 0x%llx", threadId);
+ char* name = Dbg::GetThreadName(threadId);
+ if (name == NULL) {
+ return ERR_INVALID_THREAD;
+ }
+ expandBufAddUtf8String(pReply, (uint8_t*) name);
+ free(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 handleTR_Suspend(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ if (threadId == Dbg::GetThreadSelfId()) {
+ LOG(INFO) << " Warning: ignoring request to suspend self";
+ return ERR_THREAD_NOT_SUSPENDED;
+ }
+ LOG(VERBOSE) << StringPrintf(" Req to suspend thread 0x%llx", threadId);
+ Dbg::SuspendThread(threadId);
+ return ERR_NONE;
+}
+
+/*
+ * Resume the specified thread.
+ */
+static JdwpError handleTR_Resume(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ if (threadId == Dbg::GetThreadSelfId()) {
+ LOG(INFO) << " Warning: ignoring request to resume self";
+ return ERR_NONE;
+ }
+ LOG(VERBOSE) << StringPrintf(" Req to resume thread 0x%llx", threadId);
+ Dbg::ResumeThread(threadId);
+ return ERR_NONE;
+}
+
+/*
+ * Return status of specified thread.
+ */
+static JdwpError handleTR_Status(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for status of thread 0x%llx", threadId);
+
+ uint32_t threadStatus;
+ uint32_t suspendStatus;
+ if (!Dbg::GetThreadStatus(threadId, &threadStatus, &suspendStatus)) {
+ return ERR_INVALID_THREAD;
+ }
+
+ LOG(VERBOSE) << " --> " << JdwpThreadStatus(threadStatus) << ", " << JdwpSuspendStatus(suspendStatus);
+
+ expandBufAdd4BE(pReply, threadStatus);
+ expandBufAdd4BE(pReply, suspendStatus);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the thread group that the specified thread is a member of.
+ */
+static JdwpError handleTR_ThreadGroup(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ /* currently not handling these */
+ ObjectId threadGroupId = Dbg::GetThreadGroup(threadId);
+ expandBufAddObjectId(pReply, threadGroupId);
+
+ return ERR_NONE;
+}
+
+/*
+ * 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 handleTR_Frames(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+ uint32_t startFrame = read4BE(&buf);
+ uint32_t length = read4BE(&buf);
+
+ if (!Dbg::ThreadExists(threadId)) {
+ return ERR_INVALID_THREAD;
+ }
+ if (!Dbg::IsSuspended(threadId)) {
+ LOG(VERBOSE) << StringPrintf(" Rejecting req for frames in running thread '%s' (%llx)", Dbg::GetThreadName(threadId), threadId);
+ return ERR_THREAD_NOT_SUSPENDED;
+ }
+
+ int frameCount = Dbg::GetThreadFrameCount(threadId);
+
+ LOG(VERBOSE) << StringPrintf(" Request for frames: threadId=%llx start=%d length=%d [count=%d]", threadId, startFrame, length, frameCount);
+ if (frameCount <= 0) {
+ return ERR_THREAD_NOT_SUSPENDED; /* == 0 means 100% native */
+ }
+ if (length == (uint32_t) -1) {
+ length = frameCount;
+ }
+ CHECK((int) startFrame >= 0 && (int) startFrame < frameCount);
+ CHECK_LE((int) (startFrame + length), frameCount);
+
+ uint32_t frames = length;
+ expandBufAdd4BE(pReply, frames);
+ for (uint32_t i = startFrame; i < (startFrame+length); i++) {
+ FrameId frameId;
+ JdwpLocation loc;
+
+ Dbg::GetThreadFrame(threadId, i, &frameId, &loc);
+
+ expandBufAdd8BE(pReply, frameId);
+ AddLocation(pReply, &loc);
+
+ LOG(VERBOSE) << StringPrintf(" Frame %d: id=%llx loc={type=%d cls=%llx mth=%x loc=%llx}", i, frameId, loc.typeTag, loc.classId, loc.methodId, loc.idx);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Returns the #of frames on the specified thread, which must be suspended.
+ */
+static JdwpError handleTR_FrameCount(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ if (!Dbg::ThreadExists(threadId)) {
+ return ERR_INVALID_THREAD;
+ }
+ if (!Dbg::IsSuspended(threadId)) {
+ LOG(VERBOSE) << StringPrintf(" Rejecting req for frames in running thread '%s' (%llx)", Dbg::GetThreadName(threadId), threadId);
+ return ERR_THREAD_NOT_SUSPENDED;
+ }
+
+ int frameCount = Dbg::GetThreadFrameCount(threadId);
+ if (frameCount < 0) {
+ return ERR_INVALID_THREAD;
+ }
+ expandBufAdd4BE(pReply, (uint32_t)frameCount);
+
+ return ERR_NONE;
+}
+
+/*
+ * Get the monitor that the thread is waiting on.
+ */
+static JdwpError handleTR_CurrentContendedMonitor(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId;
+
+ threadId = ReadObjectId(&buf);
+
+ // TODO: create an Object to represent the monitor (we're currently
+ // just using a raw Monitor struct in the VM)
+
+ return ERR_NOT_IMPLEMENTED;
+}
+
+/*
+ * Return the suspend count for the specified thread.
+ *
+ * (The thread *might* still be running -- it might not have examined
+ * its suspend count recently.)
+ */
+static JdwpError handleTR_SuspendCount(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+
+ uint32_t suspendCount = Dbg::GetThreadSuspendCount(threadId);
+ expandBufAdd4BE(pReply, suspendCount);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the name of a thread group.
+ *
+ * The Eclipse debugger recognizes "main" and "system" as special.
+ */
+static JdwpError handleTGR_Name(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadGroupId = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" Req for name of threadGroupId=0x%llx", threadGroupId);
+
+ char* name = Dbg::GetThreadGroupName(threadGroupId);
+ if (name != NULL) {
+ expandBufAddUtf8String(pReply, (uint8_t*) name);
+ } else {
+ expandBufAddUtf8String(pReply, (uint8_t*) "BAD-GROUP-ID");
+ LOG(VERBOSE) << StringPrintf("bad thread group ID");
+ }
+
+ free(name);
+
+ return ERR_NONE;
+}
+
+/*
+ * Returns the thread group -- if any -- that contains the specified
+ * thread group.
+ */
+static JdwpError handleTGR_Parent(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId groupId = ReadObjectId(&buf);
+
+ ObjectId parentGroup = Dbg::GetThreadGroupParent(groupId);
+ expandBufAddObjectId(pReply, parentGroup);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the active threads and thread groups that are part of the
+ * specified thread group.
+ */
+static JdwpError handleTGR_Children(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadGroupId = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" Req for threads in threadGroupId=0x%llx", threadGroupId);
+
+ ObjectId* pThreadIds;
+ uint32_t threadCount;
+ Dbg::GetThreadGroupThreads(threadGroupId, &pThreadIds, &threadCount);
+
+ expandBufAdd4BE(pReply, threadCount);
+
+ for (uint32_t i = 0; i < threadCount; i++) {
+ expandBufAddObjectId(pReply, pThreadIds[i]);
+ }
+ free(pThreadIds);
+
+ /*
+ * TODO: finish support for child groups
+ *
+ * For now, just show that "main" is a child of "system".
+ */
+ if (threadGroupId == Dbg::GetSystemThreadGroupId()) {
+ expandBufAdd4BE(pReply, 1);
+ expandBufAddObjectId(pReply, Dbg::GetMainThreadGroupId());
+ } else {
+ expandBufAdd4BE(pReply, 0);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the #of components in the array.
+ */
+static JdwpError handleAR_Length(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId arrayId = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" Req for length of array 0x%llx", arrayId);
+
+ uint32_t arrayLength = Dbg::GetArrayLength(arrayId);
+
+ LOG(VERBOSE) << StringPrintf(" --> %d", arrayLength);
+
+ expandBufAdd4BE(pReply, arrayLength);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the values from an array.
+ */
+static JdwpError handleAR_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId arrayId = ReadObjectId(&buf);
+ uint32_t firstIndex = read4BE(&buf);
+ uint32_t length = read4BE(&buf);
+
+ uint8_t tag = Dbg::GetArrayElementTag(arrayId);
+ LOG(VERBOSE) << StringPrintf(" Req for array values 0x%llx first=%d len=%d (elem tag=%c)", arrayId, firstIndex, length, tag);
+
+ expandBufAdd1(pReply, tag);
+ expandBufAdd4BE(pReply, length);
+
+ if (!Dbg::OutputArray(arrayId, firstIndex, length, pReply)) {
+ return ERR_INVALID_LENGTH;
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set values in an array.
+ */
+static JdwpError handleAR_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId arrayId = ReadObjectId(&buf);
+ uint32_t firstIndex = read4BE(&buf);
+ uint32_t values = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req to set array values 0x%llx first=%d count=%d", arrayId, firstIndex, values);
+
+ if (!Dbg::SetArrayElements(arrayId, firstIndex, values, buf)) {
+ return ERR_INVALID_LENGTH;
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the set of classes visible to a class loader. All classes which
+ * have the class loader as a defining or initiating loader are returned.
+ */
+static JdwpError handleCLR_VisibleClasses(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId classLoaderObject;
+ uint32_t numClasses = 0;
+ RefTypeId* classRefBuf = NULL;
+ int i;
+
+ classLoaderObject = ReadObjectId(&buf);
+
+ Dbg::GetVisibleClassList(classLoaderObject, &numClasses, &classRefBuf);
+
+ expandBufAdd4BE(pReply, numClasses);
+ for (i = 0; i < (int) numClasses; i++) {
+ uint8_t refTypeTag = Dbg::GetClassObjectType(classRefBuf[i]);
+
+ expandBufAdd1(pReply, refTypeTag);
+ expandBufAddRefTypeId(pReply, classRefBuf[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Return a newly-allocated string in which all occurrences of '.' have
+ * been changed to '/'. If we find a '/' in the original string, NULL
+ * is returned to avoid ambiguity.
+ */
+char* dvmDotToSlash(const char* str) {
+ char* newStr = strdup(str);
+ char* cp = newStr;
+
+ if (newStr == NULL) {
+ return NULL;
+ }
+
+ while (*cp != '\0') {
+ if (*cp == '/') {
+ CHECK(false);
+ return NULL;
+ }
+ if (*cp == '.') {
+ *cp = '/';
+ }
+ cp++;
+ }
+
+ return newStr;
+}
+
+/*
+ * Set an event trigger.
+ *
+ * Reply with a requestID.
+ */
+static JdwpError handleER_Set(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ const uint8_t* origBuf = buf;
+
+ uint8_t eventKind = read1(&buf);
+ uint8_t suspendPolicy = read1(&buf);
+ uint32_t modifierCount = read4BE(&buf);
+
+ LOG(VERBOSE) << " Set(kind=" << JdwpEventKind(eventKind)
+ << " suspend=" << JdwpSuspendPolicy(suspendPolicy)
+ << " mods=" << modifierCount << ")";
+
+ CHECK_LT(modifierCount, 256U); /* reasonableness check */
+
+ JdwpEvent* pEvent = EventAlloc(modifierCount);
+ pEvent->eventKind = static_cast<JdwpEventKind>(eventKind);
+ pEvent->suspendPolicy = static_cast<JdwpSuspendPolicy>(suspendPolicy);
+ pEvent->modCount = modifierCount;
+
+ /*
+ * Read modifiers. Ordering may be significant (see explanation of Count
+ * mods in JDWP doc).
+ */
+ for (uint32_t idx = 0; idx < modifierCount; idx++) {
+ uint8_t modKind = read1(&buf);
+
+ pEvent->mods[idx].modKind = modKind;
+
+ switch (modKind) {
+ case MK_COUNT: /* report once, when "--count" reaches 0 */
+ {
+ uint32_t count = read4BE(&buf);
+ LOG(VERBOSE) << " Count: " << count;
+ if (count == 0) {
+ return ERR_INVALID_COUNT;
+ }
+ pEvent->mods[idx].count.count = count;
+ }
+ break;
+ case MK_CONDITIONAL: /* conditional on expression) */
+ {
+ uint32_t exprId = read4BE(&buf);
+ LOG(VERBOSE) << " Conditional: " << exprId;
+ pEvent->mods[idx].conditional.exprId = exprId;
+ }
+ break;
+ case MK_THREAD_ONLY: /* only report events in specified thread */
+ {
+ ObjectId threadId = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" ThreadOnly: %llx", threadId);
+ pEvent->mods[idx].threadOnly.threadId = threadId;
+ }
+ break;
+ case MK_CLASS_ONLY: /* for ClassPrepare, MethodEntry */
+ {
+ RefTypeId clazzId = ReadRefTypeId(&buf);
+ LOG(VERBOSE) << StringPrintf(" ClassOnly: %llx (%s)", clazzId, Dbg::GetClassDescriptor(clazzId));
+ pEvent->mods[idx].classOnly.refTypeId = clazzId;
+ }
+ break;
+ case MK_CLASS_MATCH: /* restrict events to matching classes */
+ {
+ char* pattern;
+ size_t strLen;
+
+ pattern = readNewUtf8String(&buf, &strLen);
+ LOG(VERBOSE) << StringPrintf(" ClassMatch: '%s'", pattern);
+ /* pattern is "java.foo.*", we want "java/foo/ *" */
+ pEvent->mods[idx].classMatch.classPattern = dvmDotToSlash(pattern);
+ free(pattern);
+ }
+ break;
+ case MK_CLASS_EXCLUDE: /* restrict events to non-matching classes */
+ {
+ char* pattern;
+ size_t strLen;
+
+ pattern = readNewUtf8String(&buf, &strLen);
+ LOG(VERBOSE) << StringPrintf(" ClassExclude: '%s'", pattern);
+ pEvent->mods[idx].classExclude.classPattern = dvmDotToSlash(pattern);
+ free(pattern);
+ }
+ break;
+ case MK_LOCATION_ONLY: /* restrict certain events based on loc */
+ {
+ JdwpLocation loc;
+
+ jdwpReadLocation(&buf, &loc);
+ LOG(VERBOSE) << StringPrintf(" LocationOnly: typeTag=%d classId=%llx methodId=%x idx=%llx",
+ loc.typeTag, loc.classId, loc.methodId, loc.idx);
+ pEvent->mods[idx].locationOnly.loc = loc;
+ }
+ break;
+ case MK_EXCEPTION_ONLY: /* modifies EK_EXCEPTION events */
+ {
+ RefTypeId exceptionOrNull; /* null == all exceptions */
+ uint8_t caught, uncaught;
+
+ exceptionOrNull = ReadRefTypeId(&buf);
+ caught = read1(&buf);
+ uncaught = read1(&buf);
+ LOG(VERBOSE) << StringPrintf(" ExceptionOnly: type=%llx(%s) caught=%d uncaught=%d",
+ exceptionOrNull, (exceptionOrNull == 0) ? "null" : Dbg::GetClassDescriptor(exceptionOrNull), caught, uncaught);
+
+ pEvent->mods[idx].exceptionOnly.refTypeId = exceptionOrNull;
+ pEvent->mods[idx].exceptionOnly.caught = caught;
+ pEvent->mods[idx].exceptionOnly.uncaught = uncaught;
+ }
+ break;
+ case MK_FIELD_ONLY: /* for field access/mod events */
+ {
+ RefTypeId declaring = ReadRefTypeId(&buf);
+ FieldId fieldId = ReadFieldId(&buf);
+ LOG(VERBOSE) << StringPrintf(" FieldOnly: %llx %x", declaring, fieldId);
+ pEvent->mods[idx].fieldOnly.refTypeId = declaring;
+ pEvent->mods[idx].fieldOnly.fieldId = fieldId;
+ }
+ break;
+ case MK_STEP: /* for use with EK_SINGLE_STEP */
+ {
+ ObjectId threadId;
+ uint32_t size, depth;
+
+ threadId = ReadObjectId(&buf);
+ size = read4BE(&buf);
+ depth = read4BE(&buf);
+ LOG(VERBOSE) << StringPrintf(" Step: thread=%llx", threadId)
+ << " size=" << JdwpStepSize(size) << " depth=" << JdwpStepDepth(depth);
+
+ pEvent->mods[idx].step.threadId = threadId;
+ pEvent->mods[idx].step.size = size;
+ pEvent->mods[idx].step.depth = depth;
+ }
+ break;
+ case MK_INSTANCE_ONLY: /* report events related to a specific obj */
+ {
+ ObjectId instance = ReadObjectId(&buf);
+ LOG(VERBOSE) << StringPrintf(" InstanceOnly: %llx", instance);
+ pEvent->mods[idx].instanceOnly.objectId = instance;
+ }
+ break;
+ default:
+ LOG(WARNING) << "GLITCH: unsupported modKind=" << modKind;
+ break;
+ }
+ }
+
+ /*
+ * Make sure we consumed all data. It is possible that the remote side
+ * has sent us bad stuff, but for now we blame ourselves.
+ */
+ if (buf != origBuf + dataLen) {
+ LOG(WARNING) << "GLITCH: dataLen is " << dataLen << ", we have consumed " << (buf - origBuf);
+ }
+
+ /*
+ * We reply with an integer "requestID".
+ */
+ uint32_t requestId = NextEventSerial(state);
+ expandBufAdd4BE(pReply, requestId);
+
+ pEvent->requestId = requestId;
+
+ LOG(VERBOSE) << StringPrintf(" --> event requestId=%#x", requestId);
+
+ /* add it to the list */
+ JdwpError err = RegisterEvent(state, pEvent);
+ if (err != ERR_NONE) {
+ /* registration failed, probably because event is bogus */
+ EventFree(pEvent);
+ LOG(WARNING) << "WARNING: event request rejected";
+ }
+ return err;
+}
+
+/*
+ * Clear an event. Failure to find an event with a matching ID is a no-op
+ * and does not return an error.
+ */
+static JdwpError handleER_Clear(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ uint8_t eventKind;
+ eventKind = read1(&buf);
+ uint32_t requestId = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req to clear eventKind=%d requestId=%#x", eventKind, requestId);
+
+ UnregisterEventById(state, requestId);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the values of arguments and local variables.
+ */
+static JdwpError handleSF_GetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+ FrameId frameId = ReadFrameId(&buf);
+ uint32_t slots = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for %d slots in threadId=%llx frameId=%llx", slots, threadId, frameId);
+
+ expandBufAdd4BE(pReply, slots); /* "int values" */
+ for (uint32_t i = 0; i < slots; i++) {
+ uint32_t slot = read4BE(&buf);
+ uint8_t reqSigByte = read1(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" --> slot %d '%c'", slot, reqSigByte);
+
+ int width = Dbg::GetTagWidth(reqSigByte);
+ uint8_t* ptr = expandBufAddSpace(pReply, width+1);
+ Dbg::GetLocalValue(threadId, frameId, slot, reqSigByte, ptr, width);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set the values of arguments and local variables.
+ */
+static JdwpError handleSF_SetValues(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+ FrameId frameId = ReadFrameId(&buf);
+ uint32_t slots = read4BE(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req to set %d slots in threadId=%llx frameId=%llx", slots, threadId, frameId);
+
+ for (uint32_t i = 0; i < slots; i++) {
+ uint32_t slot = read4BE(&buf);
+ uint8_t sigByte = read1(&buf);
+ int width = Dbg::GetTagWidth(sigByte);
+ uint64_t value = jdwpReadValue(&buf, width);
+
+ LOG(VERBOSE) << StringPrintf(" --> slot %d '%c' %llx", slot, sigByte, value);
+ Dbg::SetLocalValue(threadId, frameId, slot, sigByte, value, width);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Returns the value of "this" for the specified frame.
+ */
+static JdwpError handleSF_ThisObject(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ ObjectId threadId = ReadObjectId(&buf);
+ FrameId frameId = ReadFrameId(&buf);
+
+ ObjectId objectId;
+ if (!Dbg::GetThisObject(threadId, frameId, &objectId)) {
+ return ERR_INVALID_FRAMEID;
+ }
+
+ uint8_t objectTag = Dbg::GetObjectTag(objectId);
+ LOG(VERBOSE) << StringPrintf(" Req for 'this' in thread=%llx frame=%llx --> %llx %s '%c'", threadId, frameId, objectId, Dbg::GetObjectTypeName(objectId), (char)objectTag);
+
+ expandBufAdd1(pReply, objectTag);
+ expandBufAddObjectId(pReply, objectId);
+
+ return ERR_NONE;
+}
+
+/*
+ * 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 handleCOR_ReflectedType(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ RefTypeId classObjectId = ReadRefTypeId(&buf);
+
+ LOG(VERBOSE) << StringPrintf(" Req for refTypeId for class=%llx (%s)", classObjectId, Dbg::GetClassDescriptor(classObjectId));
+
+ /* just hand the type back to them */
+ if (Dbg::IsInterface(classObjectId)) {
+ expandBufAdd1(pReply, TT_INTERFACE);
+ } else {
+ expandBufAdd1(pReply, TT_CLASS);
+ }
+ expandBufAddRefTypeId(pReply, classObjectId);
+
+ return ERR_NONE;
+}
+
+/*
+ * Handle a DDM packet with a single chunk in it.
+ */
+static JdwpError handleDDM_Chunk(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ uint8_t* replyBuf = NULL;
+ int replyLen = -1;
+
+ LOG(VERBOSE) << StringPrintf(" Handling DDM packet (%.4s)", buf);
+
+ /*
+ * On first DDM packet, notify all handlers that DDM is running.
+ */
+ if (!state->ddmActive) {
+ state->ddmActive = true;
+ Dbg::DdmConnected();
+ }
+
+ /*
+ * If they want to send something back, we copy it into the buffer.
+ * A no-copy approach would be nicer.
+ *
+ * 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.
+ */
+ if (Dbg::DdmHandlePacket(buf, dataLen, &replyBuf, &replyLen)) {
+ CHECK(replyLen > 0 && replyLen < 1*1024*1024);
+ memcpy(expandBufAddSpace(pReply, replyLen), replyBuf, replyLen);
+ free(replyBuf);
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Handler map decl.
+ */
+typedef JdwpError (*JdwpRequestHandler)(JdwpState* state, const uint8_t* buf, int dataLen, ExpandBuf* reply);
+
+struct JdwpHandlerMap {
+ uint8_t cmdSet;
+ uint8_t cmd;
+ JdwpRequestHandler func;
+ const char* descr;
+};
+
+/*
+ * 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 gHandlerMap[] = {
+ /* VirtualMachine command set (1) */
+ { 1, 1, handleVM_Version, "VirtualMachine.Version" },
+ { 1, 2, handleVM_ClassesBySignature, "VirtualMachine.ClassesBySignature" },
+ //1, 3, VirtualMachine.AllClasses
+ { 1, 4, handleVM_AllThreads, "VirtualMachine.AllThreads" },
+ { 1, 5, handleVM_TopLevelThreadGroups, "VirtualMachine.TopLevelThreadGroups" },
+ { 1, 6, handleVM_Dispose, "VirtualMachine.Dispose" },
+ { 1, 7, handleVM_IDSizes, "VirtualMachine.IDSizes" },
+ { 1, 8, handleVM_Suspend, "VirtualMachine.Suspend" },
+ { 1, 9, handleVM_Resume, "VirtualMachine.Resume" },
+ { 1, 10, handleVM_Exit, "VirtualMachine.Exit" },
+ { 1, 11, handleVM_CreateString, "VirtualMachine.CreateString" },
+ { 1, 12, handleVM_Capabilities, "VirtualMachine.Capabilities" },
+ { 1, 13, handleVM_ClassPaths, "VirtualMachine.ClassPaths" },
+ { 1, 14, HandleVM_DisposeObjects, "VirtualMachine.DisposeObjects" },
+ //1, 15, HoldEvents
+ //1, 16, ReleaseEvents
+ { 1, 17, handleVM_CapabilitiesNew, "VirtualMachine.CapabilitiesNew" },
+ //1, 18, RedefineClasses
+ //1, 19, SetDefaultStratum
+ { 1, 20, handleVM_AllClassesWithGeneric, "VirtualMachine.AllClassesWithGeneric"},
+ //1, 21, InstanceCounts
+
+ /* ReferenceType command set (2) */
+ { 2, 1, handleRT_Signature, "ReferenceType.Signature" },
+ { 2, 2, handleRT_ClassLoader, "ReferenceType.ClassLoader" },
+ { 2, 3, handleRT_Modifiers, "ReferenceType.Modifiers" },
+ //2, 4, Fields
+ //2, 5, Methods
+ { 2, 6, handleRT_GetValues, "ReferenceType.GetValues" },
+ { 2, 7, handleRT_SourceFile, "ReferenceType.SourceFile" },
+ //2, 8, NestedTypes
+ { 2, 9, handleRT_Status, "ReferenceType.Status" },
+ { 2, 10, handleRT_Interfaces, "ReferenceType.Interfaces" },
+ { 2, 11, handleRT_ClassObject, "ReferenceType.ClassObject" },
+ { 2, 12, handleRT_SourceDebugExtension, "ReferenceType.SourceDebugExtension" },
+ { 2, 13, handleRT_SignatureWithGeneric, "ReferenceType.SignatureWithGeneric" },
+ { 2, 14, handleRT_FieldsWithGeneric, "ReferenceType.FieldsWithGeneric" },
+ { 2, 15, handleRT_MethodsWithGeneric, "ReferenceType.MethodsWithGeneric" },
+ //2, 16, Instances
+ //2, 17, ClassFileVersion
+ //2, 18, ConstantPool
+
+ /* ClassType command set (3) */
+ { 3, 1, handleCT_Superclass, "ClassType.Superclass" },
+ { 3, 2, handleCT_SetValues, "ClassType.SetValues" },
+ { 3, 3, handleCT_InvokeMethod, "ClassType.InvokeMethod" },
+ { 3, 4, handleCT_NewInstance, "ClassType.NewInstance" },
+
+ /* ArrayType command set (4) */
+ { 4, 1, handleAT_newInstance, "ArrayType.NewInstance" },
+
+ /* InterfaceType command set (5) */
+
+ /* Method command set (6) */
+ { 6, 1, handleM_LineTable, "Method.LineTable" },
+ //6, 2, VariableTable
+ //6, 3, Bytecodes
+ //6, 4, IsObsolete
+ { 6, 5, handleM_VariableTableWithGeneric, "Method.VariableTableWithGeneric" },
+
+ /* Field command set (8) */
+
+ /* ObjectReference command set (9) */
+ { 9, 1, handleOR_ReferenceType, "ObjectReference.ReferenceType" },
+ { 9, 2, handleOR_GetValues, "ObjectReference.GetValues" },
+ { 9, 3, handleOR_SetValues, "ObjectReference.SetValues" },
+ //9, 4, (not defined)
+ //9, 5, MonitorInfo
+ { 9, 6, handleOR_InvokeMethod, "ObjectReference.InvokeMethod" },
+ { 9, 7, handleOR_DisableCollection, "ObjectReference.DisableCollection" },
+ { 9, 8, handleOR_EnableCollection, "ObjectReference.EnableCollection" },
+ { 9, 9, handleOR_IsCollected, "ObjectReference.IsCollected" },
+ //9, 10, ReferringObjects
+
+ /* StringReference command set (10) */
+ { 10, 1, handleSR_Value, "StringReference.Value" },
+
+ /* ThreadReference command set (11) */
+ { 11, 1, handleTR_Name, "ThreadReference.Name" },
+ { 11, 2, handleTR_Suspend, "ThreadReference.Suspend" },
+ { 11, 3, handleTR_Resume, "ThreadReference.Resume" },
+ { 11, 4, handleTR_Status, "ThreadReference.Status" },
+ { 11, 5, handleTR_ThreadGroup, "ThreadReference.ThreadGroup" },
+ { 11, 6, handleTR_Frames, "ThreadReference.Frames" },
+ { 11, 7, handleTR_FrameCount, "ThreadReference.FrameCount" },
+ //11, 8, OwnedMonitors
+ { 11, 9, handleTR_CurrentContendedMonitor, "ThreadReference.CurrentContendedMonitor" },
+ //11, 10, Stop
+ //11, 11, Interrupt
+ { 11, 12, handleTR_SuspendCount, "ThreadReference.SuspendCount" },
+ //11, 13, OwnedMonitorsStackDepthInfo
+ //11, 14, ForceEarlyReturn
+
+ /* ThreadGroupReference command set (12) */
+ { 12, 1, handleTGR_Name, "ThreadGroupReference.Name" },
+ { 12, 2, handleTGR_Parent, "ThreadGroupReference.Parent" },
+ { 12, 3, handleTGR_Children, "ThreadGroupReference.Children" },
+
+ /* ArrayReference command set (13) */
+ { 13, 1, handleAR_Length, "ArrayReference.Length" },
+ { 13, 2, handleAR_GetValues, "ArrayReference.GetValues" },
+ { 13, 3, handleAR_SetValues, "ArrayReference.SetValues" },
+
+ /* ClassLoaderReference command set (14) */
+ { 14, 1, handleCLR_VisibleClasses, "ClassLoaderReference.VisibleClasses" },
+
+ /* EventRequest command set (15) */
+ { 15, 1, handleER_Set, "EventRequest.Set" },
+ { 15, 2, handleER_Clear, "EventRequest.Clear" },
+ //15, 3, ClearAllBreakpoints
+
+ /* StackFrame command set (16) */
+ { 16, 1, handleSF_GetValues, "StackFrame.GetValues" },
+ { 16, 2, handleSF_SetValues, "StackFrame.SetValues" },
+ { 16, 3, handleSF_ThisObject, "StackFrame.ThisObject" },
+ //16, 4, PopFrames
+
+ /* ClassObjectReference command set (17) */
+ { 17, 1, handleCOR_ReflectedType,"ClassObjectReference.ReflectedType" },
+
+ /* Event command set (64) */
+ //64, 100, Composite <-- sent from VM to debugger, never received by VM
+
+ { 199, 1, handleDDM_Chunk, "DDM.Chunk" },
+};
+
+/*
+ * Process a request from the debugger.
+ *
+ * On entry, the JDWP thread is in VMWAIT.
+ */
+void ProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader, const uint8_t* buf, int dataLen, ExpandBuf* pReply) {
+ JdwpError result = ERR_NONE;
+ int i, respLen;
+
+ if (pHeader->cmdSet != 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::Active();
+ QuasiAtomicSwap64(0, &state->lastActivityWhen);
+ }
+
+ /*
+ * 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(state, 0);
+
+ /*
+ * Tell the VM that we're running and shouldn't be interrupted by GC.
+ * Do this after anything that can stall indefinitely.
+ */
+ Dbg::ThreadRunning();
+
+ expandBufAddSpace(pReply, kJDWPHeaderLen);
+
+ for (i = 0; i < (int) arraysize(gHandlerMap); i++) {
+ if (gHandlerMap[i].cmdSet == pHeader->cmdSet && gHandlerMap[i].cmd == pHeader->cmd) {
+ LOG(VERBOSE) << StringPrintf("REQ: %s (cmd=%d/%d dataLen=%d id=0x%06x)", gHandlerMap[i].descr, pHeader->cmdSet, pHeader->cmd, dataLen, pHeader->id);
+ result = (*gHandlerMap[i].func)(state, buf, dataLen, pReply);
+ break;
+ }
+ }
+ if (i == arraysize(gHandlerMap)) {
+ LOG(ERROR) << StringPrintf("REQ: UNSUPPORTED (cmd=%d/%d dataLen=%d id=0x%06x)", pHeader->cmdSet, pHeader->cmd, dataLen, pHeader->id);
+ if (dataLen > 0) {
+ HexDump(buf, dataLen);
+ }
+ LOG(FATAL) << "command not implemented"; // make it *really* obvious
+ 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, pHeader->id);
+ set1(replyBuf + 8, kJDWPFlagReply);
+ set2BE(replyBuf + 9, result);
+ if (result == ERR_NONE) {
+ set4BE(replyBuf + 0, expandBufGetLength(pReply));
+ } else {
+ set4BE(replyBuf + 0, kJDWPHeaderLen);
+ }
+
+ respLen = expandBufGetLength(pReply) - kJDWPHeaderLen;
+ if (false) {
+ LOG(INFO) << "reply: dataLen=" << respLen << " err=" << result << (result != ERR_NONE ? " **FAILED**" : "");
+ if (respLen > 0) {
+ HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen);
+ }
+ }
+
+ /*
+ * Update last-activity timestamp. We really only need this during
+ * the initial setup. Only update if this is a non-DDMS packet.
+ */
+ if (pHeader->cmdSet != kJDWPDdmCmdSet) {
+ QuasiAtomicSwap64(GetNowMsec(), &state->lastActivityWhen);
+ }
+
+ /* tell the VM that GC is okay again */
+ Dbg::ThreadWaiting();
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/jdwp/jdwp_handler.h b/src/jdwp/jdwp_handler.h
new file mode 100644
index 0000000..6fa75d7
--- /dev/null
+++ b/src/jdwp/jdwp_handler.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+/*
+ * Handle requests.
+ */
+#ifndef ART_JDWP_JDWPHANDLER_H_
+#define ART_JDWP_JDWPHANDLER_H_
+
+#include "jdwp_expand_buf.h"
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * JDWP message header for a request.
+ */
+struct JdwpReqHeader {
+ uint32_t length;
+ uint32_t id;
+ uint8_t cmdSet;
+ uint8_t cmd;
+};
+
+/*
+ * Process a request from the debugger.
+ *
+ * "buf" points past the header, to the content of the message. "dataLen"
+ * can therefore be zero.
+ */
+void ProcessRequest(JdwpState* state, const JdwpReqHeader* pHeader,
+ const uint8_t* buf, int dataLen, ExpandBuf* pReply);
+
+/* helper function */
+void AddLocation(ExpandBuf* pReply, const JdwpLocation* pLoc);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPHANDLER_H_
diff --git a/src/jdwp/jdwp_main.cc b/src/jdwp/jdwp_main.cc
new file mode 100644
index 0000000..f2ff937
--- /dev/null
+++ b/src/jdwp/jdwp_main.cc
@@ -0,0 +1,508 @@
+/*
+ * 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.
+ */
+
+/*
+ * JDWP initialization.
+ */
+
+#include "atomic.h"
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "logging.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <time.h>
+#include <errno.h>
+
+namespace art {
+
+namespace JDWP {
+
+static void* jdwpThreadStart(void* arg);
+
+/*
+ * JdwpNetStateBase class implementation
+ */
+JdwpNetStateBase::JdwpNetStateBase() : socket_lock_("JdwpNetStateBase lock") {
+ clientSock = -1;
+}
+
+/*
+ * Write a packet. Grabs a mutex to assure atomicity.
+ */
+ssize_t JdwpNetStateBase::writePacket(ExpandBuf* pReply) {
+ MutexLock mu(socket_lock_);
+ return write(clientSock, expandBufGetBuffer(pReply), expandBufGetLength(pReply));
+}
+
+/*
+ * Write a buffered packet. Grabs a mutex to assure atomicity.
+ */
+ssize_t JdwpNetStateBase::writeBufferedPacket(const iovec* iov, int iovcnt) {
+ MutexLock mu(socket_lock_);
+ return writev(clientSock, iov, iovcnt);
+}
+
+bool NetStartup(JdwpState* state, const JdwpStartupParams* pParams) {
+ return (*state->transport->startup)(state, pParams);
+}
+
+bool AcceptConnection(JdwpState* state) {
+ return (*state->transport->accept)(state);
+}
+
+bool EstablishConnection(JdwpState* state) {
+ return (*state->transport->establish)(state);
+}
+
+void CloseConnection(JdwpState* state) {
+ (*state->transport->close)(state);
+}
+
+void NetShutdown(JdwpState* state) {
+ (*state->transport->shutdown)(state);
+}
+
+void NetFree(JdwpState* state) {
+ (*state->transport->free)(state);
+}
+
+bool IsTransportDefined(JdwpState* state) {
+ return state != NULL && state->transport != NULL;
+}
+
+bool JdwpIsConnected(JdwpState* state) {
+ return state != NULL && (*state->transport->isConnected)(state);
+}
+
+bool AwaitingHandshake(JdwpState* state) {
+ return (*state->transport->awaitingHandshake)(state);
+}
+
+bool ProcessIncoming(JdwpState* state) {
+ return (*state->transport->processIncoming)(state);
+}
+
+bool SendRequest(JdwpState* state, ExpandBuf* pReq) {
+ return (*state->transport->sendRequest)(state, pReq);
+}
+
+static void CreateJdwpThread(JdwpState* state) {
+ CHECK_PTHREAD_CALL(pthread_create, (&state->debugThreadHandle, NULL, jdwpThreadStart, state), "JDWP thread");
+}
+
+JdwpState::JdwpState()
+ : thread_start_lock_("JDWP thread start lock"),
+ thread_start_cond_("JDWP thread start condition variable"),
+ debug_thread_started_(false),
+ debugThreadId(0),
+ run(false),
+ transport(NULL),
+ netState(NULL),
+ attach_lock_("JDWP attach lock"),
+ attach_cond_("JDWP attach condition variable"),
+ lastActivityWhen(0),
+ requestSerial(0x10000000),
+ eventSerial(0x20000000),
+ serial_lock_("JDWP serial lock"),
+ numEvents(0),
+ eventList(NULL),
+ event_lock_("JDWP event lock"),
+ event_thread_lock_("JDWP event thread lock"),
+ event_thread_cond_("JDWP event thread condition variable"),
+ eventThreadId(0),
+ ddmActive(false) {
+}
+
+/*
+ * Initialize JDWP.
+ *
+ * Does not return until JDWP thread is running, but may return before
+ * the thread is accepting network connections.
+ */
+JdwpState* JdwpStartup(const JdwpStartupParams* pParams) {
+ /* comment this out when debugging JDWP itself */
+ //android_setMinPriority(LOG_TAG, ANDROID_LOG_DEBUG);
+
+ JdwpState* state = new JdwpState;
+
+ state->params = *pParams;
+
+ switch (pParams->transport) {
+ case kJdwpTransportSocket:
+ // LOGD("prepping for JDWP over TCP");
+ state->transport = SocketTransport();
+ break;
+#ifdef HAVE_ANDROID_OS
+ case kJdwpTransportAndroidAdb:
+ // LOGD("prepping for JDWP over ADB");
+ state->transport = AndroidAdbTransport();
+ break;
+#endif
+ default:
+ LOG(FATAL) << "Unknown transport: " << pParams->transport;
+ }
+
+ if (!NetStartup(state, pParams)) {
+ goto fail;
+ }
+
+ /*
+ * Grab a mutex or two before starting the thread. This ensures they
+ * won't signal the cond var before we're waiting.
+ */
+ state->thread_start_lock_.Lock();
+ if (pParams->suspend) {
+ state->attach_lock_.Lock();
+ }
+
+ /*
+ * 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.
+ */
+ CreateJdwpThread(state);
+
+ /*
+ * Wait until the thread finishes basic initialization.
+ * TODO: cond vars should be waited upon in a loop
+ */
+ state->thread_start_cond_.Wait(state->thread_start_lock_);
+ state->thread_start_lock_.Unlock();
+
+ /*
+ * For suspend=y, wait for the debugger to connect to us or for us to
+ * connect to the debugger.
+ *
+ * The JDWP thread will signal us when it connects successfully or
+ * times out (for timeout=xxx), so we have to check to see what happened
+ * when we wake up.
+ */
+ if (pParams->suspend) {
+ {
+ ScopedThreadStateChange tsc(Thread::Current(), Thread::kVmWait);
+
+ state->attach_cond_.Wait(state->attach_lock_);
+ state->attach_lock_.Unlock();
+ }
+
+ if (!JdwpIsActive(state)) {
+ LOG(ERROR) << "JDWP connection failed";
+ goto fail;
+ }
+
+ LOG(INFO) << "JDWP connected";
+
+ /*
+ * Ordinarily we would pause briefly to allow the debugger to set
+ * breakpoints and so on, but for "suspend=y" the VM init code will
+ * pause the VM when it sends the VM_START message.
+ */
+ }
+
+ return state;
+
+fail:
+ JdwpShutdown(state); // frees state
+ return NULL;
+}
+
+/*
+ * Reset all session-related state. There should not be an active connection
+ * to the client at this point. The rest of the VM still thinks there is
+ * a debugger attached.
+ *
+ * This includes freeing up the debugger event list.
+ */
+void ResetState(JdwpState* state) {
+ /* could reset the serial numbers, but no need to */
+
+ UnregisterAll(state);
+ CHECK(state->eventList == NULL);
+
+ /*
+ * Should not have one of these in progress. If the debugger went away
+ * mid-request, though, we could see this.
+ */
+ if (state->eventThreadId != 0) {
+ LOG(WARNING) << "resetting state while event in progress";
+ DCHECK(false);
+ }
+}
+
+/*
+ * Tell the JDWP thread to shut down. Frees "state".
+ */
+void JdwpShutdown(JdwpState* state) {
+ void* threadReturn;
+
+ if (state == NULL) {
+ return;
+ }
+
+ if (IsTransportDefined(state)) {
+ if (JdwpIsConnected(state)) {
+ PostVMDeath(state);
+ }
+
+ /*
+ * Close down the network to inspire the thread to halt.
+ */
+ LOG(DEBUG) << "JDWP shutting down net...";
+ NetShutdown(state);
+
+ if (state->debug_thread_started_) {
+ state->run = false;
+ if (pthread_join(state->debugThreadHandle, &threadReturn) != 0) {
+ LOG(WARNING) << "JDWP thread join failed";
+ }
+ }
+
+ LOG(DEBUG) << "JDWP freeing netstate...";
+ NetFree(state);
+ state->netState = NULL;
+ }
+ CHECK(state->netState == NULL);
+
+ ResetState(state);
+ free(state);
+}
+
+/*
+ * Are we talking to a debugger?
+ */
+bool JdwpIsActive(JdwpState* state) {
+ return JdwpIsConnected(state);
+}
+
+/*
+ * Entry point for JDWP thread. The thread was created through the VM
+ * mechanisms, so there is a java/lang/Thread associated with us.
+ */
+static void* jdwpThreadStart(void* arg) {
+ JdwpState* state = reinterpret_cast<JdwpState*>(arg);
+ CHECK(state != NULL);
+
+ Runtime* runtime = Runtime::Current();
+ runtime->AttachCurrentThread("JDWP", true);
+
+ LOG(VERBOSE) << "JDWP: thread running";
+
+ /*
+ * Finish initializing "state", then notify the creating thread that
+ * we're running.
+ */
+ state->debugThreadHandle = pthread_self();
+ state->run = true;
+ android_atomic_release_store(true, &state->debug_thread_started_);
+
+ state->thread_start_lock_.Lock();
+ state->thread_start_cond_.Wait(state->thread_start_lock_);
+ state->thread_start_lock_.Unlock();
+
+ /* set the thread state to VMWAIT so GCs don't wait for us */
+ Dbg::ThreadWaiting();
+
+ /*
+ * Loop forever if we're in server mode, processing connections. In
+ * non-server mode, we bail out of the thread when the debugger drops
+ * us.
+ *
+ * We broadcast a notification when a debugger attaches, after we
+ * successfully process the handshake.
+ */
+ while (state->run) {
+ bool first;
+
+ if (state->params.server) {
+ /*
+ * Block forever, waiting for a connection. To support the
+ * "timeout=xxx" option we'll need to tweak this.
+ */
+ if (!AcceptConnection(state)) {
+ break;
+ }
+ } else {
+ /*
+ * If we're not acting as a server, we need to connect out to the
+ * debugger. To support the "timeout=xxx" option we need to
+ * have a timeout if the handshake reply isn't received in a
+ * reasonable amount of time.
+ */
+ if (!EstablishConnection(state)) {
+ /* wake anybody who was waiting for us to succeed */
+ MutexLock mu(state->attach_lock_);
+ state->attach_cond_.Broadcast();
+ break;
+ }
+ }
+
+ /* prep debug code to handle the new connection */
+ Dbg::Connected();
+
+ /* process requests until the debugger drops */
+ first = true;
+ while (true) {
+ // sanity check -- shouldn't happen?
+ if (Thread::Current()->GetState() != Thread::kVmWait) {
+ LOG(ERROR) << "JDWP thread no longer in VMWAIT (now " << Thread::Current()->GetState() << "); resetting";
+ Dbg::ThreadWaiting();
+ }
+
+ if (!ProcessIncoming(state)) {
+ /* blocking read */
+ break;
+ }
+
+ if (first && !AwaitingHandshake(state)) {
+ /* handshake worked, tell the interpreter that we're active */
+ first = false;
+
+ /* set thread ID; requires object registry to be active */
+ state->debugThreadId = Dbg::GetThreadSelfId();
+
+ /* wake anybody who's waiting for us */
+ MutexLock mu(state->attach_lock_);
+ state->attach_cond_.Broadcast();
+ }
+ }
+
+ CloseConnection(state);
+
+ if (state->ddmActive) {
+ state->ddmActive = false;
+
+ /* broadcast the disconnect; must be in RUNNING state */
+ Dbg::ThreadRunning();
+ Dbg::DdmDisconnected();
+ Dbg::ThreadWaiting();
+ }
+
+ /* release session state, e.g. remove breakpoint instructions */
+ ResetState(state);
+
+ /* tell the interpreter that the debugger is no longer around */
+ Dbg::Disconnected();
+
+ /* if we had threads suspended, resume them now */
+ Dbg::UndoDebuggerSuspensions();
+
+ /* if we connected out, this was a one-shot deal */
+ if (!state->params.server) {
+ state->run = false;
+ }
+ }
+
+ /* back to running, for thread shutdown */
+ Dbg::ThreadRunning();
+
+ LOG(VERBOSE) << "JDWP: thread exiting";
+ return NULL;
+}
+
+
+/*
+ * Return the thread handle, or (pthread_t)0 if the debugger isn't running.
+ */
+pthread_t GetDebugThread(JdwpState* state) {
+ if (state == NULL) {
+ return 0;
+ }
+ return state->debugThreadHandle;
+}
+
+/*
+ * Support routines for waitForDebugger().
+ *
+ * We can't have a trivial "waitForDebugger" function that returns the
+ * instant the debugger connects, because we run the risk of executing code
+ * before the debugger has had a chance to configure breakpoints or issue
+ * suspend calls. It would be nice to just sit in the suspended state, but
+ * most debuggers don't expect any threads to be suspended when they attach.
+ *
+ * There's no JDWP event we can post to tell the debugger, "we've stopped,
+ * and we like it that way". We could send a fake breakpoint, which should
+ * cause the debugger to immediately send a resume, but the debugger might
+ * send the resume immediately or might throw an exception of its own upon
+ * receiving a breakpoint event that it didn't ask for.
+ *
+ * What we really want is a "wait until the debugger is done configuring
+ * stuff" event. We can approximate this with a "wait until the debugger
+ * has been idle for a brief period".
+ */
+
+/*
+ * Get a notion of the current time, in milliseconds.
+ */
+int64_t GetNowMsec() {
+#ifdef HAVE_POSIX_CLOCKS
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_sec * 1000LL + now.tv_nsec / 1000000LL;
+#else
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ return now.tv_sec * 1000LL + now.tv_usec / 1000LL;
+#endif
+}
+
+/*
+ * Return the time, in milliseconds, since the last debugger activity.
+ *
+ * Returns -1 if no debugger is attached, or 0 if we're in the middle of
+ * processing a debugger request.
+ */
+int64_t LastDebuggerActivity(JdwpState* state) {
+ if (!Dbg::IsDebuggerConnected()) {
+ LOG(DEBUG) << "no active debugger";
+ return -1;
+ }
+
+ int64_t last = QuasiAtomicRead64(&state->lastActivityWhen);
+
+ /* initializing or in the middle of something? */
+ if (last == 0) {
+ LOG(VERBOSE) << "+++ last=busy";
+ return 0;
+ }
+
+ /* now get the current time */
+ int64_t now = GetNowMsec();
+ CHECK_GT(now, last);
+
+ LOG(VERBOSE) << "+++ debugger interval=" << (now - last);
+ return now - last;
+}
+
+static const char* kTransportNames[] = {
+ "Unknown",
+ "Socket",
+ "AndroidAdb",
+};
+std::ostream& operator<<(std::ostream& os, const JdwpTransportType& value) {
+ int32_t int_value = static_cast<int32_t>(value);
+ if (value >= kJdwpTransportUnknown && value <= kJdwpTransportAndroidAdb) {
+ os << kTransportNames[int_value];
+ } else {
+ os << "JdwpTransportType[" << int_value << "]";
+ }
+ return os;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/jdwp/jdwp_priv.h b/src/jdwp/jdwp_priv.h
new file mode 100644
index 0000000..60169dd
--- /dev/null
+++ b/src/jdwp/jdwp_priv.h
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+/*
+ * JDWP internal interfaces.
+ */
+#ifndef ART_JDWP_JDWPPRIV_H_
+#define ART_JDWP_JDWPPRIV_H_
+
+#define LOG_TAG "jdwp"
+
+#include "debugger.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_event.h"
+#include "../mutex.h" // TODO: fix our include path!
+
+#include <pthread.h>
+#include <sys/uio.h>
+
+/*
+ * JDWP constants.
+ */
+#define kJDWPHeaderLen 11
+#define kJDWPFlagReply 0x80
+
+/* DDM support */
+#define kJDWPDdmCmdSet 199 /* 0xc7, or 'G'+128 */
+#define kJDWPDdmCmd 1
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * Transport-specific network status.
+ */
+struct JdwpNetState;
+struct JdwpState;
+
+/*
+ * Transport functions.
+ */
+struct JdwpTransport {
+ bool (*startup)(JdwpState* state, const JdwpStartupParams* pParams);
+ bool (*accept)(JdwpState* state);
+ bool (*establish)(JdwpState* state);
+ void (*close)(JdwpState* state);
+ void (*shutdown)(JdwpState* state);
+ void (*free)(JdwpState* state);
+ bool (*isConnected)(JdwpState* state);
+ bool (*awaitingHandshake)(JdwpState* state);
+ bool (*processIncoming)(JdwpState* state);
+ bool (*sendRequest)(JdwpState* state, ExpandBuf* pReq);
+ bool (*sendBufferedRequest)(JdwpState* state, const iovec* iov, int iovcnt);
+};
+
+const JdwpTransport* SocketTransport();
+const JdwpTransport* AndroidAdbTransport();
+
+
+/*
+ * State for JDWP functions.
+ */
+struct JdwpState {
+ JdwpState();
+
+ JdwpStartupParams params;
+
+ /* wait for creation of the JDWP thread */
+ Mutex thread_start_lock_;
+ ConditionVariable thread_start_cond_;
+
+ volatile int32_t debug_thread_started_;
+ pthread_t debugThreadHandle;
+ ObjectId debugThreadId;
+ bool run;
+
+ const JdwpTransport* transport;
+ JdwpNetState* netState;
+
+ /* for wait-for-debugger */
+ Mutex attach_lock_;
+ ConditionVariable attach_cond_;
+
+ /* time of last debugger activity, in milliseconds */
+ int64_t lastActivityWhen;
+
+ /* global counters and a mutex to protect them */
+ uint32_t requestSerial;
+ uint32_t eventSerial;
+ Mutex serial_lock_;
+
+ /*
+ * Events requested by the debugger (breakpoints, class prep, etc).
+ */
+ int numEvents; /* #of elements in eventList */
+ JdwpEvent* eventList; /* linked list of events */
+ Mutex event_lock_; /* guards numEvents/eventList */
+
+ /*
+ * Synchronize suspension of event thread (to avoid receiving "resume"
+ * events before the thread has finished suspending itself).
+ */
+ Mutex event_thread_lock_;
+ ConditionVariable event_thread_cond_;
+ ObjectId eventThreadId;
+
+ /*
+ * DDM support.
+ */
+ bool ddmActive;
+};
+
+/*
+ * Base class for JdwpNetState
+ */
+class JdwpNetStateBase {
+public:
+ int clientSock; /* active connection to debugger */
+
+ JdwpNetStateBase();
+ ssize_t writePacket(ExpandBuf* pReply);
+ ssize_t writeBufferedPacket(const iovec* iov, int iovcnt);
+
+private:
+ Mutex socket_lock_;
+};
+
+
+/* reset all session-specific data */
+void ResetState(JdwpState* state);
+
+/* atomic ops to get next serial number */
+uint32_t NextRequestSerial(JdwpState* state);
+uint32_t NextEventSerial(JdwpState* state);
+
+/* get current time, in msec */
+int64_t GetNowMsec();
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPPRIV_H_
diff --git a/src/jdwp/jdwp_socket.cc b/src/jdwp/jdwp_socket.cc
new file mode 100644
index 0000000..b4062d0
--- /dev/null
+++ b/src/jdwp/jdwp_socket.cc
@@ -0,0 +1,883 @@
+/*
+ * 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.
+ */
+/*
+ * JDWP TCP socket network code.
+ */
+#include "jdwp/jdwp_priv.h"
+#include "jdwp/jdwp_handler.h"
+#include "logging.h"
+#include "stringprintf.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#define kBasePort 8000
+#define kMaxPort 8040
+
+#define kInputBufferSize 8192
+
+#define kMagicHandshake "JDWP-Handshake"
+#define kMagicHandshakeLen (sizeof(kMagicHandshake)-1)
+
+namespace art {
+
+namespace JDWP {
+
+// fwd
+static void netShutdown(JdwpNetState* state);
+static void netFree(JdwpNetState* state);
+
+/*
+ * JDWP network state.
+ *
+ * We only talk to one debugger at a time.
+ */
+struct JdwpNetState : public JdwpNetStateBase {
+ short listenPort;
+ int listenSock; /* listen for connection from debugger */
+ int wakePipe[2]; /* break out of select */
+
+ struct in_addr remoteAddr;
+ unsigned short remotePort;
+
+ bool awaitingHandshake; /* waiting for "JDWP-Handshake" */
+
+ /* pending data from the network; would be more efficient as circular buf */
+ unsigned char inputBuffer[kInputBufferSize];
+ int inputCount;
+
+ JdwpNetState()
+ {
+ listenPort = 0;
+ listenSock = -1;
+ wakePipe[0] = -1;
+ wakePipe[1] = -1;
+
+ awaitingHandshake = false;
+
+ inputCount = 0;
+ }
+};
+
+static JdwpNetState* netStartup(short port);
+
+/*
+ * Set up some stuff for transport=dt_socket.
+ */
+static bool prepareSocket(JdwpState* state, const JdwpStartupParams* pParams) {
+ unsigned short port;
+
+ if (pParams->server) {
+ if (pParams->port != 0) {
+ /* try only the specified port */
+ port = pParams->port;
+ state->netState = netStartup(port);
+ } else {
+ /* scan through a range of ports, binding to the first available */
+ for (port = kBasePort; port <= kMaxPort; port++) {
+ state->netState = netStartup(port);
+ if (state->netState != NULL) {
+ break;
+ }
+ }
+ }
+ if (state->netState == NULL) {
+ LOG(ERROR) << "JDWP net startup failed (req port=" << pParams->port << ")";
+ return false;
+ }
+ } else {
+ port = pParams->port; // used in a debug msg later
+ state->netState = netStartup(-1);
+ }
+
+ if (pParams->suspend) {
+ LOG(INFO) << "JDWP will wait for debugger on port " << port;
+ } else {
+ LOG(INFO) << "JDWP will " << (pParams->server ? "listen" : "connect") << " on port " << port;
+ }
+
+ return true;
+}
+
+/*
+ * Are we still waiting for the handshake string?
+ */
+static bool awaitingHandshake(JdwpState* state) {
+ return state->netState->awaitingHandshake;
+}
+
+/*
+ * Initialize JDWP stuff.
+ *
+ * Allocates a new state structure. If "port" is non-negative, this also
+ * tries to bind to a listen port. If "port" is less than zero, we assume
+ * we're preparing for an outbound connection, and return without binding
+ * to anything.
+ *
+ * This may be called several times if we're probing for a port.
+ *
+ * Returns 0 on success.
+ */
+static JdwpNetState* netStartup(short port) {
+ int one = 1;
+ JdwpNetState* netState = new JdwpNetState;
+
+ if (port < 0) {
+ return netState;
+ }
+
+ CHECK_NE(port, 0);
+
+ netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (netState->listenSock < 0) {
+ PLOG(ERROR) << "Socket create failed";
+ goto fail;
+ }
+
+ /* allow immediate re-use */
+ if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+ PLOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
+ goto fail;
+ }
+
+ union {
+ struct sockaddr_in addrInet;
+ struct sockaddr addrPlain;
+ } addr;
+ addr.addrInet.sin_family = AF_INET;
+ addr.addrInet.sin_port = htons(port);
+ inet_aton("127.0.0.1", &addr.addrInet.sin_addr);
+
+ if (bind(netState->listenSock, &addr.addrPlain, sizeof(addr)) != 0) {
+ PLOG(VERBOSE) << "attempt to bind to port " << port << " failed";
+ goto fail;
+ }
+
+ netState->listenPort = port;
+ LOG(VERBOSE) << "+++ bound to port " << netState->listenPort;
+
+ if (listen(netState->listenSock, 5) != 0) {
+ PLOG(ERROR) << "Listen failed";
+ goto fail;
+ }
+
+ return netState;
+
+fail:
+ netShutdown(netState);
+ netFree(netState);
+ return NULL;
+}
+
+/*
+ * Shut down JDWP listener. Don't free state.
+ *
+ * Note that "netState" may be partially initialized if "startup" failed.
+ *
+ * This may be called from a non-JDWP thread as part of shutting the
+ * JDWP thread down.
+ *
+ * (This is currently called several times during startup as we probe
+ * for an open port.)
+ */
+static void netShutdown(JdwpNetState* netState) {
+ if (netState == NULL) {
+ return;
+ }
+
+ int listenSock = netState->listenSock;
+ int clientSock = netState->clientSock;
+
+ /* clear these out so it doesn't wake up and try to reuse them */
+ netState->listenSock = netState->clientSock = -1;
+
+ /* "shutdown" dislodges blocking read() and accept() calls */
+ if (listenSock >= 0) {
+ shutdown(listenSock, SHUT_RDWR);
+ close(listenSock);
+ }
+ if (clientSock >= 0) {
+ shutdown(clientSock, SHUT_RDWR);
+ close(clientSock);
+ }
+
+ /* if we might be sitting in select, kick us loose */
+ if (netState->wakePipe[1] >= 0) {
+ LOG(VERBOSE) << "+++ writing to wakePipe";
+ (void) write(netState->wakePipe[1], "", 1);
+ }
+}
+
+static void netShutdownExtern(JdwpState* state) {
+ netShutdown(state->netState);
+}
+
+/*
+ * Free JDWP state.
+ *
+ * Call this after shutting the network down with netShutdown().
+ */
+static void netFree(JdwpNetState* netState) {
+ if (netState == NULL) {
+ return;
+ }
+ CHECK_EQ(netState->listenSock, -1);
+ CHECK_EQ(netState->clientSock, -1);
+
+ if (netState->wakePipe[0] >= 0) {
+ close(netState->wakePipe[0]);
+ netState->wakePipe[0] = -1;
+ }
+ if (netState->wakePipe[1] >= 0) {
+ close(netState->wakePipe[1]);
+ netState->wakePipe[1] = -1;
+ }
+
+ delete netState;
+}
+
+static void netFreeExtern(JdwpState* state) {
+ netFree(state->netState);
+}
+
+/*
+ * Returns "true" if we're connected to a debugger.
+ */
+static bool isConnected(JdwpState* state) {
+ return (state->netState != NULL && state->netState->clientSock >= 0);
+}
+
+/*
+ * Returns "true" if the fd is ready, "false" if not.
+ */
+#if 0
+static bool isFdReadable(int sock)
+{
+ fd_set readfds;
+ struct timeval tv;
+ int count;
+
+ FD_ZERO(&readfds);
+ FD_SET(sock, &readfds);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ count = select(sock+1, &readfds, NULL, NULL, &tv);
+ if (count <= 0)
+ return false;
+
+ if (FD_ISSET(sock, &readfds)) /* make sure it's our fd */
+ return true;
+
+ LOG(ERROR) << "WEIRD: odd behavior in select (count=" << count << ")";
+ return false;
+}
+#endif
+
+#if 0
+/*
+ * Check to see if we have a pending connection from the debugger.
+ *
+ * Returns true on success (meaning a connection is available).
+ */
+static bool checkConnection(JdwpState* state) {
+ JdwpNetState* netState = state->netState;
+
+ CHECK_GE(netState->listenSock, 0);
+ /* not expecting to be called when debugger is actively connected */
+ CHECK_LT(netState->clientSock, 0);
+
+ if (!isFdReadable(netState->listenSock))
+ return false;
+ return true;
+}
+#endif
+
+/*
+ * Disable the TCP Nagle algorithm, which delays transmission of outbound
+ * packets until the previous transmissions have been acked. JDWP does a
+ * lot of back-and-forth with small packets, so this may help.
+ */
+static int setNoDelay(int fd)
+{
+ int on = 1;
+ int cc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+ CHECK_EQ(cc, 0);
+ return cc;
+}
+
+/*
+ * Accept a connection. This will block waiting for somebody to show up.
+ * If that's not desirable, use checkConnection() to make sure something
+ * is pending.
+ */
+static bool acceptConnection(JdwpState* state)
+{
+ JdwpNetState* netState = state->netState;
+ union {
+ struct sockaddr_in addrInet;
+ struct sockaddr addrPlain;
+ } addr;
+ socklen_t addrlen;
+ int sock;
+
+ if (netState->listenSock < 0) {
+ return false; /* you're not listening! */
+ }
+
+ CHECK_LT(netState->clientSock, 0); /* must not already be talking */
+
+ addrlen = sizeof(addr);
+ do {
+ sock = accept(netState->listenSock, &addr.addrPlain, &addrlen);
+ if (sock < 0 && errno != EINTR) {
+ // When we call shutdown() on the socket, accept() returns with
+ // EINVAL. Don't gripe about it.
+ if (errno == EINVAL) {
+ PLOG(VERBOSE) << "accept failed";
+ } else {
+ PLOG(ERROR) << "accept failed";
+ return false;
+ }
+ }
+ } while (sock < 0);
+
+ netState->remoteAddr = addr.addrInet.sin_addr;
+ netState->remotePort = ntohs(addr.addrInet.sin_port);
+ LOG(VERBOSE) << "+++ accepted connection from " << inet_ntoa(netState->remoteAddr) << ":" << netState->remotePort;
+
+ netState->clientSock = sock;
+ netState->awaitingHandshake = true;
+ netState->inputCount = 0;
+
+ LOG(VERBOSE) << "Setting TCP_NODELAY on accepted socket";
+ setNoDelay(netState->clientSock);
+
+ if (pipe(netState->wakePipe) < 0) {
+ PLOG(ERROR) << "pipe failed";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Create a connection to a waiting debugger.
+ */
+static bool establishConnection(JdwpState* state) {
+ union {
+ struct sockaddr_in addrInet;
+ struct sockaddr addrPlain;
+ } addr;
+ struct hostent* pEntry;
+
+ CHECK(state != NULL && state->netState != NULL);
+ CHECK(!state->params.server);
+ CHECK_NE(state->params.host[0], '\0');
+ CHECK_NE(state->params.port, 0);
+
+ /*
+ * Start by resolving the host name.
+ */
+//#undef HAVE_GETHOSTBYNAME_R
+//#warning "forcing non-R"
+#ifdef HAVE_GETHOSTBYNAME_R
+ struct hostent he;
+ char auxBuf[128];
+ int error;
+ int cc = gethostbyname_r(state->params.host, &he, auxBuf, sizeof(auxBuf), &pEntry, &error);
+ if (cc != 0) {
+ LOG(WARNING) << "gethostbyname_r('" << state->params.host << "') failed: " << hstrerror(error);
+ return false;
+ }
+#else
+ h_errno = 0;
+ pEntry = gethostbyname(state->params.host);
+ if (pEntry == NULL) {
+ PLOG(WARNING) << "gethostbyname('" << state->params.host << "') failed";
+ return false;
+ }
+#endif
+
+ /* copy it out ASAP to minimize risk of multithreaded annoyances */
+ memcpy(&addr.addrInet.sin_addr, pEntry->h_addr, pEntry->h_length);
+ addr.addrInet.sin_family = pEntry->h_addrtype;
+
+ addr.addrInet.sin_port = htons(state->params.port);
+
+ LOG(INFO) << "Connecting out to " << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port);
+
+ /*
+ * Create a socket.
+ */
+ JdwpNetState* netState;
+ netState = state->netState;
+ netState->clientSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (netState->clientSock < 0) {
+ PLOG(ERROR) << "Unable to create socket";
+ return false;
+ }
+
+ /*
+ * Try to connect.
+ */
+ if (connect(netState->clientSock, &addr.addrPlain, sizeof(addr)) != 0) {
+ PLOG(ERROR) << "Unable to connect to " << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port);
+ close(netState->clientSock);
+ netState->clientSock = -1;
+ return false;
+ }
+
+ LOG(INFO) << "Connection established to " << state->params.host << " (" << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port) << ")";
+ netState->awaitingHandshake = true;
+ netState->inputCount = 0;
+
+ setNoDelay(netState->clientSock);
+
+ if (pipe(netState->wakePipe) < 0) {
+ PLOG(ERROR) << "pipe failed";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Close the connection to the debugger.
+ *
+ * Reset the state so we're ready to receive a new connection.
+ */
+static void closeConnection(JdwpState* state) {
+ JdwpNetState* netState;
+
+ CHECK(state != NULL && state->netState != NULL);
+
+ netState = state->netState;
+ if (netState->clientSock < 0) {
+ return;
+ }
+
+ LOG(VERBOSE) << "+++ closed connection to " << inet_ntoa(netState->remoteAddr) << ":" << netState->remotePort;
+
+ close(netState->clientSock);
+ netState->clientSock = -1;
+}
+
+/*
+ * Figure out if we have a full packet in the buffer.
+ */
+static bool haveFullPacket(JdwpNetState* netState) {
+ if (netState->awaitingHandshake) {
+ return (netState->inputCount >= (int) kMagicHandshakeLen);
+ }
+ if (netState->inputCount < 4) {
+ return false;
+ }
+ long length = get4BE(netState->inputBuffer);
+ return (netState->inputCount >= length);
+}
+
+/*
+ * Consume bytes from the buffer.
+ *
+ * This would be more efficient with a circular buffer. However, we're
+ * usually only going to find one packet, which is trivial to handle.
+ */
+static void consumeBytes(JdwpNetState* netState, int count) {
+ CHECK_GT(count, 0);
+ CHECK_LE(count, netState->inputCount);
+
+ if (count == netState->inputCount) {
+ netState->inputCount = 0;
+ return;
+ }
+
+ memmove(netState->inputBuffer, netState->inputBuffer + count, netState->inputCount - count);
+ netState->inputCount -= count;
+}
+
+/*
+ * Dump the contents of a packet to stdout.
+ */
+#if 0
+static void dumpPacket(const unsigned char* packetBuf)
+{
+ const unsigned char* buf = packetBuf;
+ uint32_t length, id;
+ uint8_t flags, cmdSet, cmd;
+ uint16_t error;
+ bool reply;
+ int dataLen;
+
+ cmd = cmdSet = 0xcc;
+
+ length = read4BE(&buf);
+ id = read4BE(&buf);
+ flags = read1(&buf);
+ if ((flags & kJDWPFlagReply) != 0) {
+ reply = true;
+ error = read2BE(&buf);
+ } else {
+ reply = false;
+ cmdSet = read1(&buf);
+ cmd = read1(&buf);
+ }
+
+ dataLen = length - (buf - packetBuf);
+
+ LOG(VERBOSE) << StringPrintf("--- %s: dataLen=%u id=0x%08x flags=0x%02x cmd=%d/%d",
+ reply ? "reply" : "req",
+ dataLen, id, flags, cmdSet, cmd);
+ if (dataLen > 0) {
+ HexDump(buf, dataLen);
+ }
+}
+#endif
+
+/*
+ * Handle a packet. Returns "false" if we encounter a connection-fatal error.
+ */
+static bool handlePacket(JdwpState* state)
+{
+ JdwpNetState* netState = state->netState;
+ const unsigned char* buf = netState->inputBuffer;
+ JdwpReqHeader hdr;
+ uint32_t length, id;
+ uint8_t flags, cmdSet, cmd;
+ uint16_t error;
+ bool reply;
+ int dataLen;
+
+ cmd = cmdSet = 0; // shut up gcc
+
+ /*dumpPacket(netState->inputBuffer);*/
+
+ length = read4BE(&buf);
+ id = read4BE(&buf);
+ flags = read1(&buf);
+ if ((flags & kJDWPFlagReply) != 0) {
+ reply = true;
+ error = read2BE(&buf);
+ } else {
+ reply = false;
+ cmdSet = read1(&buf);
+ cmd = read1(&buf);
+ }
+
+ CHECK_LE((int) length, netState->inputCount);
+ dataLen = length - (buf - netState->inputBuffer);
+
+ if (!reply) {
+ ExpandBuf* pReply = expandBufAlloc();
+
+ hdr.length = length;
+ hdr.id = id;
+ hdr.cmdSet = cmdSet;
+ hdr.cmd = cmd;
+ ProcessRequest(state, &hdr, buf, dataLen, pReply);
+ if (expandBufGetLength(pReply) > 0) {
+ ssize_t cc = netState->writePacket(pReply);
+
+ if (cc != (ssize_t) expandBufGetLength(pReply)) {
+ PLOG(ERROR) << "Failed sending reply to debugger";
+ expandBufFree(pReply);
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "No reply created for set=" << cmdSet << " cmd=" << cmd;
+ }
+ expandBufFree(pReply);
+ } else {
+ LOG(ERROR) << "reply?!";
+ DCHECK(false);
+ }
+
+ LOG(VERBOSE) << "----------";
+
+ consumeBytes(netState, length);
+ return true;
+}
+
+/*
+ * Process incoming data. If no data is available, this will block until
+ * some arrives.
+ *
+ * If we get a full packet, handle it.
+ *
+ * To take some of the mystery out of life, we want to reject incoming
+ * connections if we already have a debugger attached. If we don't, the
+ * debugger will just mysteriously hang until it times out. We could just
+ * close the listen socket, but there's a good chance we won't be able to
+ * bind to the same port again, which would confuse utilities.
+ *
+ * Returns "false" on error (indicating that the connection has been severed),
+ * "true" if things are still okay.
+ */
+static bool processIncoming(JdwpState* state) {
+ JdwpNetState* netState = state->netState;
+ int readCount;
+
+ CHECK_GE(netState->clientSock, 0);
+
+ if (!haveFullPacket(netState)) {
+ /* read some more, looping until we have data */
+ errno = 0;
+ while (1) {
+ int selCount;
+ fd_set readfds;
+ int maxfd;
+ int fd;
+
+ maxfd = netState->listenSock;
+ if (netState->clientSock > maxfd) {
+ maxfd = netState->clientSock;
+ }
+ if (netState->wakePipe[0] > maxfd) {
+ maxfd = netState->wakePipe[0];
+ }
+
+ if (maxfd < 0) {
+ LOG(VERBOSE) << "+++ all fds are closed";
+ return false;
+ }
+
+ FD_ZERO(&readfds);
+
+ /* configure fds; note these may get zapped by another thread */
+ fd = netState->listenSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ }
+ fd = netState->clientSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ }
+ fd = netState->wakePipe[0];
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ } else {
+ LOG(INFO) << "NOTE: entering select w/o wakepipe";
+ }
+
+ /*
+ * Select blocks until it sees activity on the file descriptors.
+ * Closing the local file descriptor does not count as activity,
+ * so we can't rely on that to wake us up (it works for read()
+ * and accept(), but not select()).
+ *
+ * We can do one of three things: (1) send a signal and catch
+ * EINTR, (2) open an additional fd ("wakePipe") and write to
+ * it when it's time to exit, or (3) time out periodically and
+ * re-issue the select. We're currently using #2, as it's more
+ * reliable than #1 and generally better than #3. Wastes two fds.
+ */
+ selCount = select(maxfd+1, &readfds, NULL, NULL, NULL);
+ if (selCount < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ PLOG(ERROR) << "select failed";
+ goto fail;
+ }
+
+ if (netState->wakePipe[0] >= 0 && FD_ISSET(netState->wakePipe[0], &readfds)) {
+ if (netState->listenSock >= 0) {
+ LOG(ERROR) << "Exit wake set, but not exiting?";
+ } else {
+ LOG(DEBUG) << "Got wake-up signal, bailing out of select";
+ }
+ goto fail;
+ }
+ if (netState->listenSock >= 0 && FD_ISSET(netState->listenSock, &readfds)) {
+ LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
+ union {
+ struct sockaddr_in addrInet;
+ struct sockaddr addrPlain;
+ } addr;
+ socklen_t addrlen;
+ int tmpSock;
+ tmpSock = accept(netState->listenSock, &addr.addrPlain, &addrlen);
+ if (tmpSock < 0) {
+ LOG(INFO) << "Weird -- accept failed";
+ } else {
+ close(tmpSock);
+ }
+ }
+ if (netState->clientSock >= 0 && FD_ISSET(netState->clientSock, &readfds)) {
+ readCount = read(netState->clientSock, netState->inputBuffer + netState->inputCount, sizeof(netState->inputBuffer) - netState->inputCount);
+ if (readCount < 0) {
+ /* read failed */
+ if (errno != EINTR) {
+ goto fail;
+ }
+ LOG(DEBUG) << "+++ EINTR hit";
+ return true;
+ } else if (readCount == 0) {
+ /* EOF hit -- far end went away */
+ LOG(DEBUG) << "+++ peer disconnected";
+ goto fail;
+ } else {
+ break;
+ }
+ }
+ }
+
+ netState->inputCount += readCount;
+ if (!haveFullPacket(netState)) {
+ return true; /* still not there yet */
+ }
+ }
+
+ /*
+ * Special-case the initial handshake. For some bizarre reason we're
+ * expected to emulate bad tty settings by echoing the request back
+ * exactly as it was sent. Note the handshake is always initiated by
+ * the debugger, no matter who connects to whom.
+ *
+ * Other than this one case, the protocol [claims to be] stateless.
+ */
+ if (netState->awaitingHandshake) {
+ int cc;
+
+ if (memcmp(netState->inputBuffer, kMagicHandshake, kMagicHandshakeLen) != 0) {
+ LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", netState->inputBuffer);
+ goto fail;
+ }
+
+ errno = 0;
+ cc = write(netState->clientSock, netState->inputBuffer, kMagicHandshakeLen);
+ if (cc != kMagicHandshakeLen) {
+ PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
+ goto fail;
+ }
+
+ consumeBytes(netState, kMagicHandshakeLen);
+ netState->awaitingHandshake = false;
+ LOG(VERBOSE) << "+++ handshake complete";
+ return true;
+ }
+
+ /*
+ * Handle this packet.
+ */
+ return handlePacket(state);
+
+fail:
+ closeConnection(state);
+ return false;
+}
+
+/*
+ * Send a request.
+ *
+ * The entire packet must be sent with a single write() call to avoid
+ * threading issues.
+ *
+ * Returns "true" if it was sent successfully.
+ */
+static bool sendRequest(JdwpState* state, ExpandBuf* pReq) {
+ JdwpNetState* netState = state->netState;
+
+ /*dumpPacket(expandBufGetBuffer(pReq));*/
+ if (netState->clientSock < 0) {
+ /* can happen with some DDMS events */
+ LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+ return false;
+ }
+
+ errno = 0;
+ ssize_t cc = netState->writePacket(pReq);
+
+ if (cc != (ssize_t) expandBufGetLength(pReq)) {
+ PLOG(ERROR) << "Failed sending req to debugger (" << cc << " of " << expandBufGetLength(pReq) << ")";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Send a request that was split into multiple buffers.
+ *
+ * The entire packet must be sent with a single writev() call to avoid
+ * threading issues.
+ *
+ * Returns "true" if it was sent successfully.
+ */
+static bool sendBufferedRequest(JdwpState* state, const iovec* iov, int iovcnt) {
+ JdwpNetState* netState = state->netState;
+
+ if (netState->clientSock < 0) {
+ /* can happen with some DDMS events */
+ LOG(VERBOSE) << "NOT sending request -- no debugger is attached";
+ return false;
+ }
+
+ size_t expected = 0;
+ for (int i = 0; i < iovcnt; i++) {
+ expected += iov[i].iov_len;
+ }
+
+ ssize_t actual = netState->writeBufferedPacket(iov, iovcnt);
+
+ if ((size_t)actual != expected) {
+ PLOG(ERROR) << "Failed sending b-req to debugger (" << actual << " of " << expected << ")";
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Our functions.
+ *
+ * We can't generally share the implementations with other transports,
+ * even if they're also socket-based, because our JdwpNetState will be
+ * different from theirs.
+ */
+static const JdwpTransport socketTransport = {
+ prepareSocket,
+ acceptConnection,
+ establishConnection,
+ closeConnection,
+ netShutdownExtern,
+ netFreeExtern,
+ isConnected,
+ awaitingHandshake,
+ processIncoming,
+ sendRequest,
+ sendBufferedRequest,
+};
+
+/*
+ * Return our set.
+ */
+const JdwpTransport* SocketTransport() {
+ return &socketTransport;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/src/logging.cc b/src/logging.cc
index bf6ab91..bb68442 100644
--- a/src/logging.cc
+++ b/src/logging.cc
@@ -63,4 +63,78 @@
return data_->buffer;
}
+/*
+ * Print a hex dump in this format:
+ *
+ * 01234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef\n
+ *
+ * Does not use printf() or other string-formatting calls.
+ */
+void HexDump(const void* address, size_t byte_count, bool show_actual_address) {
+ static const char gHexDigit[] = "0123456789abcdef";
+ const unsigned char* addr = reinterpret_cast<const unsigned char*>(address);
+ char out[77]; /* exact fit */
+ unsigned int offset; /* offset to show while printing */
+
+ if (show_actual_address) {
+ offset = (int) addr;
+ } else {
+ offset = 0;
+ }
+ memset(out, ' ', sizeof(out)-1);
+ out[8] = ':';
+ out[sizeof(out)-2] = '\n';
+ out[sizeof(out)-1] = '\0';
+
+ int gap = (int) offset & 0x0f;
+ while (byte_count) {
+ unsigned int lineOffset = offset & ~0x0f;
+ int i, count;
+
+ char* hex = out;
+ char* asc = out + 59;
+
+ for (i = 0; i < 8; i++) {
+ *hex++ = gHexDigit[lineOffset >> 28];
+ lineOffset <<= 4;
+ }
+ hex++;
+ hex++;
+
+ count = ((int)byte_count > 16-gap) ? 16-gap : (int)byte_count; /* cap length */
+ CHECK_NE(count, 0);
+ CHECK_LE(count + gap, 16);
+
+ if (gap) {
+ /* only on first line */
+ hex += gap * 3;
+ asc += gap;
+ }
+
+ for (i = gap ; i < count+gap; i++) {
+ *hex++ = gHexDigit[*addr >> 4];
+ *hex++ = gHexDigit[*addr & 0x0f];
+ hex++;
+ if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/)
+ *asc++ = *addr;
+ else
+ *asc++ = '.';
+ addr++;
+ }
+ for ( ; i < 16; i++) {
+ /* erase extra stuff; only happens on last line */
+ *hex++ = ' ';
+ *hex++ = ' ';
+ hex++;
+ *asc++ = ' ';
+ }
+
+ LOG(INFO) << out;
+
+ gap = 0;
+ byte_count -= count;
+ offset += count;
+ }
+}
+
} // namespace art
diff --git a/src/logging.h b/src/logging.h
index d3dbcd4..2e7856e 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -169,6 +169,8 @@
DISALLOW_COPY_AND_ASSIGN(LogMessage);
};
+void HexDump(const void* address, size_t byte_count, bool show_actual_address = false);
+
} // namespace art
#endif // ART_SRC_LOGGING_H_
diff --git a/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index 3392689..b0aba2f 100644
--- a/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/src/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "debugger.h"
#include "logging.h"
#include "JniConstants.h" // Last to avoid problems with LOG redefinition.
@@ -28,7 +29,7 @@
{
ScopedByteArrayRO data(env, javaData);
DCHECK_LE(offset + length, static_cast<int32_t>(data.size()));
- UNIMPLEMENTED(WARNING) << "dvmDbgDdmSendChunk(type, length, data->get() + offset);";
+ Dbg::DdmSendChunk(type, length, reinterpret_cast<const uint8_t*>(&data[offset]));
}
static JNINativeMethod gMethods[] = {
diff --git a/src/thread.cc b/src/thread.cc
index 42d9f1a..1800834 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -26,6 +26,7 @@
#include <iostream>
#include <list>
+#include "debugger.h"
#include "class_linker.h"
#include "class_loader.h"
#include "context.h"
@@ -165,10 +166,7 @@
// in progress while we were starting up.)
runtime->GetThreadList()->WaitForGo();
- // TODO: say "hi" to the debugger.
- //if (gDvm.debuggerConnected) {
- // dvmDbgPostThreadStart(self);
- //}
+ Dbg::PostThreadStart(self);
// Invoke the 'run' method of our java.lang.Thread.
CHECK(self->peer_ != NULL);
@@ -767,10 +765,7 @@
// this.vmData = 0;
SetVmData(peer_, NULL);
- // TODO: say "bye" to the debugger.
- //if (gDvm.debuggerConnected) {
- // dvmDbgPostThreadDeath(self);
- //}
+ Dbg::PostThreadDeath(this);
// Thread.join() is implemented as an Object.wait() on the Thread.lock
// object. Signal anyone who is waiting.