summaryrefslogtreecommitdiffstats
path: root/runtime/jdwp
diff options
context:
space:
mode:
authorBrian Carlstrom <bdc@google.com>2013-07-12 13:46:57 -0700
committerBrian Carlstrom <bdc@google.com>2013-07-12 17:49:01 -0700
commit7940e44f4517de5e2634a7e07d58d0fb26160513 (patch)
treeac90242d96229a6942f6e24ab137bc1f8f2e0025 /runtime/jdwp
parent5cd9e3b122f276f610980cbaf0d2ad6ed4cd9088 (diff)
downloadandroid_art-7940e44f4517de5e2634a7e07d58d0fb26160513.tar.gz
android_art-7940e44f4517de5e2634a7e07d58d0fb26160513.tar.bz2
android_art-7940e44f4517de5e2634a7e07d58d0fb26160513.zip
Create separate Android.mk for main build targets
The runtime, compiler, dex2oat, and oatdump now are in seperate trees to prevent dependency creep. They can now be individually built without rebuilding the rest of the art projects. dalvikvm and jdwpspy were already this way. Builds in the art directory should behave as before, building everything including tests. Change-Id: Ic6b1151e5ed0f823c3dd301afd2b13eb2d8feb81
Diffstat (limited to 'runtime/jdwp')
-rw-r--r--runtime/jdwp/README.txt11
-rw-r--r--runtime/jdwp/jdwp.h432
-rw-r--r--runtime/jdwp/jdwp_adb.cc445
-rw-r--r--runtime/jdwp/jdwp_bits.h124
-rw-r--r--runtime/jdwp/jdwp_constants.h249
-rw-r--r--runtime/jdwp/jdwp_event.cc1093
-rw-r--r--runtime/jdwp/jdwp_event.h113
-rw-r--r--runtime/jdwp/jdwp_expand_buf.cc188
-rw-r--r--runtime/jdwp/jdwp_expand_buf.h70
-rw-r--r--runtime/jdwp/jdwp_handler.cc1750
-rw-r--r--runtime/jdwp/jdwp_main.cc625
-rw-r--r--runtime/jdwp/jdwp_priv.h104
-rw-r--r--runtime/jdwp/jdwp_request.cc183
-rw-r--r--runtime/jdwp/jdwp_socket.cc502
-rw-r--r--runtime/jdwp/object_registry.cc211
-rw-r--r--runtime/jdwp/object_registry.h100
16 files changed, 6200 insertions, 0 deletions
diff --git a/runtime/jdwp/README.txt b/runtime/jdwp/README.txt
new file mode 100644
index 0000000000..da25fb17dd
--- /dev/null
+++ b/runtime/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/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
new file mode 100644
index 0000000000..436525c3d0
--- /dev/null
+++ b/runtime/jdwp/jdwp.h
@@ -0,0 +1,432 @@
+/*
+ * 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 "base/mutex.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 mirror {
+class AbstractMethod;
+} // namespace mirror
+class Thread;
+
+namespace JDWP {
+
+/*
+ * 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 */
+
+ObjectId ReadObjectId(const uint8_t** 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 {
+ JdwpTypeTag type_tag;
+ RefTypeId class_id;
+ MethodId method_id;
+ uint64_t dex_pc;
+};
+std::ostream& operator<<(std::ostream& os, const JdwpLocation& rhs)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+bool operator==(const JdwpLocation& lhs, const JdwpLocation& rhs);
+bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs);
+
+/*
+ * 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);
+
+struct JdwpOptions {
+ JdwpTransportType transport;
+ bool server;
+ bool suspend;
+ std::string host;
+ uint16_t port;
+};
+
+struct JdwpEvent;
+struct JdwpNetStateBase;
+struct ModBasket;
+struct Request;
+
+/*
+ * State for JDWP functions.
+ */
+struct JdwpState {
+ /*
+ * 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.
+ */
+ static JdwpState* Create(const JdwpOptions* options)
+ LOCKS_EXCLUDED(Locks::mutator_lock_);
+
+ ~JdwpState();
+
+ /*
+ * Returns "true" if a debugger or DDM is connected.
+ */
+ bool IsActive();
+
+ /**
+ * Returns the Thread* for the JDWP daemon thread.
+ */
+ Thread* GetDebugThread();
+
+ /*
+ * Get time, in milliseconds, since the last debugger activity.
+ */
+ int64_t LastDebuggerActivity();
+
+ void ExitAfterReplying(int exit_status);
+
+ /*
+ * When we hit a debugger event that requires suspension, it's important
+ * that we wait for the thread to suspend itself before processing any
+ * additional requests. (Otherwise, if the debugger immediately sends a
+ * "resume thread" command, the resume might arrive before the thread has
+ * suspended itself.)
+ *
+ * The thread should call the "set" function before sending the event to
+ * the debugger. The main JDWP handler loop calls "get" before processing
+ * an event, and will wait for thread suspension if it's set. Once the
+ * thread has suspended itself, the JDWP handler calls "clear" and
+ * continues processing the current event. This works in the suspend-all
+ * case because the event thread doesn't suspend itself until everything
+ * else has suspended.
+ *
+ * It's possible that multiple threads could encounter thread-suspending
+ * events at the same time, so we grab a mutex in the "set" call, and
+ * release it in the "clear" call.
+ */
+ //ObjectId GetWaitForEventThread();
+ void SetWaitForEventThread(ObjectId threadId);
+ void ClearWaitForEventThread();
+
+ /*
+ * 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() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * 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(const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * An exception has been thrown.
+ *
+ * Pass in a zeroed-out "*pCatchLoc" if the exception wasn't caught.
+ */
+ bool PostException(const JdwpLocation* pThrowLoc, ObjectId excepId, RefTypeId excepClassId,
+ const JdwpLocation* pCatchLoc, ObjectId thisPtr)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * A thread has started or stopped.
+ */
+ bool PostThreadChange(ObjectId threadId, bool start)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * Class has been prepared.
+ */
+ bool PostClassPrepare(JdwpTypeTag tag, RefTypeId refTypeId, const std::string& signature,
+ int status)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * The VM is about to stop.
+ */
+ bool PostVMDeath();
+
+ // Called if/when we realize we're talking to DDMS.
+ void NotifyDdmsActive() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * Send up a chunk of DDM data.
+ */
+ void DdmSendChunkV(uint32_t type, const iovec* iov, int iov_count)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ bool HandlePacket();
+
+ void SendRequest(ExpandBuf* pReq);
+
+ void ResetState()
+ LOCKS_EXCLUDED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /* atomic ops to get next serial number */
+ uint32_t NextRequestSerial();
+ uint32_t NextEventSerial();
+
+ void Run()
+ LOCKS_EXCLUDED(Locks::mutator_lock_,
+ Locks::thread_suspend_count_lock_);
+
+ /*
+ * 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(JdwpEvent* pEvent)
+ LOCKS_EXCLUDED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * Unregister an event, given the requestId.
+ */
+ void UnregisterEventById(uint32_t requestId)
+ LOCKS_EXCLUDED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ /*
+ * Unregister all events.
+ */
+ void UnregisterAll()
+ LOCKS_EXCLUDED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ private:
+ explicit JdwpState(const JdwpOptions* options);
+ void ProcessRequest(Request& request, ExpandBuf* pReply);
+ bool InvokeInProgress();
+ bool IsConnected();
+ void SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId thread_self_id)
+ LOCKS_EXCLUDED(Locks::mutator_lock_);
+ void SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy suspend_policy,
+ ObjectId threadId)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ void CleanupMatchList(JdwpEvent** match_list,
+ int match_count)
+ EXCLUSIVE_LOCKS_REQUIRED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ void EventFinish(ExpandBuf* pReq);
+ void FindMatchingEvents(JdwpEventKind eventKind,
+ ModBasket* basket,
+ JdwpEvent** match_list,
+ int* pMatchCount)
+ EXCLUSIVE_LOCKS_REQUIRED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ void UnregisterEvent(JdwpEvent* pEvent)
+ EXCLUSIVE_LOCKS_REQUIRED(event_list_lock_)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ void SendBufferedRequest(uint32_t type, const iovec* iov, int iov_count);
+
+ public: // TODO: fix privacy
+ const JdwpOptions* options_;
+
+ private:
+ /* wait for creation of the JDWP thread */
+ Mutex thread_start_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ ConditionVariable thread_start_cond_ GUARDED_BY(thread_start_lock_);
+
+ pthread_t pthread_;
+ Thread* thread_;
+
+ volatile int32_t debug_thread_started_ GUARDED_BY(thread_start_lock_);
+ ObjectId debug_thread_id_;
+
+ private:
+ bool run;
+
+ public: // TODO: fix privacy
+ JdwpNetStateBase* netState;
+
+ private:
+ // For wait-for-debugger.
+ Mutex attach_lock_ ACQUIRED_AFTER(thread_start_lock_);
+ ConditionVariable attach_cond_ GUARDED_BY(attach_lock_);
+
+ // Time of last debugger activity, in milliseconds.
+ int64_t last_activity_time_ms_;
+
+ // Global counters and a mutex to protect them.
+ Mutex serial_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ uint32_t request_serial_ GUARDED_BY(serial_lock_);
+ uint32_t event_serial_ GUARDED_BY(serial_lock_);
+
+ // Linked list of events requested by the debugger (breakpoints, class prep, etc).
+ Mutex event_list_lock_;
+ JdwpEvent* event_list_ GUARDED_BY(event_list_lock_);
+ int event_list_size_ GUARDED_BY(event_list_lock_); // Number of elements in event_list_.
+
+ // Used to synchronize suspension of the event thread (to avoid receiving "resume"
+ // events before the thread has finished suspending itself).
+ Mutex event_thread_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ ConditionVariable event_thread_cond_ GUARDED_BY(event_thread_lock_);
+ ObjectId event_thread_id_;
+
+ bool ddm_is_active_;
+
+ bool should_exit_;
+ int exit_status_;
+};
+
+std::string DescribeField(const FieldId& field_id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+std::string DescribeMethod(const MethodId& method_id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+std::string DescribeRefTypeId(const RefTypeId& ref_type_id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+class Request {
+ public:
+ Request(const uint8_t* bytes, uint32_t available);
+ ~Request();
+
+ std::string ReadUtf8String();
+
+ // Helper function: read a variable-width value from the input buffer.
+ uint64_t ReadValue(size_t width);
+
+ int32_t ReadSigned32(const char* what);
+
+ uint32_t ReadUnsigned32(const char* what);
+
+ FieldId ReadFieldId() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ MethodId ReadMethodId() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ ObjectId ReadObjectId(const char* specific_kind);
+
+ ObjectId ReadArrayId();
+
+ ObjectId ReadObjectId();
+
+ ObjectId ReadThreadId();
+
+ ObjectId ReadThreadGroupId();
+
+ RefTypeId ReadRefTypeId() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ FrameId ReadFrameId();
+
+ template <typename T> T ReadEnum1(const char* specific_kind) {
+ T value = static_cast<T>(Read1());
+ VLOG(jdwp) << " " << specific_kind << " " << value;
+ return value;
+ }
+
+ JdwpTag ReadTag();
+
+ JdwpTypeTag ReadTypeTag();
+
+ JdwpLocation ReadLocation() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ JdwpModKind ReadModKind();
+
+ //
+ // Return values from this JDWP packet's header.
+ //
+ size_t GetLength() { return byte_count_; }
+ uint32_t GetId() { return id_; }
+ uint8_t GetCommandSet() { return command_set_; }
+ uint8_t GetCommand() { return command_; }
+
+ // Returns the number of bytes remaining.
+ size_t size() { return end_ - p_; }
+
+ // Returns a pointer to the next byte.
+ const uint8_t* data() { return p_; }
+
+ void Skip(size_t count) { p_ += count; }
+
+ void CheckConsumed();
+
+ private:
+ uint8_t Read1();
+ uint16_t Read2BE();
+ uint32_t Read4BE();
+ uint64_t Read8BE();
+
+ uint32_t byte_count_;
+ uint32_t id_;
+ uint8_t command_set_;
+ uint8_t command_;
+
+ const uint8_t* p_;
+ const uint8_t* end_;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWP_H_
diff --git a/runtime/jdwp/jdwp_adb.cc b/runtime/jdwp/jdwp_adb.cc
new file mode 100644
index 0000000000..9652f6075c
--- /dev/null
+++ b/runtime/jdwp/jdwp_adb.cc
@@ -0,0 +1,445 @@
+/*
+ * 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 <errno.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "jdwp/jdwp_priv.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 kJdwpControlName "\0jdwp-control"
+#define kJdwpControlNameLen (sizeof(kJdwpControlName)-1)
+
+namespace art {
+
+namespace JDWP {
+
+struct JdwpAdbState : public JdwpNetStateBase {
+ public:
+ JdwpAdbState(JdwpState* state) : JdwpNetStateBase(state) {
+ control_sock_ = -1;
+ shutting_down_ = false;
+
+ control_addr_.controlAddrUn.sun_family = AF_UNIX;
+ control_addr_len_ = sizeof(control_addr_.controlAddrUn.sun_family) + kJdwpControlNameLen;
+ memcpy(control_addr_.controlAddrUn.sun_path, kJdwpControlName, kJdwpControlNameLen);
+ }
+
+ ~JdwpAdbState() {
+ if (clientSock != -1) {
+ shutdown(clientSock, SHUT_RDWR);
+ close(clientSock);
+ }
+ if (control_sock_ != -1) {
+ shutdown(control_sock_, SHUT_RDWR);
+ close(control_sock_);
+ }
+ }
+
+ virtual bool Accept();
+
+ virtual bool Establish(const JdwpOptions*) {
+ return false;
+ }
+
+ virtual void Shutdown() {
+ shutting_down_ = true;
+
+ int control_sock = this->control_sock_;
+ int clientSock = this->clientSock;
+
+ /* clear these out so it doesn't wake up and try to reuse them */
+ this->control_sock_ = this->clientSock = -1;
+
+ if (clientSock != -1) {
+ shutdown(clientSock, SHUT_RDWR);
+ }
+
+ if (control_sock != -1) {
+ shutdown(control_sock, SHUT_RDWR);
+ }
+
+ WakePipe();
+ }
+
+ virtual bool ProcessIncoming();
+
+ private:
+ int ReceiveClientFd();
+
+ int control_sock_;
+ bool shutting_down_;
+
+ socklen_t control_addr_len_;
+ union {
+ sockaddr_un controlAddrUn;
+ sockaddr controlAddrPlain;
+ } control_addr_;
+};
+
+/*
+ * 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.
+ */
+bool InitAdbTransport(JdwpState* state, const JdwpOptions*) {
+ VLOG(jdwp) << "ADB transport startup";
+ state->netState = new JdwpAdbState(state);
+ return (state->netState != NULL);
+}
+
+/*
+ * 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->control_sock_.
+ */
+int JdwpAdbState::ReceiveClientFd() {
+ char dummy = '!';
+ union {
+ cmsghdr cm;
+ char buffer[CMSG_SPACE(sizeof(int))];
+ } cm_un;
+
+ iovec iov;
+ iov.iov_base = &dummy;
+ iov.iov_len = 1;
+
+ msghdr msg;
+ 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);
+
+ cmsghdr* 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;
+
+ int rc = TEMP_FAILURE_RETRY(recvmsg(control_sock_, &msg, 0));
+
+ if (rc <= 0) {
+ if (rc == -1) {
+ PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << control_sock_ << ")";
+ }
+ close(control_sock_);
+ control_sock_ = -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.
+ */
+bool JdwpAdbState::Accept() {
+ int retryCount = 0;
+
+ /* first, ensure that we get a connection to the ADB daemon */
+
+ retry:
+ if (shutting_down_) {
+ return false;
+ }
+
+ if (control_sock_ == -1) {
+ int sleep_ms = 500;
+ const int sleep_max_ms = 2*1000;
+ char buff[5];
+
+ control_sock_ = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (control_sock_ < 0) {
+ PLOG(ERROR) << "Could not create ADB control socket";
+ return false;
+ }
+
+ if (!MakePipe()) {
+ 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(control_sock_, &control_addr_.controlAddrPlain, control_addr_len_);
+ if (!ret) {
+#ifdef HAVE_ANDROID_OS
+ if (!socket_peer_is_trusted(control_sock_)) {
+ if (shutdown(control_sock_, SHUT_RDWR)) {
+ PLOG(ERROR) << "trouble shutting down socket";
+ }
+ return false;
+ }
+#endif
+
+ /* now try to send our pid to the ADB daemon */
+ ret = TEMP_FAILURE_RETRY(send(control_sock_, buff, 4, 0));
+ if (ret >= 0) {
+ VLOG(jdwp) << StringPrintf("PID sent as '%.*s' to ADB", 4, buff);
+ break;
+ }
+
+ PLOG(ERROR) << "Weird, can't send JDWP process pid to ADB";
+ return false;
+ }
+ if (VLOG_IS_ON(jdwp)) {
+ PLOG(ERROR) << "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 (shutting_down_) {
+ return false;
+ }
+ }
+ }
+
+ VLOG(jdwp) << "trying to receive file descriptor from ADB";
+ /* now we can receive a client file descriptor */
+ clientSock = ReceiveClientFd();
+ if (shutting_down_) {
+ return false; // suppress logs and additional activity
+ }
+ if (clientSock == -1) {
+ if (++retryCount > 5) {
+ LOG(ERROR) << "adb connection max retries exceeded";
+ return false;
+ }
+ goto retry;
+ } else {
+ VLOG(jdwp) << "received file descriptor " << clientSock << " from ADB";
+ SetAwaitingHandshake(true);
+ input_count_ = 0;
+ 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.
+ */
+bool JdwpAdbState::ProcessIncoming() {
+ int readCount;
+
+ CHECK(clientSock != -1);
+
+ if (!HaveFullPacket()) {
+ /* 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 = control_sock_;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ }
+ fd = clientSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ }
+ fd = wake_pipe_[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) {
+ VLOG(jdwp) << "+++ 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 ("wake pipe") 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 (wake_pipe_[0] >= 0 && FD_ISSET(wake_pipe_[0], &readfds)) {
+ LOG(DEBUG) << "Got wake-up signal, bailing out of select";
+ goto fail;
+ }
+ if (control_sock_ >= 0 && FD_ISSET(control_sock_, &readfds)) {
+ int sock = ReceiveClientFd();
+ if (sock >= 0) {
+ LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
+ close(sock);
+ } else {
+ CHECK(control_sock_ == -1);
+ /*
+ * Remote side most likely went away, so our next read
+ * on clientSock will fail and throw us out of the loop.
+ */
+ }
+ }
+ if (clientSock >= 0 && FD_ISSET(clientSock, &readfds)) {
+ readCount = read(clientSock, input_buffer_ + input_count_, sizeof(input_buffer_) - input_count_);
+ 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 */
+ VLOG(jdwp) << "+++ peer disconnected";
+ goto fail;
+ } else {
+ break;
+ }
+ }
+ }
+
+ input_count_ += readCount;
+ if (!HaveFullPacket()) {
+ 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 (IsAwaitingHandshake()) {
+ if (memcmp(input_buffer_, kMagicHandshake, kMagicHandshakeLen) != 0) {
+ LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", input_buffer_);
+ goto fail;
+ }
+
+ errno = 0;
+ int cc = TEMP_FAILURE_RETRY(write(clientSock, input_buffer_, kMagicHandshakeLen));
+ if (cc != kMagicHandshakeLen) {
+ PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
+ goto fail;
+ }
+
+ ConsumeBytes(kMagicHandshakeLen);
+ SetAwaitingHandshake(false);
+ VLOG(jdwp) << "+++ handshake complete";
+ return true;
+ }
+
+ /*
+ * Handle this packet.
+ */
+ return state_->HandlePacket();
+
+ fail:
+ Close();
+ return false;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/jdwp_bits.h b/runtime/jdwp/jdwp_bits.h
new file mode 100644
index 0000000000..2a3c775164
--- /dev/null
+++ b/runtime/jdwp/jdwp_bits.h
@@ -0,0 +1,124 @@
+/*
+ * 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>
+#include <string>
+#include <vector>
+
+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 void Append1BE(std::vector<uint8_t>& bytes, uint8_t value) {
+ bytes.push_back(value);
+}
+
+static inline void Append2BE(std::vector<uint8_t>& bytes, uint16_t value) {
+ bytes.push_back(static_cast<uint8_t>(value >> 8));
+ bytes.push_back(static_cast<uint8_t>(value));
+}
+
+static inline void Append4BE(std::vector<uint8_t>& bytes, uint32_t value) {
+ bytes.push_back(static_cast<uint8_t>(value >> 24));
+ bytes.push_back(static_cast<uint8_t>(value >> 16));
+ bytes.push_back(static_cast<uint8_t>(value >> 8));
+ bytes.push_back(static_cast<uint8_t>(value));
+}
+
+static inline void Append8BE(std::vector<uint8_t>& bytes, uint64_t value) {
+ bytes.push_back(static_cast<uint8_t>(value >> 56));
+ bytes.push_back(static_cast<uint8_t>(value >> 48));
+ bytes.push_back(static_cast<uint8_t>(value >> 40));
+ bytes.push_back(static_cast<uint8_t>(value >> 32));
+ bytes.push_back(static_cast<uint8_t>(value >> 24));
+ bytes.push_back(static_cast<uint8_t>(value >> 16));
+ bytes.push_back(static_cast<uint8_t>(value >> 8));
+ bytes.push_back(static_cast<uint8_t>(value));
+}
+
+static inline void AppendUtf16BE(std::vector<uint8_t>& bytes, const uint16_t* chars, size_t char_count) {
+ Append4BE(bytes, char_count);
+ for (size_t i = 0; i < char_count; ++i) {
+ Append2BE(bytes, chars[i]);
+ }
+}
+
+// @deprecated
+static inline void Set1(uint8_t* buf, uint8_t val) {
+ *buf = (uint8_t)(val);
+}
+
+// @deprecated
+static inline void Set2BE(uint8_t* buf, uint16_t val) {
+ *buf++ = (uint8_t)(val >> 8);
+ *buf = (uint8_t)(val);
+}
+
+// @deprecated
+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);
+}
+
+// @deprecated
+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);
+}
+
+static inline void Write1BE(uint8_t** dst, uint8_t value) {
+ Set1(*dst, value);
+ *dst += sizeof(value);
+}
+
+static inline void Write2BE(uint8_t** dst, uint16_t value) {
+ Set2BE(*dst, value);
+ *dst += sizeof(value);
+}
+
+static inline void Write4BE(uint8_t** dst, uint32_t value) {
+ Set4BE(*dst, value);
+ *dst += sizeof(value);
+}
+
+static inline void Write8BE(uint8_t** dst, uint64_t value) {
+ Set8BE(*dst, value);
+ *dst += sizeof(value);
+}
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_BITS_H_
diff --git a/runtime/jdwp/jdwp_constants.h b/runtime/jdwp/jdwp_constants.h
new file mode 100644
index 0000000000..ebc575b6b6
--- /dev/null
+++ b/runtime/jdwp/jdwp_constants.h
@@ -0,0 +1,249 @@
+/*
+ * 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_THREAD_NOT_ALIVE = 15,
+ 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_DEATH = 7, // Formerly known as THREAD_END.
+ 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_METHOD_EXIT_WITH_RETURN_VALUE = 42,
+ EK_MONITOR_CONTENDED_ENTER = 43,
+ EK_MONITOR_CONTENDED_ENTERED = 44,
+ EK_MONITOR_WAIT = 45,
+ EK_MONITOR_WAITED = 46,
+ EK_VM_START = 90, // Formerly known as VM_INIT.
+ EK_VM_DEATH = 99,
+ EK_VM_DISCONNECTED = 100, // "Never sent across JDWP".
+};
+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,
+ MK_SOURCE_NAME_MATCH = 12, // Since Java 6.
+};
+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 (for example, one bytecode).
+ 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 JdwpTag {
+ 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 JdwpTag& value);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPCONSTANTS_H_
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
new file mode 100644
index 0000000000..77434e12f8
--- /dev/null
+++ b/runtime/jdwp/jdwp_event.cc
@@ -0,0 +1,1093 @@
+/*
+ * 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 "jdwp/jdwp_event.h"
+
+#include <stddef.h> /* for offsetof() */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "debugger.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "jdwp/jdwp_priv.h"
+#include "thread-inl.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 {
+ ModBasket() : pLoc(NULL), threadId(0), classId(0), excepClassId(0),
+ caught(false), field(0), thisPtr(0) { }
+
+ const JdwpLocation* pLoc; /* LocationOnly */
+ std::string 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 */
+};
+
+/*
+ * 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->suspend_policy << " modCount=" << pEvent->modCount;
+
+ for (int i = 0; i < pEvent->modCount; i++) {
+ const JdwpEventMod* pMod = &pEvent->mods[i];
+ LOG(INFO) << " " << 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 JdwpState::RegisterEvent(JdwpEvent* pEvent) {
+ 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);
+ JdwpError status = Dbg::ConfigureStep(pMod->step.threadId, size, depth);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ } 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.
+ */
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ if (event_list_ != NULL) {
+ pEvent->next = event_list_;
+ event_list_->prev = pEvent;
+ }
+ event_list_ = pEvent;
+ ++event_list_size_;
+
+ 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.
+ */
+void JdwpState::UnregisterEvent(JdwpEvent* pEvent) {
+ if (pEvent->prev == NULL) {
+ /* head of the list */
+ CHECK(event_list_ == pEvent);
+
+ event_list_ = 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);
+ }
+ }
+
+ --event_list_size_;
+ CHECK(event_list_size_ != 0 || event_list_ == 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 JdwpState::UnregisterEventById(uint32_t requestId) {
+ MutexLock mu(Thread::Current(), event_list_lock_);
+
+ JdwpEvent* pEvent = event_list_;
+ while (pEvent != NULL) {
+ if (pEvent->requestId == requestId) {
+ UnregisterEvent(pEvent);
+ EventFree(pEvent);
+ return; /* there can be only one with a given ID */
+ }
+
+ pEvent = pEvent->next;
+ }
+
+ //LOGD("Odd: no match when removing event reqId=0x%04x", requestId);
+}
+
+/*
+ * Remove all entries from the event list.
+ */
+void JdwpState::UnregisterAll() {
+ MutexLock mu(Thread::Current(), event_list_lock_);
+
+ JdwpEvent* pEvent = event_list_;
+ while (pEvent != NULL) {
+ JdwpEvent* pNextEvent = pEvent->next;
+
+ UnregisterEvent(pEvent);
+ EventFree(pEvent);
+ pEvent = pNextEvent;
+ }
+
+ event_list_ = NULL;
+}
+
+/*
+ * 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->event_list_ != 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(size_t event_count) {
+ return new JdwpEvent*[event_count];
+}
+
+/*
+ * Run through the list and remove any entries with an expired "count" mod
+ * from the event list, then free the match list.
+ */
+void JdwpState::CleanupMatchList(JdwpEvent** match_list, int match_count) {
+ JdwpEvent** ppEvent = match_list;
+
+ while (match_count--) {
+ JdwpEvent* pEvent = *ppEvent;
+
+ for (int i = 0; i < pEvent->modCount; i++) {
+ if (pEvent->mods[i].modKind == MK_COUNT && pEvent->mods[i].count.count == 0) {
+ VLOG(jdwp) << "##### Removing expired event";
+ UnregisterEvent(pEvent);
+ EventFree(pEvent);
+ break;
+ }
+ }
+
+ ppEvent++;
+ }
+
+ delete[] match_list;
+}
+
+/*
+ * 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 std::string& target) {
+ size_t patLen = strlen(pattern);
+ if (pattern[0] == '*') {
+ patLen--;
+ if (target.size() < patLen) {
+ return false;
+ }
+ return strcmp(pattern+1, target.c_str() + (target.size()-patLen)) == 0;
+ } else if (pattern[patLen-1] == '*') {
+ return strncmp(pattern, target.c_str(), patLen-1) == 0;
+ } else {
+ return strcmp(pattern, target.c_str()) == 0;
+ }
+}
+
+/*
+ * 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(JdwpEvent* pEvent, ModBasket* basket)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ 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 (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(FATAL) << "unknown mod kind " << pMod->modKind;
+ 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 "match_list", 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.
+ */
+void JdwpState::FindMatchingEvents(JdwpEventKind eventKind, ModBasket* basket,
+ JdwpEvent** match_list, int* pMatchCount) {
+ /* start after the existing entries */
+ match_list += *pMatchCount;
+
+ JdwpEvent* pEvent = event_list_;
+ while (pEvent != NULL) {
+ if (pEvent->eventKind == eventKind && ModsMatch(pEvent, basket)) {
+ *match_list++ = pEvent;
+ (*pMatchCount)++;
+ }
+
+ pEvent = pEvent->next;
+ }
+}
+
+/*
+ * Scan through the list of matches and determine the most severe
+ * suspension policy.
+ */
+static JdwpSuspendPolicy scanSuspendPolicy(JdwpEvent** match_list, int match_count) {
+ JdwpSuspendPolicy policy = SP_NONE;
+
+ while (match_count--) {
+ if ((*match_list)->suspend_policy > policy) {
+ policy = (*match_list)->suspend_policy;
+ }
+ match_list++;
+ }
+
+ return policy;
+}
+
+/*
+ * Three possibilities:
+ * SP_NONE - do nothing
+ * SP_EVENT_THREAD - suspend ourselves
+ * SP_ALL - suspend everybody except JDWP support thread
+ */
+void JdwpState::SuspendByPolicy(JdwpSuspendPolicy suspend_policy, JDWP::ObjectId thread_self_id) {
+ VLOG(jdwp) << "SuspendByPolicy(" << suspend_policy << ")";
+ if (suspend_policy == SP_NONE) {
+ return;
+ }
+
+ if (suspend_policy == SP_ALL) {
+ Dbg::SuspendVM();
+ } else {
+ CHECK_EQ(suspend_policy, SP_EVENT_THREAD);
+ }
+
+ /* this is rare but possible -- see CLASS_PREPARE handling */
+ if (thread_self_id == debug_thread_id_) {
+ 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->invoke_needed_) {
+ /*LOGD("SuspendByPolicy: no invoke needed");*/
+ break;
+ }
+
+ /* grab this before posting/suspending again */
+ SetWaitForEventThread(thread_self_id);
+
+ /* leave pReq->invoke_needed_ raised so we can check reentrancy */
+ Dbg::ExecuteMethod(pReq);
+
+ pReq->error = ERR_NONE;
+
+ /* clear this before signaling */
+ pReq->invoke_needed_ = false;
+
+ VLOG(jdwp) << "invoke complete, signaling and self-suspending";
+ Thread* self = Thread::Current();
+ MutexLock mu(self, pReq->lock_);
+ pReq->cond_.Signal(self);
+ }
+}
+
+void JdwpState::SendRequestAndPossiblySuspend(ExpandBuf* pReq, JdwpSuspendPolicy suspend_policy,
+ ObjectId threadId) {
+ Thread* self = Thread::Current();
+ self->AssertThreadSuspensionIsAllowable();
+ /* send request and possibly suspend ourselves */
+ if (pReq != NULL) {
+ JDWP::ObjectId thread_self_id = Dbg::GetThreadSelfId();
+ self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend);
+ if (suspend_policy != SP_NONE) {
+ SetWaitForEventThread(threadId);
+ }
+ EventFinish(pReq);
+ SuspendByPolicy(suspend_policy, thread_self_id);
+ self->TransitionFromSuspendedToRunnable();
+ }
+}
+
+/*
+ * Determine if there is a method invocation in progress in the current
+ * thread.
+ *
+ * We look at the "invoke_needed" flag in the per-thread DebugInvokeReq
+ * state. If set, we're in the process of invoking a method.
+ */
+bool JdwpState::InvokeInProgress() {
+ DebugInvokeReq* pReq = Dbg::GetInvokeReq();
+ return pReq->invoke_needed_;
+}
+
+/*
+ * We need the JDWP thread to hold off on doing stuff while we post an
+ * event and then suspend ourselves.
+ *
+ * Call this with a threadId of zero if you just want to wait for the
+ * current thread operation to complete.
+ *
+ * This could go to sleep waiting for another thread, so it's important
+ * that the thread be marked as VMWAIT before calling here.
+ */
+void JdwpState::SetWaitForEventThread(ObjectId threadId) {
+ bool waited = false;
+
+ /* this is held for very brief periods; contention is unlikely */
+ Thread* self = Thread::Current();
+ MutexLock mu(self, event_thread_lock_);
+
+ /*
+ * If another thread is already doing stuff, wait for it. This can
+ * go to sleep indefinitely.
+ */
+ while (event_thread_id_ != 0) {
+ VLOG(jdwp) << StringPrintf("event in progress (%#llx), %#llx sleeping", event_thread_id_, threadId);
+ waited = true;
+ event_thread_cond_.Wait(self);
+ }
+
+ if (waited || threadId != 0) {
+ VLOG(jdwp) << StringPrintf("event token grabbed (%#llx)", threadId);
+ }
+ if (threadId != 0) {
+ event_thread_id_ = threadId;
+ }
+}
+
+/*
+ * Clear the threadId and signal anybody waiting.
+ */
+void JdwpState::ClearWaitForEventThread() {
+ /*
+ * 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.
+ */
+ Thread* self = Thread::Current();
+ MutexLock mu(self, event_thread_lock_);
+
+ CHECK_NE(event_thread_id_, 0U);
+ VLOG(jdwp) << StringPrintf("cleared event token (%#llx)", event_thread_id_);
+
+ event_thread_id_ = 0;
+
+ event_thread_cond_.Signal(self);
+}
+
+
+/*
+ * 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).
+ */
+void JdwpState::EventFinish(ExpandBuf* pReq) {
+ uint8_t* buf = expandBufGetBuffer(pReq);
+
+ Set4BE(buf, expandBufGetLength(pReq));
+ Set4BE(buf+4, NextRequestSerial());
+ Set1(buf+8, 0); /* flags */
+ Set1(buf+9, kJdwpEventCommandSet);
+ Set1(buf+10, kJdwpCompositeCommand);
+
+ SendRequest(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 JdwpState::PostVMStart() {
+ JdwpSuspendPolicy suspend_policy;
+ ObjectId threadId = Dbg::GetThreadSelfId();
+
+ if (options_->suspend) {
+ suspend_policy = SP_ALL;
+ } else {
+ suspend_policy = SP_NONE;
+ }
+
+ ExpandBuf* pReq = eventPrep();
+ {
+ MutexLock mu(Thread::Current(), event_list_lock_); // probably don't need this here
+
+ VLOG(jdwp) << "EVENT: " << EK_VM_START;
+ VLOG(jdwp) << " suspend_policy=" << suspend_policy;
+
+ expandBufAdd1(pReq, suspend_policy);
+ expandBufAdd4BE(pReq, 1);
+
+ expandBufAdd1(pReq, EK_VM_START);
+ expandBufAdd4BE(pReq, 0); /* requestId */
+ expandBufAdd8BE(pReq, threadId);
+ }
+
+ /* send request and possibly suspend ourselves */
+ SendRequestAndPossiblySuspend(pReq, suspend_policy, threadId);
+
+ return true;
+}
+
+/*
+ * 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 JdwpState::PostLocationEvent(const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) {
+ ModBasket basket;
+ basket.pLoc = pLoc;
+ basket.classId = pLoc->class_id;
+ basket.thisPtr = thisPtr;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = Dbg::GetClassName(pLoc->class_id);
+
+ /*
+ * 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 == debug_thread_id_) {
+ VLOG(jdwp) << "Ignoring location event in JDWP thread";
+ 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()) {
+ VLOG(jdwp) << "Not checking breakpoints during invoke (" << basket.className << ")";
+ return false;
+ }
+
+ JdwpEvent** match_list = NULL;
+ int match_count = 0;
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspend_policy = SP_NONE;
+
+ {
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ match_list = AllocMatchList(event_list_size_);
+ if ((eventFlags & Dbg::kBreakpoint) != 0) {
+ FindMatchingEvents(EK_BREAKPOINT, &basket, match_list, &match_count);
+ }
+ if ((eventFlags & Dbg::kSingleStep) != 0) {
+ FindMatchingEvents(EK_SINGLE_STEP, &basket, match_list, &match_count);
+ }
+ if ((eventFlags & Dbg::kMethodEntry) != 0) {
+ FindMatchingEvents(EK_METHOD_ENTRY, &basket, match_list, &match_count);
+ }
+ if ((eventFlags & Dbg::kMethodExit) != 0) {
+ FindMatchingEvents(EK_METHOD_EXIT, &basket, match_list, &match_count);
+
+ // TODO: match EK_METHOD_EXIT_WITH_RETURN_VALUE too; we need to include the 'value', though.
+ //FindMatchingEvents(EK_METHOD_EXIT_WITH_RETURN_VALUE, &basket, match_list, &match_count);
+ }
+ if (match_count != 0) {
+ VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total) "
+ << basket.className << "." << Dbg::GetMethodName(pLoc->method_id)
+ << StringPrintf(" thread=%#llx dex_pc=%#llx)", basket.threadId, pLoc->dex_pc);
+
+ suspend_policy = scanSuspendPolicy(match_list, match_count);
+ VLOG(jdwp) << " suspend_policy=" << suspend_policy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspend_policy);
+ expandBufAdd4BE(pReq, match_count);
+
+ for (int i = 0; i < match_count; i++) {
+ expandBufAdd1(pReq, match_list[i]->eventKind);
+ expandBufAdd4BE(pReq, match_list[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+ expandBufAddLocation(pReq, *pLoc);
+ }
+ }
+
+ CleanupMatchList(match_list, match_count);
+ }
+
+ SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId);
+ return match_count != 0;
+}
+
+/*
+ * A thread is starting or stopping.
+ *
+ * Valid mods:
+ * Count, ThreadOnly
+ */
+bool JdwpState::PostThreadChange(ObjectId threadId, bool start) {
+ CHECK_EQ(threadId, Dbg::GetThreadSelfId());
+
+ /*
+ * I don't think this can happen.
+ */
+ if (InvokeInProgress()) {
+ LOG(WARNING) << "Not posting thread change during invoke";
+ return false;
+ }
+
+ ModBasket basket;
+ basket.threadId = threadId;
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspend_policy = SP_NONE;
+ int match_count = 0;
+ {
+ // Don't allow the list to be updated while we scan it.
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ JdwpEvent** match_list = AllocMatchList(event_list_size_);
+
+ if (start) {
+ FindMatchingEvents(EK_THREAD_START, &basket, match_list, &match_count);
+ } else {
+ FindMatchingEvents(EK_THREAD_DEATH, &basket, match_list, &match_count);
+ }
+
+ if (match_count != 0) {
+ VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total) "
+ << StringPrintf("thread=%#llx", basket.threadId) << ")";
+
+ suspend_policy = scanSuspendPolicy(match_list, match_count);
+ VLOG(jdwp) << " suspend_policy=" << suspend_policy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspend_policy);
+ expandBufAdd4BE(pReq, match_count);
+
+ for (int i = 0; i < match_count; i++) {
+ expandBufAdd1(pReq, match_list[i]->eventKind);
+ expandBufAdd4BE(pReq, match_list[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+ }
+ }
+
+ CleanupMatchList(match_list, match_count);
+ }
+
+ SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId);
+
+ return match_count != 0;
+}
+
+/*
+ * Send a polite "VM is dying" message to the debugger.
+ *
+ * Skips the usual "event token" stuff.
+ */
+bool JdwpState::PostVMDeath() {
+ VLOG(jdwp) << "EVENT: " << EK_VM_DEATH;
+
+ ExpandBuf* pReq = eventPrep();
+ expandBufAdd1(pReq, SP_NONE);
+ expandBufAdd4BE(pReq, 1);
+
+ expandBufAdd1(pReq, EK_VM_DEATH);
+ expandBufAdd4BE(pReq, 0);
+ EventFinish(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 JdwpState::PostException(const JdwpLocation* pThrowLoc,
+ ObjectId exceptionId, RefTypeId exceptionClassId,
+ const JdwpLocation* pCatchLoc, ObjectId thisPtr) {
+ ModBasket basket;
+
+ basket.pLoc = pThrowLoc;
+ basket.classId = pThrowLoc->class_id;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = Dbg::GetClassName(basket.classId);
+ basket.excepClassId = exceptionClassId;
+ basket.caught = (pCatchLoc->class_id != 0);
+ basket.thisPtr = thisPtr;
+
+ /* don't try to post an exception caused by the debugger */
+ if (InvokeInProgress()) {
+ VLOG(jdwp) << "Not posting exception hit during invoke (" << basket.className << ")";
+ return false;
+ }
+
+ JdwpEvent** match_list = NULL;
+ int match_count = 0;
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspend_policy = SP_NONE;
+ {
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ match_list = AllocMatchList(event_list_size_);
+ FindMatchingEvents(EK_EXCEPTION, &basket, match_list, &match_count);
+ if (match_count != 0) {
+ VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total)"
+ << StringPrintf(" thread=%#llx", basket.threadId)
+ << StringPrintf(" exceptId=%#llx", exceptionId)
+ << " caught=" << basket.caught << ")"
+ << " throw: " << *pThrowLoc;
+ if (pCatchLoc->class_id == 0) {
+ VLOG(jdwp) << " catch: (not caught)";
+ } else {
+ VLOG(jdwp) << " catch: " << *pCatchLoc;
+ }
+
+ suspend_policy = scanSuspendPolicy(match_list, match_count);
+ VLOG(jdwp) << " suspend_policy=" << suspend_policy;
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspend_policy);
+ expandBufAdd4BE(pReq, match_count);
+
+ for (int i = 0; i < match_count; i++) {
+ expandBufAdd1(pReq, match_list[i]->eventKind);
+ expandBufAdd4BE(pReq, match_list[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+
+ expandBufAddLocation(pReq, *pThrowLoc);
+ expandBufAdd1(pReq, JT_OBJECT);
+ expandBufAdd8BE(pReq, exceptionId);
+ expandBufAddLocation(pReq, *pCatchLoc);
+ }
+ }
+
+ CleanupMatchList(match_list, match_count);
+ }
+
+ SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId);
+
+ return match_count != 0;
+}
+
+/*
+ * Announce that a class has been loaded.
+ *
+ * Valid mods:
+ * Count, ThreadOnly, ClassOnly, ClassMatch, ClassExclude
+ */
+bool JdwpState::PostClassPrepare(JdwpTypeTag tag, RefTypeId refTypeId, const std::string& signature,
+ int status) {
+ ModBasket basket;
+
+ basket.classId = refTypeId;
+ basket.threadId = Dbg::GetThreadSelfId();
+ basket.className = Dbg::GetClassName(basket.classId);
+
+ /* suppress class prep caused by debugger */
+ if (InvokeInProgress()) {
+ VLOG(jdwp) << "Not posting class prep caused by invoke (" << basket.className << ")";
+ return false;
+ }
+
+ ExpandBuf* pReq = NULL;
+ JdwpSuspendPolicy suspend_policy = SP_NONE;
+ int match_count = 0;
+ {
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ JdwpEvent** match_list = AllocMatchList(event_list_size_);
+ FindMatchingEvents(EK_CLASS_PREPARE, &basket, match_list, &match_count);
+ if (match_count != 0) {
+ VLOG(jdwp) << "EVENT: " << match_list[0]->eventKind << "(" << match_count << " total) "
+ << StringPrintf("thread=%#llx", basket.threadId) << ") " << signature;
+
+ suspend_policy = scanSuspendPolicy(match_list, match_count);
+ VLOG(jdwp) << " suspend_policy=" << suspend_policy;
+
+ if (basket.threadId == debug_thread_id_) {
+ /*
+ * 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.
+ */
+ VLOG(jdwp) << " NOTE: class prepare in debugger thread!";
+ basket.threadId = 0;
+ if (suspend_policy == SP_EVENT_THREAD) {
+ suspend_policy = SP_ALL;
+ }
+ }
+
+ pReq = eventPrep();
+ expandBufAdd1(pReq, suspend_policy);
+ expandBufAdd4BE(pReq, match_count);
+
+ for (int i = 0; i < match_count; i++) {
+ expandBufAdd1(pReq, match_list[i]->eventKind);
+ expandBufAdd4BE(pReq, match_list[i]->requestId);
+ expandBufAdd8BE(pReq, basket.threadId);
+
+ expandBufAdd1(pReq, tag);
+ expandBufAdd8BE(pReq, refTypeId);
+ expandBufAddUtf8String(pReq, signature);
+ expandBufAdd4BE(pReq, status);
+ }
+ }
+ CleanupMatchList(match_list, match_count);
+ }
+
+ SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId);
+
+ return match_count != 0;
+}
+
+/*
+ * 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 JdwpState::DdmSendChunkV(uint32_t type, const iovec* iov, int iov_count) {
+ uint8_t header[kJDWPHeaderLen + 8];
+ size_t dataLen = 0;
+
+ CHECK(iov != NULL);
+ CHECK_GT(iov_count, 0);
+ CHECK_LT(iov_count, 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[iov_count+1];
+ for (int i = 0; i < iov_count; 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());
+ 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);
+
+ // Try to avoid blocking GC during a send, but only safe when not using mutexes at a lower-level
+ // than mutator for lock ordering reasons.
+ Thread* self = Thread::Current();
+ bool safe_to_release_mutator_lock_over_send = !Locks::mutator_lock_->IsExclusiveHeld(self);
+ if (safe_to_release_mutator_lock_over_send) {
+ for (size_t i=0; i < kMutatorLock; ++i) {
+ if (self->GetHeldMutex(static_cast<LockLevel>(i)) != NULL) {
+ safe_to_release_mutator_lock_over_send = false;
+ break;
+ }
+ }
+ }
+ if (safe_to_release_mutator_lock_over_send) {
+ // Change state to waiting to allow GC, ... while we're sending.
+ self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend);
+ SendBufferedRequest(type, wrapiov, iov_count + 1);
+ self->TransitionFromSuspendedToRunnable();
+ } else {
+ // Send and possibly block GC...
+ SendBufferedRequest(type, wrapiov, iov_count + 1);
+ }
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/jdwp_event.h b/runtime/jdwp/jdwp_event.h
new file mode 100644
index 0000000000..a6eabb1371
--- /dev/null
+++ b/runtime/jdwp/jdwp_event.h
@@ -0,0 +1,113 @@
+/*
+ * 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.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 {
+ JdwpModKind modKind;
+ struct {
+ JdwpModKind modKind;
+ int count;
+ } count;
+ struct {
+ JdwpModKind modKind;
+ uint32_t exprId;
+ } conditional;
+ struct {
+ JdwpModKind modKind;
+ ObjectId threadId;
+ } threadOnly;
+ struct {
+ JdwpModKind modKind;
+ RefTypeId refTypeId;
+ } classOnly;
+ struct {
+ JdwpModKind modKind;
+ char* classPattern;
+ } classMatch;
+ struct {
+ JdwpModKind modKind;
+ char* classPattern;
+ } classExclude;
+ struct {
+ JdwpModKind modKind;
+ JdwpLocation loc;
+ } locationOnly;
+ struct {
+ JdwpModKind modKind;
+ uint8_t caught;
+ uint8_t uncaught;
+ RefTypeId refTypeId;
+ } exceptionOnly;
+ struct {
+ JdwpModKind modKind;
+ RefTypeId refTypeId;
+ FieldId fieldId;
+ } fieldOnly;
+ struct {
+ JdwpModKind modKind;
+ ObjectId threadId;
+ int size; /* JdwpStepSize */
+ int depth; /* JdwpStepDepth */
+ } step;
+ struct {
+ JdwpModKind 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 suspend_policy; /* 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);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPEVENT_H_
diff --git a/runtime/jdwp/jdwp_expand_buf.cc b/runtime/jdwp/jdwp_expand_buf.cc
new file mode 100644
index 0000000000..0a64f28e11
--- /dev/null
+++ b/runtime/jdwp/jdwp_expand_buf.cc
@@ -0,0 +1,188 @@
+/*
+ * 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_expand_buf.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/logging.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_bits.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 = new ExpandBuf;
+ newBuf->storage = reinterpret_cast<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);
+ delete 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 = reinterpret_cast<uint8_t*>(realloc(pBuf->storage, pBuf->maxLen));
+ if (newPtr == NULL) {
+ LOG(FATAL) << "realloc(" << pBuf->maxLen << ") failed";
+ }
+
+ 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);
+}
+
+static void SetUtf8String(uint8_t* buf, const char* str, size_t strLen) {
+ Set4BE(buf, strLen);
+ memcpy(buf + sizeof(uint32_t), str, strLen);
+}
+
+/*
+ * 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 char* s) {
+ int strLen = strlen(s);
+ ensureSpace(pBuf, sizeof(uint32_t) + strLen);
+ SetUtf8String(pBuf->storage + pBuf->curLen, s, strLen);
+ pBuf->curLen += sizeof(uint32_t) + strLen;
+}
+
+void expandBufAddUtf8String(ExpandBuf* pBuf, const std::string& s) {
+ ensureSpace(pBuf, sizeof(uint32_t) + s.size());
+ SetUtf8String(pBuf->storage + pBuf->curLen, s.data(), s.size());
+ pBuf->curLen += sizeof(uint32_t) + s.size();
+}
+
+void expandBufAddLocation(ExpandBuf* buf, const JdwpLocation& location) {
+ expandBufAdd1(buf, location.type_tag);
+ expandBufAddObjectId(buf, location.class_id);
+ expandBufAddMethodId(buf, location.method_id);
+ expandBufAdd8BE(buf, location.dex_pc);
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/jdwp_expand_buf.h b/runtime/jdwp/jdwp_expand_buf.h
new file mode 100644
index 0000000000..820f62d6a0
--- /dev/null
+++ b/runtime/jdwp/jdwp_expand_buf.h
@@ -0,0 +1,70 @@
+/*
+ * 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 <string>
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace art {
+
+namespace JDWP {
+
+struct ExpandBuf; /* private */
+struct JdwpLocation;
+
+/* 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 char* s);
+void expandBufAddUtf8String(ExpandBuf* pBuf, const std::string& s);
+void expandBufAddLocation(ExpandBuf* pReply, const JdwpLocation& location);
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_EXPANDBUF_H_
diff --git a/runtime/jdwp/jdwp_handler.cc b/runtime/jdwp/jdwp_handler.cc
new file mode 100644
index 0000000000..8ef146c096
--- /dev/null
+++ b/runtime/jdwp/jdwp_handler.cc
@@ -0,0 +1,1750 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "atomic.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/stringprintf.h"
+#include "debugger.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/jdwp_expand_buf.h"
+#include "jdwp/jdwp_priv.h"
+#include "runtime.h"
+#include "thread-inl.h"
+#include "UniquePtr.h"
+
+namespace art {
+
+namespace JDWP {
+
+std::string DescribeField(const FieldId& field_id) {
+ return StringPrintf("%#x (%s)", field_id, Dbg::GetFieldName(field_id).c_str());
+}
+
+std::string DescribeMethod(const MethodId& method_id) {
+ return StringPrintf("%#x (%s)", method_id, Dbg::GetMethodName(method_id).c_str());
+}
+
+std::string DescribeRefTypeId(const RefTypeId& ref_type_id) {
+ std::string signature("unknown");
+ Dbg::GetSignature(ref_type_id, signature);
+ return StringPrintf("%#llx (%s)", ref_type_id, signature.c_str());
+}
+
+// Helper function: write a variable-width value into the output input buffer.
+static void WriteValue(ExpandBuf* pReply, int width, uint64_t value) {
+ switch (width) {
+ case 1: expandBufAdd1(pReply, value); break;
+ case 2: expandBufAdd2BE(pReply, value); break;
+ case 4: expandBufAdd4BE(pReply, value); break;
+ case 8: expandBufAdd8BE(pReply, value); break;
+ default: LOG(FATAL) << width; break;
+ }
+}
+
+static JdwpError WriteTaggedObject(ExpandBuf* reply, ObjectId object_id)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ uint8_t tag;
+ JdwpError rc = Dbg::GetObjectTag(object_id, tag);
+ if (rc == ERR_NONE) {
+ expandBufAdd1(reply, tag);
+ expandBufAddObjectId(reply, object_id);
+ }
+ return rc;
+}
+
+static JdwpError WriteTaggedObjectList(ExpandBuf* reply, const std::vector<ObjectId>& objects)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAdd4BE(reply, objects.size());
+ for (size_t i = 0; i < objects.size(); ++i) {
+ JdwpError rc = WriteTaggedObject(reply, objects[i]);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Common code for *_InvokeMethod requests.
+ *
+ * If "is_constructor" is set, this returns "object_id" rather than the
+ * expected-to-be-void return value of the called function.
+ */
+static JdwpError FinishInvoke(JdwpState*, Request& request, ExpandBuf* pReply,
+ ObjectId thread_id, ObjectId object_id,
+ RefTypeId class_id, MethodId method_id, bool is_constructor)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ CHECK(!is_constructor || object_id != 0);
+
+ int32_t arg_count = request.ReadSigned32("argument count");
+
+ VLOG(jdwp) << StringPrintf(" --> thread_id=%#llx object_id=%#llx", thread_id, object_id);
+ VLOG(jdwp) << StringPrintf(" class_id=%#llx method_id=%x %s.%s", class_id,
+ method_id, Dbg::GetClassName(class_id).c_str(),
+ Dbg::GetMethodName(method_id).c_str());
+ VLOG(jdwp) << StringPrintf(" %d args:", arg_count);
+
+ UniquePtr<JdwpTag[]> argTypes(arg_count > 0 ? new JdwpTag[arg_count] : NULL);
+ UniquePtr<uint64_t[]> argValues(arg_count > 0 ? new uint64_t[arg_count] : NULL);
+ for (int32_t i = 0; i < arg_count; ++i) {
+ argTypes[i] = request.ReadTag();
+ size_t width = Dbg::GetTagWidth(argTypes[i]);
+ argValues[i] = request.ReadValue(width);
+ VLOG(jdwp) << " " << argTypes[i] << StringPrintf("(%zd): %#llx", width, argValues[i]);
+ }
+
+ uint32_t options = request.ReadUnsigned32("InvokeOptions bit flags");
+ VLOG(jdwp) << StringPrintf(" options=0x%04x%s%s", options,
+ (options & INVOKE_SINGLE_THREADED) ? " (SINGLE_THREADED)" : "",
+ (options & INVOKE_NONVIRTUAL) ? " (NONVIRTUAL)" : "");
+
+ JdwpTag resultTag;
+ uint64_t resultValue;
+ ObjectId exceptObjId;
+ JdwpError err = Dbg::InvokeMethod(thread_id, object_id, class_id, method_id, arg_count, argValues.get(), argTypes.get(), options, &resultTag, &resultValue, &exceptObjId);
+ if (err != ERR_NONE) {
+ return err;
+ }
+
+ if (err == ERR_NONE) {
+ if (is_constructor) {
+ // If we invoked a constructor (which actually returns void), return the receiver,
+ // unless we threw, in which case we return NULL.
+ resultTag = JT_OBJECT;
+ resultValue = (exceptObjId == 0) ? object_id : 0;
+ }
+
+ size_t width = Dbg::GetTagWidth(resultTag);
+ expandBufAdd1(pReply, resultTag);
+ if (width != 0) {
+ WriteValue(pReply, width, resultValue);
+ }
+ expandBufAdd1(pReply, JT_OBJECT);
+ expandBufAddObjectId(pReply, exceptObjId);
+
+ VLOG(jdwp) << " --> returned " << resultTag << StringPrintf(" %#llx (except=%#llx)", resultValue, exceptObjId);
+
+ /* show detailed debug output */
+ if (resultTag == JT_STRING && exceptObjId == 0) {
+ if (resultValue != 0) {
+ VLOG(jdwp) << " string '" << Dbg::StringToUtf8(resultValue) << "'";
+ } else {
+ VLOG(jdwp) << " string (null)";
+ }
+ }
+ }
+
+ return err;
+}
+
+static JdwpError VM_Version(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ // Text information on runtime version.
+ std::string version(StringPrintf("Android Runtime %s", Runtime::Current()->GetVersion()));
+ expandBufAddUtf8String(pReply, version);
+
+ // JDWP version numbers, major and minor.
+ expandBufAdd4BE(pReply, 1);
+ expandBufAdd4BE(pReply, 6);
+
+ // "java.version".
+ expandBufAddUtf8String(pReply, "1.6.0");
+
+ // "java.vm.name".
+ expandBufAddUtf8String(pReply, "Dalvik");
+
+ return ERR_NONE;
+}
+
+/*
+ * Given a class JNI signature (e.g. "Ljava/lang/Error;"), return the
+ * referenceTypeID. We need to send back more than one if the class has
+ * been loaded by multiple class loaders.
+ */
+static JdwpError VM_ClassesBySignature(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::string classDescriptor(request.ReadUtf8String());
+
+ std::vector<RefTypeId> ids;
+ Dbg::FindLoadedClassBySignature(classDescriptor.c_str(), ids);
+
+ expandBufAdd4BE(pReply, ids.size());
+
+ for (size_t i = 0; i < ids.size(); ++i) {
+ // Get class vs. interface and status flags.
+ JDWP::JdwpTypeTag type_tag;
+ uint32_t class_status;
+ JDWP::JdwpError status = Dbg::GetClassInfo(ids[i], &type_tag, &class_status, NULL);
+ if (status != ERR_NONE) {
+ return status;
+ }
+
+ expandBufAdd1(pReply, type_tag);
+ expandBufAddRefTypeId(pReply, ids[i]);
+ expandBufAdd4BE(pReply, class_status);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Handle request for the thread IDs of all running threads.
+ *
+ * We exclude ourselves from the list, because we don't allow ourselves
+ * to be suspended, and that violates some JDWP expectations.
+ */
+static JdwpError VM_AllThreads(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::vector<ObjectId> thread_ids;
+ Dbg::GetThreads(0, thread_ids);
+
+ expandBufAdd4BE(pReply, thread_ids.size());
+ for (uint32_t i = 0; i < thread_ids.size(); ++i) {
+ expandBufAddObjectId(pReply, thread_ids[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * List all thread groups that do not have a parent.
+ */
+static JdwpError VM_TopLevelThreadGroups(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ /*
+ * TODO: maintain a list of parentless thread groups in the VM.
+ *
+ * For now, just return "system". Application threads are created
+ * in "main", which is a child of "system".
+ */
+ uint32_t groups = 1;
+ expandBufAdd4BE(pReply, groups);
+ ObjectId thread_group_id = Dbg::GetSystemThreadGroupId();
+ expandBufAddObjectId(pReply, thread_group_id);
+
+ return ERR_NONE;
+}
+
+/*
+ * Respond with the sizes of the basic debugger types.
+ *
+ * All IDs are 8 bytes.
+ */
+static JdwpError VM_IDSizes(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAdd4BE(pReply, sizeof(FieldId));
+ expandBufAdd4BE(pReply, sizeof(MethodId));
+ expandBufAdd4BE(pReply, sizeof(ObjectId));
+ expandBufAdd4BE(pReply, sizeof(RefTypeId));
+ expandBufAdd4BE(pReply, sizeof(FrameId));
+ return ERR_NONE;
+}
+
+static JdwpError VM_Dispose(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Dbg::Disposed();
+ return ERR_NONE;
+}
+
+/*
+ * Suspend the execution of the application running in the VM (i.e. suspend
+ * all threads).
+ *
+ * This needs to increment the "suspend count" on all threads.
+ */
+static JdwpError VM_Suspend(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Thread* self = Thread::Current();
+ self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSuspension);
+ Dbg::SuspendVM();
+ self->TransitionFromSuspendedToRunnable();
+ return ERR_NONE;
+}
+
+/*
+ * Resume execution. Decrements the "suspend count" of all threads.
+ */
+static JdwpError VM_Resume(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ Dbg::ResumeVM();
+ return ERR_NONE;
+}
+
+static JdwpError VM_Exit(JdwpState* state, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ uint32_t exit_status = request.ReadUnsigned32("exit_status");
+ state->ExitAfterReplying(exit_status);
+ return ERR_NONE;
+}
+
+/*
+ * Create a new string in the VM and return its ID.
+ *
+ * (Ctrl-Shift-I in Eclipse on an array of objects causes it to create the
+ * string "java.util.Arrays".)
+ */
+static JdwpError VM_CreateString(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::string str(request.ReadUtf8String());
+ ObjectId stringId = Dbg::CreateString(str);
+ if (stringId == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ expandBufAddObjectId(pReply, stringId);
+ return ERR_NONE;
+}
+
+static JdwpError VM_ClassPaths(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAddUtf8String(pReply, "/");
+
+ std::vector<std::string> class_path;
+ Split(Runtime::Current()->GetClassPathString(), ':', class_path);
+ expandBufAdd4BE(pReply, class_path.size());
+ for (size_t i = 0; i < class_path.size(); ++i) {
+ expandBufAddUtf8String(pReply, class_path[i]);
+ }
+
+ std::vector<std::string> boot_class_path;
+ Split(Runtime::Current()->GetBootClassPathString(), ':', boot_class_path);
+ expandBufAdd4BE(pReply, boot_class_path.size());
+ for (size_t i = 0; i < boot_class_path.size(); ++i) {
+ expandBufAddUtf8String(pReply, boot_class_path[i]);
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError VM_DisposeObjects(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ size_t object_count = request.ReadUnsigned32("object_count");
+ for (size_t i = 0; i < object_count; ++i) {
+ ObjectId object_id = request.ReadObjectId();
+ uint32_t reference_count = request.ReadUnsigned32("reference_count");
+ Dbg::DisposeObject(object_id, reference_count);
+ }
+ return ERR_NONE;
+}
+
+static JdwpError VM_Capabilities(JdwpState*, Request&, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ expandBufAdd1(reply, false); // canWatchFieldModification
+ expandBufAdd1(reply, false); // canWatchFieldAccess
+ expandBufAdd1(reply, true); // canGetBytecodes
+ expandBufAdd1(reply, true); // canGetSyntheticAttribute
+ expandBufAdd1(reply, true); // canGetOwnedMonitorInfo
+ expandBufAdd1(reply, true); // canGetCurrentContendedMonitor
+ expandBufAdd1(reply, true); // canGetMonitorInfo
+ return ERR_NONE;
+}
+
+static JdwpError VM_CapabilitiesNew(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+
+ // The first few capabilities are the same as those reported by the older call.
+ VM_Capabilities(NULL, request, reply);
+
+ expandBufAdd1(reply, false); // canRedefineClasses
+ expandBufAdd1(reply, false); // canAddMethod
+ expandBufAdd1(reply, false); // canUnrestrictedlyRedefineClasses
+ expandBufAdd1(reply, false); // canPopFrames
+ expandBufAdd1(reply, false); // canUseInstanceFilters
+ expandBufAdd1(reply, false); // canGetSourceDebugExtension
+ expandBufAdd1(reply, false); // canRequestVMDeathEvent
+ expandBufAdd1(reply, false); // canSetDefaultStratum
+ expandBufAdd1(reply, true); // 1.6: canGetInstanceInfo
+ expandBufAdd1(reply, false); // 1.6: canRequestMonitorEvents
+ expandBufAdd1(reply, true); // 1.6: canGetMonitorFrameInfo
+ expandBufAdd1(reply, false); // 1.6: canUseSourceNameFilters
+ expandBufAdd1(reply, false); // 1.6: canGetConstantPool
+ expandBufAdd1(reply, false); // 1.6: canForceEarlyReturn
+
+ // Fill in reserved22 through reserved32; note count started at 1.
+ for (size_t i = 22; i <= 32; ++i) {
+ expandBufAdd1(reply, false);
+ }
+ return ERR_NONE;
+}
+
+static JdwpError VM_AllClassesImpl(ExpandBuf* pReply, bool descriptor_and_status, bool generic)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ std::vector<JDWP::RefTypeId> classes;
+ Dbg::GetClassList(classes);
+
+ expandBufAdd4BE(pReply, classes.size());
+
+ for (size_t i = 0; i < classes.size(); ++i) {
+ static const char genericSignature[1] = "";
+ JDWP::JdwpTypeTag type_tag;
+ std::string descriptor;
+ uint32_t class_status;
+ JDWP::JdwpError status = Dbg::GetClassInfo(classes[i], &type_tag, &class_status, &descriptor);
+ if (status != ERR_NONE) {
+ return status;
+ }
+
+ expandBufAdd1(pReply, type_tag);
+ expandBufAddRefTypeId(pReply, classes[i]);
+ if (descriptor_and_status) {
+ expandBufAddUtf8String(pReply, descriptor);
+ if (generic) {
+ expandBufAddUtf8String(pReply, genericSignature);
+ }
+ expandBufAdd4BE(pReply, class_status);
+ }
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError VM_AllClasses(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return VM_AllClassesImpl(pReply, true, false);
+}
+
+static JdwpError VM_AllClassesWithGeneric(JdwpState*, Request&, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return VM_AllClassesImpl(pReply, true, true);
+}
+
+static JdwpError VM_InstanceCounts(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ int32_t class_count = request.ReadSigned32("class count");
+ if (class_count < 0) {
+ return ERR_ILLEGAL_ARGUMENT;
+ }
+ std::vector<RefTypeId> class_ids;
+ for (int32_t i = 0; i < class_count; ++i) {
+ class_ids.push_back(request.ReadRefTypeId());
+ }
+
+ std::vector<uint64_t> counts;
+ JdwpError rc = Dbg::GetInstanceCounts(class_ids, counts);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ expandBufAdd4BE(pReply, counts.size());
+ for (size_t i = 0; i < counts.size(); ++i) {
+ expandBufAdd8BE(pReply, counts[i]);
+ }
+ return ERR_NONE;
+}
+
+static JdwpError RT_Modifiers(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::GetModifiers(refTypeId, pReply);
+}
+
+/*
+ * Get values from static fields in a reference type.
+ */
+static JdwpError RT_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ int32_t field_count = request.ReadSigned32("field count");
+ expandBufAdd4BE(pReply, field_count);
+ for (int32_t i = 0; i < field_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+ JdwpError status = Dbg::GetStaticFieldValue(refTypeId, fieldId, pReply);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Get the name of the source file in which a reference type was declared.
+ */
+static JdwpError RT_SourceFile(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ std::string source_file;
+ JdwpError status = Dbg::GetSourceFile(refTypeId, source_file);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAddUtf8String(pReply, source_file);
+ return ERR_NONE;
+}
+
+/*
+ * Return the current status of the reference type.
+ */
+static JdwpError RT_Status(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ JDWP::JdwpTypeTag type_tag;
+ uint32_t class_status;
+ JDWP::JdwpError status = Dbg::GetClassInfo(refTypeId, &type_tag, &class_status, NULL);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAdd4BE(pReply, class_status);
+ return ERR_NONE;
+}
+
+/*
+ * Return interfaces implemented directly by this class.
+ */
+static JdwpError RT_Interfaces(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredInterfaces(refTypeId, pReply);
+}
+
+/*
+ * Return the class object corresponding to this type.
+ */
+static JdwpError RT_ClassObject(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ ObjectId class_object_id;
+ JdwpError status = Dbg::GetClassObject(refTypeId, class_object_id);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ VLOG(jdwp) << StringPrintf(" --> ObjectId %#llx", class_object_id);
+ expandBufAddObjectId(pReply, class_object_id);
+ return ERR_NONE;
+}
+
+/*
+ * Returns the value of the SourceDebugExtension attribute.
+ *
+ * JDB seems interested, but DEX files don't currently support this.
+ */
+static JdwpError RT_SourceDebugExtension(JdwpState*, Request&, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ /* referenceTypeId in, string out */
+ return ERR_ABSENT_INFORMATION;
+}
+
+static JdwpError RT_Signature(JdwpState*, Request& request, ExpandBuf* pReply, bool with_generic)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+
+ std::string signature;
+ JdwpError status = Dbg::GetSignature(refTypeId, signature);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAddUtf8String(pReply, signature);
+ if (with_generic) {
+ expandBufAddUtf8String(pReply, "");
+ }
+ return ERR_NONE;
+}
+
+static JdwpError RT_Signature(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return RT_Signature(state, request, pReply, false);
+}
+
+static JdwpError RT_SignatureWithGeneric(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return RT_Signature(state, request, pReply, true);
+}
+
+/*
+ * Return the instance of java.lang.ClassLoader that loaded the specified
+ * reference type, or null if it was loaded by the system loader.
+ */
+static JdwpError RT_ClassLoader(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::GetClassLoader(refTypeId, pReply);
+}
+
+/*
+ * Given a referenceTypeId, return a block of stuff that describes the
+ * fields declared by a class.
+ */
+static JdwpError RT_FieldsWithGeneric(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredFields(refTypeId, true, pReply);
+}
+
+// Obsolete equivalent of FieldsWithGeneric, without the generic type information.
+static JdwpError RT_Fields(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredFields(refTypeId, false, pReply);
+}
+
+/*
+ * Given a referenceTypeID, return a block of goodies describing the
+ * methods declared by a class.
+ */
+static JdwpError RT_MethodsWithGeneric(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredMethods(refTypeId, true, pReply);
+}
+
+// Obsolete equivalent of MethodsWithGeneric, without the generic type information.
+static JdwpError RT_Methods(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ return Dbg::OutputDeclaredMethods(refTypeId, false, pReply);
+}
+
+static JdwpError RT_Instances(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ int32_t max_count = request.ReadSigned32("max count");
+ if (max_count < 0) {
+ return ERR_ILLEGAL_ARGUMENT;
+ }
+
+ std::vector<ObjectId> instances;
+ JdwpError rc = Dbg::GetInstances(class_id, max_count, instances);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ return WriteTaggedObjectList(reply, instances);
+}
+
+/*
+ * Return the immediate superclass of a class.
+ */
+static JdwpError CT_Superclass(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ RefTypeId superClassId;
+ JdwpError status = Dbg::GetSuperclass(class_id, superClassId);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ expandBufAddRefTypeId(pReply, superClassId);
+ return ERR_NONE;
+}
+
+/*
+ * Set static class values.
+ */
+static JdwpError CT_SetValues(JdwpState* , Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ int32_t values_count = request.ReadSigned32("values count");
+
+ UNUSED(class_id);
+
+ for (int32_t i = 0; i < values_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+ JDWP::JdwpTag fieldTag = Dbg::GetStaticFieldBasicTag(fieldId);
+ size_t width = Dbg::GetTagWidth(fieldTag);
+ uint64_t value = request.ReadValue(width);
+
+ VLOG(jdwp) << " --> field=" << fieldId << " tag=" << fieldTag << " --> " << value;
+ JdwpError status = Dbg::SetStaticFieldValue(fieldId, value, width);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Invoke a static method.
+ *
+ * Example: Eclipse sometimes uses java/lang/Class.forName(String s) on
+ * values in the "variables" display.
+ */
+static JdwpError CT_InvokeMethod(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ ObjectId thread_id = request.ReadThreadId();
+ MethodId method_id = request.ReadMethodId();
+
+ return FinishInvoke(state, request, pReply, thread_id, 0, class_id, method_id, false);
+}
+
+/*
+ * Create a new object of the requested type, and invoke the specified
+ * constructor.
+ *
+ * Example: in IntelliJ, create a watch on "new String(myByteArray)" to
+ * see the contents of a byte[] as a string.
+ */
+static JdwpError CT_NewInstance(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ ObjectId thread_id = request.ReadThreadId();
+ MethodId method_id = request.ReadMethodId();
+
+ ObjectId object_id;
+ JdwpError status = Dbg::CreateObject(class_id, object_id);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ if (object_id == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ return FinishInvoke(state, request, pReply, thread_id, object_id, class_id, method_id, true);
+}
+
+/*
+ * Create a new array object of the requested type and length.
+ */
+static JdwpError AT_newInstance(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId arrayTypeId = request.ReadRefTypeId();
+ int32_t length = request.ReadSigned32("length");
+
+ ObjectId object_id;
+ JdwpError status = Dbg::CreateArrayObject(arrayTypeId, length, object_id);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ if (object_id == 0) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ expandBufAdd1(pReply, JT_ARRAY);
+ expandBufAddObjectId(pReply, object_id);
+ return ERR_NONE;
+}
+
+/*
+ * Return line number information for the method, if present.
+ */
+static JdwpError M_LineTable(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId refTypeId = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ Dbg::OutputLineTable(refTypeId, method_id, pReply);
+
+ return ERR_NONE;
+}
+
+static JdwpError M_VariableTable(JdwpState*, Request& request, ExpandBuf* pReply,
+ bool generic)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ // We could return ERR_ABSENT_INFORMATION here if the DEX file was built without local variable
+ // information. That will cause Eclipse to make a best-effort attempt at displaying local
+ // variables anonymously. However, the attempt isn't very good, so we're probably better off just
+ // not showing anything.
+ Dbg::OutputVariableTable(class_id, method_id, generic, pReply);
+ return ERR_NONE;
+}
+
+static JdwpError M_VariableTable(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return M_VariableTable(state, request, pReply, false);
+}
+
+static JdwpError M_VariableTableWithGeneric(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return M_VariableTable(state, request, pReply, true);
+}
+
+static JdwpError M_Bytecodes(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_id = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ std::vector<uint8_t> bytecodes;
+ JdwpError rc = Dbg::GetBytecodes(class_id, method_id, bytecodes);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ expandBufAdd4BE(reply, bytecodes.size());
+ for (size_t i = 0; i < bytecodes.size(); ++i) {
+ expandBufAdd1(reply, bytecodes[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Given an object reference, return the runtime type of the object
+ * (class or array).
+ *
+ * This can get called on different things, e.g. thread_id gets
+ * passed in here.
+ */
+static JdwpError OR_ReferenceType(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::GetReferenceType(object_id, pReply);
+}
+
+/*
+ * Get values from the fields of an object.
+ */
+static JdwpError OR_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ int32_t field_count = request.ReadSigned32("field count");
+
+ expandBufAdd4BE(pReply, field_count);
+ for (int32_t i = 0; i < field_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+ JdwpError status = Dbg::GetFieldValue(object_id, fieldId, pReply);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set values in the fields of an object.
+ */
+static JdwpError OR_SetValues(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ int32_t field_count = request.ReadSigned32("field count");
+
+ for (int32_t i = 0; i < field_count; ++i) {
+ FieldId fieldId = request.ReadFieldId();
+
+ JDWP::JdwpTag fieldTag = Dbg::GetFieldBasicTag(fieldId);
+ size_t width = Dbg::GetTagWidth(fieldTag);
+ uint64_t value = request.ReadValue(width);
+
+ VLOG(jdwp) << " --> fieldId=" << fieldId << " tag=" << fieldTag << "(" << width << ") value=" << value;
+ JdwpError status = Dbg::SetFieldValue(object_id, fieldId, value, width);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError OR_MonitorInfo(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::GetMonitorInfo(object_id, reply);
+}
+
+/*
+ * Invoke an instance method. The invocation must occur in the specified
+ * thread, which must have been suspended by an event.
+ *
+ * The call is synchronous. All threads in the VM are resumed, unless the
+ * SINGLE_THREADED flag is set.
+ *
+ * If you ask Eclipse to "inspect" an object (or ask JDB to "print" an
+ * object), it will try to invoke the object's toString() function. This
+ * feature becomes crucial when examining ArrayLists with Eclipse.
+ */
+static JdwpError OR_InvokeMethod(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ ObjectId thread_id = request.ReadThreadId();
+ RefTypeId class_id = request.ReadRefTypeId();
+ MethodId method_id = request.ReadMethodId();
+
+ return FinishInvoke(state, request, pReply, thread_id, object_id, class_id, method_id, false);
+}
+
+static JdwpError OR_DisableCollection(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::DisableCollection(object_id);
+}
+
+static JdwpError OR_EnableCollection(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ return Dbg::EnableCollection(object_id);
+}
+
+static JdwpError OR_IsCollected(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ bool is_collected;
+ JdwpError rc = Dbg::IsCollected(object_id, is_collected);
+ expandBufAdd1(pReply, is_collected ? 1 : 0);
+ return rc;
+}
+
+static JdwpError OR_ReferringObjects(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId object_id = request.ReadObjectId();
+ int32_t max_count = request.ReadSigned32("max count");
+ if (max_count < 0) {
+ return ERR_ILLEGAL_ARGUMENT;
+ }
+
+ std::vector<ObjectId> referring_objects;
+ JdwpError rc = Dbg::GetReferringObjects(object_id, max_count, referring_objects);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ return WriteTaggedObjectList(reply, referring_objects);
+}
+
+/*
+ * Return the string value in a string object.
+ */
+static JdwpError SR_Value(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId stringObject = request.ReadObjectId();
+ std::string str(Dbg::StringToUtf8(stringObject));
+
+ VLOG(jdwp) << StringPrintf(" --> %s", PrintableString(str).c_str());
+
+ expandBufAddUtf8String(pReply, str);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return a thread's name.
+ */
+static JdwpError TR_Name(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ std::string name;
+ JdwpError error = Dbg::GetThreadName(thread_id, name);
+ if (error != ERR_NONE) {
+ return error;
+ }
+ VLOG(jdwp) << StringPrintf(" Name of thread %#llx is \"%s\"", thread_id, name.c_str());
+ expandBufAddUtf8String(pReply, name);
+
+ return ERR_NONE;
+}
+
+/*
+ * Suspend the specified thread.
+ *
+ * It's supposed to remain suspended even if interpreted code wants to
+ * resume it; only the JDI is allowed to resume it.
+ */
+static JdwpError TR_Suspend(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ if (thread_id == Dbg::GetThreadSelfId()) {
+ LOG(INFO) << " Warning: ignoring request to suspend self";
+ return ERR_THREAD_NOT_SUSPENDED;
+ }
+
+ Thread* self = Thread::Current();
+ self->TransitionFromRunnableToSuspended(kWaitingForDebuggerSend);
+ JdwpError result = Dbg::SuspendThread(thread_id);
+ self->TransitionFromSuspendedToRunnable();
+ return result;
+}
+
+/*
+ * Resume the specified thread.
+ */
+static JdwpError TR_Resume(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ if (thread_id == Dbg::GetThreadSelfId()) {
+ LOG(INFO) << " Warning: ignoring request to resume self";
+ return ERR_NONE;
+ }
+
+ Dbg::ResumeThread(thread_id);
+ return ERR_NONE;
+}
+
+/*
+ * Return status of specified thread.
+ */
+static JdwpError TR_Status(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ JDWP::JdwpThreadStatus threadStatus;
+ JDWP::JdwpSuspendStatus suspendStatus;
+ JdwpError error = Dbg::GetThreadStatus(thread_id, &threadStatus, &suspendStatus);
+ if (error != ERR_NONE) {
+ return error;
+ }
+
+ VLOG(jdwp) << " --> " << threadStatus << ", " << suspendStatus;
+
+ expandBufAdd4BE(pReply, threadStatus);
+ expandBufAdd4BE(pReply, suspendStatus);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the thread group that the specified thread is a member of.
+ */
+static JdwpError TR_ThreadGroup(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ return Dbg::GetThreadGroup(thread_id, pReply);
+}
+
+/*
+ * Return the current call stack of a suspended thread.
+ *
+ * If the thread isn't suspended, the error code isn't defined, but should
+ * be THREAD_NOT_SUSPENDED.
+ */
+static JdwpError TR_Frames(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ uint32_t start_frame = request.ReadUnsigned32("start frame");
+ uint32_t length = request.ReadUnsigned32("length");
+
+ size_t actual_frame_count;
+ JdwpError error = Dbg::GetThreadFrameCount(thread_id, actual_frame_count);
+ if (error != ERR_NONE) {
+ return error;
+ }
+
+ if (actual_frame_count <= 0) {
+ return ERR_THREAD_NOT_SUSPENDED; // 0 means no managed frames (which means "in native").
+ }
+
+ if (start_frame > actual_frame_count) {
+ return ERR_INVALID_INDEX;
+ }
+ if (length == static_cast<uint32_t>(-1)) {
+ length = actual_frame_count - start_frame;
+ }
+ if (start_frame + length > actual_frame_count) {
+ return ERR_INVALID_LENGTH;
+ }
+
+ return Dbg::GetThreadFrames(thread_id, start_frame, length, pReply);
+}
+
+/*
+ * Returns the #of frames on the specified thread, which must be suspended.
+ */
+static JdwpError TR_FrameCount(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ size_t frame_count;
+ JdwpError rc = Dbg::GetThreadFrameCount(thread_id, frame_count);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ expandBufAdd4BE(pReply, static_cast<uint32_t>(frame_count));
+
+ return ERR_NONE;
+}
+
+static JdwpError TR_OwnedMonitors(Request& request, ExpandBuf* reply, bool with_stack_depths)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ std::vector<ObjectId> monitors;
+ std::vector<uint32_t> stack_depths;
+ JdwpError rc = Dbg::GetOwnedMonitors(thread_id, monitors, stack_depths);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ expandBufAdd4BE(reply, monitors.size());
+ for (size_t i = 0; i < monitors.size(); ++i) {
+ rc = WriteTaggedObject(reply, monitors[i]);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ if (with_stack_depths) {
+ expandBufAdd4BE(reply, stack_depths[i]);
+ }
+ }
+ return ERR_NONE;
+}
+
+static JdwpError TR_OwnedMonitors(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return TR_OwnedMonitors(request, reply, false);
+}
+
+static JdwpError TR_OwnedMonitorsStackDepthInfo(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ return TR_OwnedMonitors(request, reply, true);
+}
+
+static JdwpError TR_CurrentContendedMonitor(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+
+ ObjectId contended_monitor;
+ JdwpError rc = Dbg::GetContendedMonitor(thread_id, contended_monitor);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+ return WriteTaggedObject(reply, contended_monitor);
+}
+
+static JdwpError TR_Interrupt(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ return Dbg::Interrupt(thread_id);
+}
+
+/*
+ * Return the debug suspend count for the specified thread.
+ *
+ * (The thread *might* still be running -- it might not have examined
+ * its suspend count recently.)
+ */
+static JdwpError TR_DebugSuspendCount(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ return Dbg::GetThreadDebugSuspendCount(thread_id, pReply);
+}
+
+/*
+ * Return the name of a thread group.
+ *
+ * The Eclipse debugger recognizes "main" and "system" as special.
+ */
+static JdwpError TGR_Name(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_group_id = request.ReadThreadGroupId();
+
+ expandBufAddUtf8String(pReply, Dbg::GetThreadGroupName(thread_group_id));
+
+ return ERR_NONE;
+}
+
+/*
+ * Returns the thread group -- if any -- that contains the specified
+ * thread group.
+ */
+static JdwpError TGR_Parent(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_group_id = request.ReadThreadGroupId();
+
+ ObjectId parentGroup = Dbg::GetThreadGroupParent(thread_group_id);
+ expandBufAddObjectId(pReply, parentGroup);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the active threads and thread groups that are part of the
+ * specified thread group.
+ */
+static JdwpError TGR_Children(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_group_id = request.ReadThreadGroupId();
+
+ std::vector<ObjectId> thread_ids;
+ Dbg::GetThreads(thread_group_id, thread_ids);
+ expandBufAdd4BE(pReply, thread_ids.size());
+ for (uint32_t i = 0; i < thread_ids.size(); ++i) {
+ expandBufAddObjectId(pReply, thread_ids[i]);
+ }
+
+ std::vector<ObjectId> child_thread_groups_ids;
+ Dbg::GetChildThreadGroups(thread_group_id, child_thread_groups_ids);
+ expandBufAdd4BE(pReply, child_thread_groups_ids.size());
+ for (uint32_t i = 0; i < child_thread_groups_ids.size(); ++i) {
+ expandBufAddObjectId(pReply, child_thread_groups_ids[i]);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the #of components in the array.
+ */
+static JdwpError AR_Length(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId array_id = request.ReadArrayId();
+
+ int length;
+ JdwpError status = Dbg::GetArrayLength(array_id, length);
+ if (status != ERR_NONE) {
+ return status;
+ }
+ VLOG(jdwp) << " --> " << length;
+
+ expandBufAdd4BE(pReply, length);
+
+ return ERR_NONE;
+}
+
+/*
+ * Return the values from an array.
+ */
+static JdwpError AR_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId array_id = request.ReadArrayId();
+ uint32_t offset = request.ReadUnsigned32("offset");
+ uint32_t length = request.ReadUnsigned32("length");
+ return Dbg::OutputArray(array_id, offset, length, pReply);
+}
+
+/*
+ * Set values in an array.
+ */
+static JdwpError AR_SetValues(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId array_id = request.ReadArrayId();
+ uint32_t offset = request.ReadUnsigned32("offset");
+ uint32_t count = request.ReadUnsigned32("count");
+ return Dbg::SetArrayElements(array_id, offset, count, request);
+}
+
+static JdwpError CLR_VisibleClasses(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ request.ReadObjectId(); // classLoaderObject
+ // TODO: we should only return classes which have the given class loader as a defining or
+ // initiating loader. The former would be easy; the latter is hard, because we don't have
+ // any such notion.
+ return VM_AllClassesImpl(pReply, false, false);
+}
+
+/*
+ * Set an event trigger.
+ *
+ * Reply with a requestID.
+ */
+static JdwpError ER_Set(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ JdwpEventKind event_kind = request.ReadEnum1<JdwpEventKind>("event kind");
+ JdwpSuspendPolicy suspend_policy = request.ReadEnum1<JdwpSuspendPolicy>("suspend policy");
+ int32_t modifier_count = request.ReadSigned32("modifier count");
+
+ CHECK_LT(modifier_count, 256); /* reasonableness check */
+
+ JdwpEvent* pEvent = EventAlloc(modifier_count);
+ pEvent->eventKind = event_kind;
+ pEvent->suspend_policy = suspend_policy;
+ pEvent->modCount = modifier_count;
+
+ /*
+ * Read modifiers. Ordering may be significant (see explanation of Count
+ * mods in JDWP doc).
+ */
+ for (int32_t i = 0; i < modifier_count; ++i) {
+ JdwpEventMod& mod = pEvent->mods[i];
+ mod.modKind = request.ReadModKind();
+ switch (mod.modKind) {
+ case MK_COUNT:
+ {
+ // Report once, when "--count" reaches 0.
+ uint32_t count = request.ReadUnsigned32("count");
+ if (count == 0) {
+ return ERR_INVALID_COUNT;
+ }
+ mod.count.count = count;
+ }
+ break;
+ case MK_CONDITIONAL:
+ {
+ // Conditional on expression.
+ uint32_t exprId = request.ReadUnsigned32("expr id");
+ mod.conditional.exprId = exprId;
+ }
+ break;
+ case MK_THREAD_ONLY:
+ {
+ // Only report events in specified thread.
+ ObjectId thread_id = request.ReadThreadId();
+ mod.threadOnly.threadId = thread_id;
+ }
+ break;
+ case MK_CLASS_ONLY:
+ {
+ // For ClassPrepare, MethodEntry.
+ RefTypeId class_id = request.ReadRefTypeId();
+ mod.classOnly.refTypeId = class_id;
+ }
+ break;
+ case MK_CLASS_MATCH:
+ {
+ // Restrict events to matching classes.
+ // pattern is "java.foo.*", we want "java/foo/*".
+ std::string pattern(request.ReadUtf8String());
+ std::replace(pattern.begin(), pattern.end(), '.', '/');
+ mod.classMatch.classPattern = strdup(pattern.c_str());
+ }
+ break;
+ case MK_CLASS_EXCLUDE:
+ {
+ // Restrict events to non-matching classes.
+ // pattern is "java.foo.*", we want "java/foo/*".
+ std::string pattern(request.ReadUtf8String());
+ std::replace(pattern.begin(), pattern.end(), '.', '/');
+ mod.classExclude.classPattern = strdup(pattern.c_str());
+ }
+ break;
+ case MK_LOCATION_ONLY:
+ {
+ // Restrict certain events based on location.
+ JdwpLocation location = request.ReadLocation();
+ mod.locationOnly.loc = location;
+ }
+ break;
+ case MK_EXCEPTION_ONLY:
+ {
+ // Modifies EK_EXCEPTION events,
+ mod.exceptionOnly.refTypeId = request.ReadRefTypeId(); // null => all exceptions.
+ mod.exceptionOnly.caught = request.ReadEnum1<uint8_t>("caught");
+ mod.exceptionOnly.uncaught = request.ReadEnum1<uint8_t>("uncaught");
+ }
+ break;
+ case MK_FIELD_ONLY:
+ {
+ // For field access/modification events.
+ RefTypeId declaring = request.ReadRefTypeId();
+ FieldId fieldId = request.ReadFieldId();
+ mod.fieldOnly.refTypeId = declaring;
+ mod.fieldOnly.fieldId = fieldId;
+ }
+ break;
+ case MK_STEP:
+ {
+ // For use with EK_SINGLE_STEP.
+ ObjectId thread_id = request.ReadThreadId();
+ uint32_t size = request.ReadUnsigned32("step size");
+ uint32_t depth = request.ReadUnsigned32("step depth");
+ VLOG(jdwp) << StringPrintf(" Step: thread=%#llx", thread_id)
+ << " size=" << JdwpStepSize(size) << " depth=" << JdwpStepDepth(depth);
+
+ mod.step.threadId = thread_id;
+ mod.step.size = size;
+ mod.step.depth = depth;
+ }
+ break;
+ case MK_INSTANCE_ONLY:
+ {
+ // Report events related to a specific object.
+ ObjectId instance = request.ReadObjectId();
+ mod.instanceOnly.objectId = instance;
+ }
+ break;
+ default:
+ LOG(WARNING) << "GLITCH: unsupported modKind=" << mod.modKind;
+ break;
+ }
+ }
+
+ /*
+ * We reply with an integer "requestID".
+ */
+ uint32_t requestId = state->NextEventSerial();
+ expandBufAdd4BE(pReply, requestId);
+
+ pEvent->requestId = requestId;
+
+ VLOG(jdwp) << StringPrintf(" --> event requestId=%#x", requestId);
+
+ /* add it to the list */
+ JdwpError err = state->RegisterEvent(pEvent);
+ if (err != ERR_NONE) {
+ /* registration failed, probably because event is bogus */
+ EventFree(pEvent);
+ LOG(WARNING) << "WARNING: event request rejected";
+ }
+ return err;
+}
+
+static JdwpError ER_Clear(JdwpState* state, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ request.ReadEnum1<JdwpEventKind>("event kind");
+ uint32_t requestId = request.ReadUnsigned32("request id");
+
+ // Failure to find an event with a matching ID is a no-op
+ // and does not return an error.
+ state->UnregisterEventById(requestId);
+ return ERR_NONE;
+}
+
+/*
+ * Return the values of arguments and local variables.
+ */
+static JdwpError SF_GetValues(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ FrameId frame_id = request.ReadFrameId();
+ int32_t slot_count = request.ReadSigned32("slot count");
+
+ expandBufAdd4BE(pReply, slot_count); /* "int values" */
+ for (int32_t i = 0; i < slot_count; ++i) {
+ uint32_t slot = request.ReadUnsigned32("slot");
+ JDWP::JdwpTag reqSigByte = request.ReadTag();
+
+ VLOG(jdwp) << " --> slot " << slot << " " << reqSigByte;
+
+ size_t width = Dbg::GetTagWidth(reqSigByte);
+ uint8_t* ptr = expandBufAddSpace(pReply, width+1);
+ Dbg::GetLocalValue(thread_id, frame_id, slot, reqSigByte, ptr, width);
+ }
+
+ return ERR_NONE;
+}
+
+/*
+ * Set the values of arguments and local variables.
+ */
+static JdwpError SF_SetValues(JdwpState*, Request& request, ExpandBuf*)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ FrameId frame_id = request.ReadFrameId();
+ int32_t slot_count = request.ReadSigned32("slot count");
+
+ for (int32_t i = 0; i < slot_count; ++i) {
+ uint32_t slot = request.ReadUnsigned32("slot");
+ JDWP::JdwpTag sigByte = request.ReadTag();
+ size_t width = Dbg::GetTagWidth(sigByte);
+ uint64_t value = request.ReadValue(width);
+
+ VLOG(jdwp) << " --> slot " << slot << " " << sigByte << " " << value;
+ Dbg::SetLocalValue(thread_id, frame_id, slot, sigByte, value, width);
+ }
+
+ return ERR_NONE;
+}
+
+static JdwpError SF_ThisObject(JdwpState*, Request& request, ExpandBuf* reply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ ObjectId thread_id = request.ReadThreadId();
+ FrameId frame_id = request.ReadFrameId();
+
+ ObjectId object_id;
+ JdwpError rc = Dbg::GetThisObject(thread_id, frame_id, &object_id);
+ if (rc != ERR_NONE) {
+ return rc;
+ }
+
+ return WriteTaggedObject(reply, object_id);
+}
+
+/*
+ * Return the reference type reflected by this class object.
+ *
+ * This appears to be required because ReferenceTypeId values are NEVER
+ * reused, whereas ClassIds can be recycled like any other object. (Either
+ * that, or I have no idea what this is for.)
+ */
+static JdwpError COR_ReflectedType(JdwpState*, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ RefTypeId class_object_id = request.ReadRefTypeId();
+ return Dbg::GetReflectedType(class_object_id, pReply);
+}
+
+/*
+ * Handle a DDM packet with a single chunk in it.
+ */
+static JdwpError DDM_Chunk(JdwpState* state, Request& request, ExpandBuf* pReply)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ state->NotifyDdmsActive();
+ uint8_t* replyBuf = NULL;
+ int replyLen = -1;
+ if (Dbg::DdmHandlePacket(request, &replyBuf, &replyLen)) {
+ // If they want to send something back, we copy it into the buffer.
+ // TODO: consider altering the JDWP stuff to hold the packet header
+ // in a separate buffer. That would allow us to writev() DDM traffic
+ // instead of copying it into the expanding buffer. The reduction in
+ // heap requirements is probably more valuable than the efficiency.
+ CHECK_GT(replyLen, 0);
+ CHECK_LT(replyLen, 1*1024*1024);
+ memcpy(expandBufAddSpace(pReply, replyLen), replyBuf, replyLen);
+ free(replyBuf);
+ }
+ return ERR_NONE;
+}
+
+/*
+ * Handler map decl.
+ */
+typedef JdwpError (*JdwpRequestHandler)(JdwpState* state, Request& request, ExpandBuf* reply);
+
+struct JdwpHandlerMap {
+ uint8_t cmdSet;
+ uint8_t cmd;
+ JdwpRequestHandler func;
+ const char* name;
+};
+
+/*
+ * Map commands to functions.
+ *
+ * Command sets 0-63 are incoming requests, 64-127 are outbound requests,
+ * and 128-256 are vendor-defined.
+ */
+static const JdwpHandlerMap gHandlers[] = {
+ /* VirtualMachine command set (1) */
+ { 1, 1, VM_Version, "VirtualMachine.Version" },
+ { 1, 2, VM_ClassesBySignature, "VirtualMachine.ClassesBySignature" },
+ { 1, 3, VM_AllClasses, "VirtualMachine.AllClasses" },
+ { 1, 4, VM_AllThreads, "VirtualMachine.AllThreads" },
+ { 1, 5, VM_TopLevelThreadGroups, "VirtualMachine.TopLevelThreadGroups" },
+ { 1, 6, VM_Dispose, "VirtualMachine.Dispose" },
+ { 1, 7, VM_IDSizes, "VirtualMachine.IDSizes" },
+ { 1, 8, VM_Suspend, "VirtualMachine.Suspend" },
+ { 1, 9, VM_Resume, "VirtualMachine.Resume" },
+ { 1, 10, VM_Exit, "VirtualMachine.Exit" },
+ { 1, 11, VM_CreateString, "VirtualMachine.CreateString" },
+ { 1, 12, VM_Capabilities, "VirtualMachine.Capabilities" },
+ { 1, 13, VM_ClassPaths, "VirtualMachine.ClassPaths" },
+ { 1, 14, VM_DisposeObjects, "VirtualMachine.DisposeObjects" },
+ { 1, 15, NULL, "VirtualMachine.HoldEvents" },
+ { 1, 16, NULL, "VirtualMachine.ReleaseEvents" },
+ { 1, 17, VM_CapabilitiesNew, "VirtualMachine.CapabilitiesNew" },
+ { 1, 18, NULL, "VirtualMachine.RedefineClasses" },
+ { 1, 19, NULL, "VirtualMachine.SetDefaultStratum" },
+ { 1, 20, VM_AllClassesWithGeneric, "VirtualMachine.AllClassesWithGeneric" },
+ { 1, 21, VM_InstanceCounts, "VirtualMachine.InstanceCounts" },
+
+ /* ReferenceType command set (2) */
+ { 2, 1, RT_Signature, "ReferenceType.Signature" },
+ { 2, 2, RT_ClassLoader, "ReferenceType.ClassLoader" },
+ { 2, 3, RT_Modifiers, "ReferenceType.Modifiers" },
+ { 2, 4, RT_Fields, "ReferenceType.Fields" },
+ { 2, 5, RT_Methods, "ReferenceType.Methods" },
+ { 2, 6, RT_GetValues, "ReferenceType.GetValues" },
+ { 2, 7, RT_SourceFile, "ReferenceType.SourceFile" },
+ { 2, 8, NULL, "ReferenceType.NestedTypes" },
+ { 2, 9, RT_Status, "ReferenceType.Status" },
+ { 2, 10, RT_Interfaces, "ReferenceType.Interfaces" },
+ { 2, 11, RT_ClassObject, "ReferenceType.ClassObject" },
+ { 2, 12, RT_SourceDebugExtension, "ReferenceType.SourceDebugExtension" },
+ { 2, 13, RT_SignatureWithGeneric, "ReferenceType.SignatureWithGeneric" },
+ { 2, 14, RT_FieldsWithGeneric, "ReferenceType.FieldsWithGeneric" },
+ { 2, 15, RT_MethodsWithGeneric, "ReferenceType.MethodsWithGeneric" },
+ { 2, 16, RT_Instances, "ReferenceType.Instances" },
+ { 2, 17, NULL, "ReferenceType.ClassFileVersion" },
+ { 2, 18, NULL, "ReferenceType.ConstantPool" },
+
+ /* ClassType command set (3) */
+ { 3, 1, CT_Superclass, "ClassType.Superclass" },
+ { 3, 2, CT_SetValues, "ClassType.SetValues" },
+ { 3, 3, CT_InvokeMethod, "ClassType.InvokeMethod" },
+ { 3, 4, CT_NewInstance, "ClassType.NewInstance" },
+
+ /* ArrayType command set (4) */
+ { 4, 1, AT_newInstance, "ArrayType.NewInstance" },
+
+ /* InterfaceType command set (5) */
+
+ /* Method command set (6) */
+ { 6, 1, M_LineTable, "Method.LineTable" },
+ { 6, 2, M_VariableTable, "Method.VariableTable" },
+ { 6, 3, M_Bytecodes, "Method.Bytecodes" },
+ { 6, 4, NULL, "Method.IsObsolete" },
+ { 6, 5, M_VariableTableWithGeneric, "Method.VariableTableWithGeneric" },
+
+ /* Field command set (8) */
+
+ /* ObjectReference command set (9) */
+ { 9, 1, OR_ReferenceType, "ObjectReference.ReferenceType" },
+ { 9, 2, OR_GetValues, "ObjectReference.GetValues" },
+ { 9, 3, OR_SetValues, "ObjectReference.SetValues" },
+ { 9, 4, NULL, "ObjectReference.UNUSED" },
+ { 9, 5, OR_MonitorInfo, "ObjectReference.MonitorInfo" },
+ { 9, 6, OR_InvokeMethod, "ObjectReference.InvokeMethod" },
+ { 9, 7, OR_DisableCollection, "ObjectReference.DisableCollection" },
+ { 9, 8, OR_EnableCollection, "ObjectReference.EnableCollection" },
+ { 9, 9, OR_IsCollected, "ObjectReference.IsCollected" },
+ { 9, 10, OR_ReferringObjects, "ObjectReference.ReferringObjects" },
+
+ /* StringReference command set (10) */
+ { 10, 1, SR_Value, "StringReference.Value" },
+
+ /* ThreadReference command set (11) */
+ { 11, 1, TR_Name, "ThreadReference.Name" },
+ { 11, 2, TR_Suspend, "ThreadReference.Suspend" },
+ { 11, 3, TR_Resume, "ThreadReference.Resume" },
+ { 11, 4, TR_Status, "ThreadReference.Status" },
+ { 11, 5, TR_ThreadGroup, "ThreadReference.ThreadGroup" },
+ { 11, 6, TR_Frames, "ThreadReference.Frames" },
+ { 11, 7, TR_FrameCount, "ThreadReference.FrameCount" },
+ { 11, 8, TR_OwnedMonitors, "ThreadReference.OwnedMonitors" },
+ { 11, 9, TR_CurrentContendedMonitor, "ThreadReference.CurrentContendedMonitor" },
+ { 11, 10, NULL, "ThreadReference.Stop" },
+ { 11, 11, TR_Interrupt, "ThreadReference.Interrupt" },
+ { 11, 12, TR_DebugSuspendCount, "ThreadReference.SuspendCount" },
+ { 11, 13, TR_OwnedMonitorsStackDepthInfo, "ThreadReference.OwnedMonitorsStackDepthInfo" },
+ { 11, 14, NULL, "ThreadReference.ForceEarlyReturn" },
+
+ /* ThreadGroupReference command set (12) */
+ { 12, 1, TGR_Name, "ThreadGroupReference.Name" },
+ { 12, 2, TGR_Parent, "ThreadGroupReference.Parent" },
+ { 12, 3, TGR_Children, "ThreadGroupReference.Children" },
+
+ /* ArrayReference command set (13) */
+ { 13, 1, AR_Length, "ArrayReference.Length" },
+ { 13, 2, AR_GetValues, "ArrayReference.GetValues" },
+ { 13, 3, AR_SetValues, "ArrayReference.SetValues" },
+
+ /* ClassLoaderReference command set (14) */
+ { 14, 1, CLR_VisibleClasses, "ClassLoaderReference.VisibleClasses" },
+
+ /* EventRequest command set (15) */
+ { 15, 1, ER_Set, "EventRequest.Set" },
+ { 15, 2, ER_Clear, "EventRequest.Clear" },
+ { 15, 3, NULL, "EventRequest.ClearAllBreakpoints" },
+
+ /* StackFrame command set (16) */
+ { 16, 1, SF_GetValues, "StackFrame.GetValues" },
+ { 16, 2, SF_SetValues, "StackFrame.SetValues" },
+ { 16, 3, SF_ThisObject, "StackFrame.ThisObject" },
+ { 16, 4, NULL, "StackFrame.PopFrames" },
+
+ /* ClassObjectReference command set (17) */
+ { 17, 1, COR_ReflectedType, "ClassObjectReference.ReflectedType" },
+
+ /* Event command set (64) */
+ { 64, 100, NULL, "Event.Composite" }, // sent from VM to debugger, never received by VM
+
+ { 199, 1, DDM_Chunk, "DDM.Chunk" },
+};
+
+static const char* GetCommandName(Request& request) {
+ for (size_t i = 0; i < arraysize(gHandlers); ++i) {
+ if (gHandlers[i].cmdSet == request.GetCommandSet() && gHandlers[i].cmd == request.GetCommand()) {
+ return gHandlers[i].name;
+ }
+ }
+ return "?UNKNOWN?";
+}
+
+static std::string DescribeCommand(Request& request) {
+ std::string result;
+ result += "REQUEST: ";
+ result += GetCommandName(request);
+ result += StringPrintf(" (length=%d id=0x%06x)", request.GetLength(), request.GetId());
+ return result;
+}
+
+/*
+ * Process a request from the debugger.
+ *
+ * On entry, the JDWP thread is in VMWAIT.
+ */
+void JdwpState::ProcessRequest(Request& request, ExpandBuf* pReply) {
+ JdwpError result = ERR_NONE;
+
+ if (request.GetCommandSet() != kJDWPDdmCmdSet) {
+ /*
+ * Activity from a debugger, not merely ddms. Mark us as having an
+ * active debugger session, and zero out the last-activity timestamp
+ * so waitForDebugger() doesn't return if we stall for a bit here.
+ */
+ Dbg::GoActive();
+ QuasiAtomic::Write64(&last_activity_time_ms_, 0);
+ }
+
+ /*
+ * If a debugger event has fired in another thread, wait until the
+ * initiating thread has suspended itself before processing messages
+ * from the debugger. Otherwise we (the JDWP thread) could be told to
+ * resume the thread before it has suspended.
+ *
+ * We call with an argument of zero to wait for the current event
+ * thread to finish, and then clear the block. Depending on the thread
+ * suspend policy, this may allow events in other threads to fire,
+ * but those events have no bearing on what the debugger has sent us
+ * in the current request.
+ *
+ * Note that we MUST clear the event token before waking the event
+ * thread up, or risk waiting for the thread to suspend after we've
+ * told it to resume.
+ */
+ SetWaitForEventThread(0);
+
+ /*
+ * Tell the VM that we're running and shouldn't be interrupted by GC.
+ * Do this after anything that can stall indefinitely.
+ */
+ Thread* self = Thread::Current();
+ ThreadState old_state = self->TransitionFromSuspendedToRunnable();
+
+ expandBufAddSpace(pReply, kJDWPHeaderLen);
+
+ size_t i;
+ for (i = 0; i < arraysize(gHandlers); ++i) {
+ if (gHandlers[i].cmdSet == request.GetCommandSet() && gHandlers[i].cmd == request.GetCommand() && gHandlers[i].func != NULL) {
+ VLOG(jdwp) << DescribeCommand(request);
+ result = (*gHandlers[i].func)(this, request, pReply);
+ if (result == ERR_NONE) {
+ request.CheckConsumed();
+ }
+ break;
+ }
+ }
+ if (i == arraysize(gHandlers)) {
+ LOG(ERROR) << "Command not implemented: " << DescribeCommand(request);
+ LOG(ERROR) << HexDump(request.data(), request.size());
+ result = ERR_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * Set up the reply header.
+ *
+ * If we encountered an error, only send the header back.
+ */
+ uint8_t* replyBuf = expandBufGetBuffer(pReply);
+ Set4BE(replyBuf + 4, request.GetId());
+ Set1(replyBuf + 8, kJDWPFlagReply);
+ Set2BE(replyBuf + 9, result);
+ if (result == ERR_NONE) {
+ Set4BE(replyBuf + 0, expandBufGetLength(pReply));
+ } else {
+ Set4BE(replyBuf + 0, kJDWPHeaderLen);
+ }
+
+ CHECK_GT(expandBufGetLength(pReply), 0U) << GetCommandName(request) << " " << request.GetId();
+
+ size_t respLen = expandBufGetLength(pReply) - kJDWPHeaderLen;
+ VLOG(jdwp) << "REPLY: " << GetCommandName(request) << " " << result << " (length=" << respLen << ")";
+ if (false) {
+ VLOG(jdwp) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen);
+ }
+
+ VLOG(jdwp) << "----------";
+
+ /*
+ * Update last-activity timestamp. We really only need this during
+ * the initial setup. Only update if this is a non-DDMS packet.
+ */
+ if (request.GetCommandSet() != kJDWPDdmCmdSet) {
+ QuasiAtomic::Write64(&last_activity_time_ms_, MilliTime());
+ }
+
+ /* tell the VM that GC is okay again */
+ self->TransitionFromRunnableToSuspended(old_state);
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc
new file mode 100644
index 0000000000..3b6dd810cc
--- /dev/null
+++ b/runtime/jdwp/jdwp_main.cc
@@ -0,0 +1,625 @@
+/*
+ * 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 <errno.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "atomic.h"
+#include "base/logging.h"
+#include "debugger.h"
+#include "jdwp/jdwp_priv.h"
+#include "scoped_thread_state_change.h"
+
+namespace art {
+
+namespace JDWP {
+
+static void* StartJdwpThread(void* arg);
+
+/*
+ * JdwpNetStateBase class implementation
+ */
+JdwpNetStateBase::JdwpNetStateBase(JdwpState* state)
+ : state_(state), socket_lock_("JdwpNetStateBase lock", kJdwpSerialSocketLock) {
+ clientSock = -1;
+ wake_pipe_[0] = -1;
+ wake_pipe_[1] = -1;
+ input_count_ = 0;
+ awaiting_handshake_ = false;
+}
+
+JdwpNetStateBase::~JdwpNetStateBase() {
+ if (wake_pipe_[0] != -1) {
+ close(wake_pipe_[0]);
+ wake_pipe_[0] = -1;
+ }
+ if (wake_pipe_[1] != -1) {
+ close(wake_pipe_[1]);
+ wake_pipe_[1] = -1;
+ }
+}
+
+bool JdwpNetStateBase::MakePipe() {
+ if (pipe(wake_pipe_) == -1) {
+ PLOG(ERROR) << "pipe failed";
+ return false;
+ }
+ return true;
+}
+
+void JdwpNetStateBase::WakePipe() {
+ // If we might be sitting in select, kick us loose.
+ if (wake_pipe_[1] != -1) {
+ VLOG(jdwp) << "+++ writing to wake pipe";
+ TEMP_FAILURE_RETRY(write(wake_pipe_[1], "", 1));
+ }
+}
+
+void JdwpNetStateBase::ConsumeBytes(size_t count) {
+ CHECK_GT(count, 0U);
+ CHECK_LE(count, input_count_);
+
+ if (count == input_count_) {
+ input_count_ = 0;
+ return;
+ }
+
+ memmove(input_buffer_, input_buffer_ + count, input_count_ - count);
+ input_count_ -= count;
+}
+
+bool JdwpNetStateBase::HaveFullPacket() {
+ if (awaiting_handshake_) {
+ return (input_count_ >= kMagicHandshakeLen);
+ }
+ if (input_count_ < 4) {
+ return false;
+ }
+ uint32_t length = Get4BE(input_buffer_);
+ return (input_count_ >= length);
+}
+
+bool JdwpNetStateBase::IsAwaitingHandshake() {
+ return awaiting_handshake_;
+}
+
+void JdwpNetStateBase::SetAwaitingHandshake(bool new_state) {
+ awaiting_handshake_ = new_state;
+}
+
+bool JdwpNetStateBase::IsConnected() {
+ return clientSock >= 0;
+}
+
+// Close a connection from a debugger (which may have already dropped us).
+// Resets the state so we're ready to receive a new connection.
+// Only called from the JDWP thread.
+void JdwpNetStateBase::Close() {
+ if (clientSock < 0) {
+ return;
+ }
+
+ VLOG(jdwp) << "+++ closing JDWP connection on fd " << clientSock;
+
+ close(clientSock);
+ clientSock = -1;
+}
+
+/*
+ * Write a packet. Grabs a mutex to assure atomicity.
+ */
+ssize_t JdwpNetStateBase::WritePacket(ExpandBuf* pReply) {
+ MutexLock mu(Thread::Current(), socket_lock_);
+ return TEMP_FAILURE_RETRY(write(clientSock, expandBufGetBuffer(pReply), expandBufGetLength(pReply)));
+}
+
+/*
+ * Write a buffered packet. Grabs a mutex to assure atomicity.
+ */
+ssize_t JdwpNetStateBase::WriteBufferedPacket(const iovec* iov, int iov_count) {
+ MutexLock mu(Thread::Current(), socket_lock_);
+ return TEMP_FAILURE_RETRY(writev(clientSock, iov, iov_count));
+}
+
+bool JdwpState::IsConnected() {
+ return netState != NULL && netState->IsConnected();
+}
+
+void JdwpState::SendBufferedRequest(uint32_t type, const iovec* iov, int iov_count) {
+ if (netState->clientSock < 0) {
+ // Can happen with some DDMS events.
+ VLOG(jdwp) << "Not sending JDWP packet: no debugger attached!";
+ return;
+ }
+
+ size_t expected = 0;
+ for (int i = 0; i < iov_count; ++i) {
+ expected += iov[i].iov_len;
+ }
+
+ errno = 0;
+ ssize_t actual = netState->WriteBufferedPacket(iov, iov_count);
+ if (static_cast<size_t>(actual) != expected) {
+ PLOG(ERROR) << StringPrintf("Failed to send JDWP packet %c%c%c%c to debugger (%d of %d)",
+ static_cast<uint8_t>(type >> 24),
+ static_cast<uint8_t>(type >> 16),
+ static_cast<uint8_t>(type >> 8),
+ static_cast<uint8_t>(type),
+ actual, expected);
+ }
+}
+
+void JdwpState::SendRequest(ExpandBuf* pReq) {
+ if (netState->clientSock < 0) {
+ // Can happen with some DDMS events.
+ VLOG(jdwp) << "Not sending JDWP packet: no debugger attached!";
+ return;
+ }
+
+ errno = 0;
+ ssize_t actual = netState->WritePacket(pReq);
+ if (static_cast<size_t>(actual) != expandBufGetLength(pReq)) {
+ PLOG(ERROR) << StringPrintf("Failed to send JDWP packet to debugger (%d of %d)",
+ actual, expandBufGetLength(pReq));
+ }
+}
+
+/*
+ * Get the next "request" serial number. We use this when sending
+ * packets to the debugger.
+ */
+uint32_t JdwpState::NextRequestSerial() {
+ MutexLock mu(Thread::Current(), serial_lock_);
+ return request_serial_++;
+}
+
+/*
+ * Get the next "event" serial number. We use this in the response to
+ * message type EventRequest.Set.
+ */
+uint32_t JdwpState::NextEventSerial() {
+ MutexLock mu(Thread::Current(), serial_lock_);
+ return event_serial_++;
+}
+
+JdwpState::JdwpState(const JdwpOptions* options)
+ : options_(options),
+ thread_start_lock_("JDWP thread start lock", kJdwpStartLock),
+ thread_start_cond_("JDWP thread start condition variable", thread_start_lock_),
+ pthread_(0),
+ thread_(NULL),
+ debug_thread_started_(false),
+ debug_thread_id_(0),
+ run(false),
+ netState(NULL),
+ attach_lock_("JDWP attach lock", kJdwpAttachLock),
+ attach_cond_("JDWP attach condition variable", attach_lock_),
+ last_activity_time_ms_(0),
+ serial_lock_("JDWP serial lock", kJdwpSerialSocketLock),
+ request_serial_(0x10000000),
+ event_serial_(0x20000000),
+ event_list_lock_("JDWP event list lock", kJdwpEventListLock),
+ event_list_(NULL),
+ event_list_size_(0),
+ event_thread_lock_("JDWP event thread lock"),
+ event_thread_cond_("JDWP event thread condition variable", event_thread_lock_),
+ event_thread_id_(0),
+ ddm_is_active_(false),
+ should_exit_(false),
+ exit_status_(0) {
+}
+
+/*
+ * Initialize JDWP.
+ *
+ * Does not return until JDWP thread is running, but may return before
+ * the thread is accepting network connections.
+ */
+JdwpState* JdwpState::Create(const JdwpOptions* options) {
+ Thread* self = Thread::Current();
+ Locks::mutator_lock_->AssertNotHeld(self);
+ UniquePtr<JdwpState> state(new JdwpState(options));
+ switch (options->transport) {
+ case kJdwpTransportSocket:
+ InitSocketTransport(state.get(), options);
+ break;
+#ifdef HAVE_ANDROID_OS
+ case kJdwpTransportAndroidAdb:
+ InitAdbTransport(state.get(), options);
+ break;
+#endif
+ default:
+ LOG(FATAL) << "Unknown transport: " << options->transport;
+ }
+
+ /*
+ * Grab a mutex or two before starting the thread. This ensures they
+ * won't signal the cond var before we're waiting.
+ */
+ {
+ MutexLock thread_start_locker(self, state->thread_start_lock_);
+ const bool should_suspend = options->suspend;
+ if (!should_suspend) {
+ /*
+ * We have bound to a port, or are trying to connect outbound to a
+ * debugger. Create the JDWP thread and let it continue the mission.
+ */
+ CHECK_PTHREAD_CALL(pthread_create, (&state->pthread_, NULL, StartJdwpThread, state.get()), "JDWP thread");
+
+ /*
+ * Wait until the thread finishes basic initialization.
+ * TODO: cond vars should be waited upon in a loop
+ */
+ state->thread_start_cond_.Wait(self);
+ } else {
+ {
+ /*
+ * We have bound to a port, or are trying to connect outbound to a
+ * debugger. Create the JDWP thread and let it continue the mission.
+ */
+ CHECK_PTHREAD_CALL(pthread_create, (&state->pthread_, NULL, StartJdwpThread, state.get()), "JDWP thread");
+
+ /*
+ * Wait until the thread finishes basic initialization.
+ * TODO: cond vars should be waited upon in a loop
+ */
+ state->thread_start_cond_.Wait(self);
+
+ /*
+ * 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.
+ */
+ {
+ ScopedThreadStateChange tsc(self, kWaitingForDebuggerToAttach);
+ MutexLock attach_locker(self, state->attach_lock_);
+ state->attach_cond_.Wait(self);
+ }
+ }
+ if (!state->IsActive()) {
+ LOG(ERROR) << "JDWP connection failed";
+ return NULL;
+ }
+
+ 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.release();
+}
+
+/*
+ * 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 JdwpState::ResetState() {
+ /* could reset the serial numbers, but no need to */
+
+ UnregisterAll();
+ {
+ MutexLock mu(Thread::Current(), event_list_lock_);
+ CHECK(event_list_ == NULL);
+ }
+
+ /*
+ * Should not have one of these in progress. If the debugger went away
+ * mid-request, though, we could see this.
+ */
+ if (event_thread_id_ != 0) {
+ LOG(WARNING) << "Resetting state while event in progress";
+ DCHECK(false);
+ }
+}
+
+/*
+ * Tell the JDWP thread to shut down. Frees "state".
+ */
+JdwpState::~JdwpState() {
+ if (netState != NULL) {
+ if (IsConnected()) {
+ PostVMDeath();
+ }
+
+ /*
+ * Close down the network to inspire the thread to halt.
+ */
+ VLOG(jdwp) << "JDWP shutting down net...";
+ netState->Shutdown();
+
+ if (debug_thread_started_) {
+ run = false;
+ void* threadReturn;
+ if (pthread_join(pthread_, &threadReturn) != 0) {
+ LOG(WARNING) << "JDWP thread join failed";
+ }
+ }
+
+ VLOG(jdwp) << "JDWP freeing netstate...";
+ delete netState;
+ netState = NULL;
+ }
+ CHECK(netState == NULL);
+
+ ResetState();
+}
+
+/*
+ * Are we talking to a debugger?
+ */
+bool JdwpState::IsActive() {
+ return IsConnected();
+}
+
+// Returns "false" if we encounter a connection-fatal error.
+bool JdwpState::HandlePacket() {
+ JdwpNetStateBase* netStateBase = reinterpret_cast<JdwpNetStateBase*>(netState);
+ JDWP::Request request(netStateBase->input_buffer_, netStateBase->input_count_);
+
+ ExpandBuf* pReply = expandBufAlloc();
+ ProcessRequest(request, pReply);
+ ssize_t cc = netStateBase->WritePacket(pReply);
+ if (cc != (ssize_t) expandBufGetLength(pReply)) {
+ PLOG(ERROR) << "Failed sending reply to debugger";
+ expandBufFree(pReply);
+ return false;
+ }
+ expandBufFree(pReply);
+ netStateBase->ConsumeBytes(request.GetLength());
+ return true;
+}
+
+/*
+ * 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* StartJdwpThread(void* arg) {
+ JdwpState* state = reinterpret_cast<JdwpState*>(arg);
+ CHECK(state != NULL);
+
+ state->Run();
+ return NULL;
+}
+
+void JdwpState::Run() {
+ Runtime* runtime = Runtime::Current();
+ CHECK(runtime->AttachCurrentThread("JDWP", true, runtime->GetSystemThreadGroup(),
+ !runtime->IsCompiler()));
+
+ VLOG(jdwp) << "JDWP: thread running";
+
+ /*
+ * Finish initializing, then notify the creating thread that
+ * we're running.
+ */
+ thread_ = Thread::Current();
+ run = true;
+
+ {
+ MutexLock locker(thread_, thread_start_lock_);
+ debug_thread_started_ = true;
+ thread_start_cond_.Broadcast(thread_);
+ }
+
+ /* set the thread state to kWaitingInMainDebuggerLoop so GCs don't wait for us */
+ CHECK_EQ(thread_->GetState(), kNative);
+ Locks::mutator_lock_->AssertNotHeld(thread_);
+ thread_->SetState(kWaitingInMainDebuggerLoop);
+
+ /*
+ * 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 (run) {
+ if (options_->server) {
+ /*
+ * Block forever, waiting for a connection. To support the
+ * "timeout=xxx" option we'll need to tweak this.
+ */
+ if (!netState->Accept()) {
+ 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 (!netState->Establish(options_)) {
+ /* wake anybody who was waiting for us to succeed */
+ MutexLock mu(thread_, attach_lock_);
+ attach_cond_.Broadcast(thread_);
+ break;
+ }
+ }
+
+ /* prep debug code to handle the new connection */
+ Dbg::Connected();
+
+ /* process requests until the debugger drops */
+ bool first = true;
+ while (!Dbg::IsDisposed()) {
+ {
+ // sanity check -- shouldn't happen?
+ MutexLock mu(thread_, *Locks::thread_suspend_count_lock_);
+ CHECK_EQ(thread_->GetState(), kWaitingInMainDebuggerLoop);
+ }
+
+ if (!netState->ProcessIncoming()) {
+ /* blocking read */
+ break;
+ }
+
+ if (should_exit_) {
+ exit(exit_status_);
+ }
+
+ if (first && !netState->IsAwaitingHandshake()) {
+ /* handshake worked, tell the interpreter that we're active */
+ first = false;
+
+ /* set thread ID; requires object registry to be active */
+ {
+ ScopedObjectAccess soa(thread_);
+ debug_thread_id_ = Dbg::GetThreadSelfId();
+ }
+
+ /* wake anybody who's waiting for us */
+ MutexLock mu(thread_, attach_lock_);
+ attach_cond_.Broadcast(thread_);
+ }
+ }
+
+ netState->Close();
+
+ if (ddm_is_active_) {
+ ddm_is_active_ = false;
+
+ /* broadcast the disconnect; must be in RUNNING state */
+ thread_->TransitionFromSuspendedToRunnable();
+ Dbg::DdmDisconnected();
+ thread_->TransitionFromRunnableToSuspended(kWaitingInMainDebuggerLoop);
+ }
+
+ {
+ ScopedObjectAccess soa(thread_);
+
+ // Release session state, e.g. remove breakpoint instructions.
+ ResetState();
+ }
+ // Tell the rest of the runtime 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 (!options_->server) {
+ run = false;
+ }
+ }
+
+ /* back to native, for thread shutdown */
+ CHECK_EQ(thread_->GetState(), kWaitingInMainDebuggerLoop);
+ thread_->SetState(kNative);
+
+ VLOG(jdwp) << "JDWP: thread detaching and exiting...";
+ runtime->DetachCurrentThread();
+}
+
+void JdwpState::NotifyDdmsActive() {
+ if (!ddm_is_active_) {
+ ddm_is_active_ = true;
+ Dbg::DdmConnected();
+ }
+}
+
+Thread* JdwpState::GetDebugThread() {
+ return thread_;
+}
+
+/*
+ * 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".
+ */
+
+/*
+ * 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 JdwpState::LastDebuggerActivity() {
+ if (!Dbg::IsDebuggerActive()) {
+ LOG(DEBUG) << "no active debugger";
+ return -1;
+ }
+
+ int64_t last = QuasiAtomic::Read64(&last_activity_time_ms_);
+
+ /* initializing or in the middle of something? */
+ if (last == 0) {
+ VLOG(jdwp) << "+++ last=busy";
+ return 0;
+ }
+
+ /* now get the current time */
+ int64_t now = MilliTime();
+ CHECK_GE(now, last);
+
+ VLOG(jdwp) << "+++ debugger interval=" << (now - last);
+ return now - last;
+}
+
+void JdwpState::ExitAfterReplying(int exit_status) {
+ LOG(WARNING) << "Debugger told VM to exit with status " << exit_status;
+ should_exit_ = true;
+ exit_status_ = exit_status;
+}
+
+std::ostream& operator<<(std::ostream& os, const JdwpLocation& rhs) {
+ os << "JdwpLocation["
+ << Dbg::GetClassName(rhs.class_id) << "." << Dbg::GetMethodName(rhs.method_id)
+ << "@" << StringPrintf("%#llx", rhs.dex_pc) << " " << rhs.type_tag << "]";
+ return os;
+}
+
+bool operator==(const JdwpLocation& lhs, const JdwpLocation& rhs) {
+ return lhs.dex_pc == rhs.dex_pc && lhs.method_id == rhs.method_id &&
+ lhs.class_id == rhs.class_id && lhs.type_tag == rhs.type_tag;
+}
+
+bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/jdwp_priv.h b/runtime/jdwp/jdwp_priv.h
new file mode 100644
index 0000000000..c8a7b2686d
--- /dev/null
+++ b/runtime/jdwp/jdwp_priv.h
@@ -0,0 +1,104 @@
+/*
+ * 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_
+
+#include "debugger.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_event.h"
+
+#include <pthread.h>
+#include <sys/uio.h>
+
+/*
+ * JDWP constants.
+ */
+#define kJDWPHeaderLen 11
+#define kJDWPFlagReply 0x80
+
+#define kMagicHandshake "JDWP-Handshake"
+#define kMagicHandshakeLen (sizeof(kMagicHandshake)-1)
+
+/* DDM support */
+#define kJDWPDdmCmdSet 199 /* 0xc7, or 'G'+128 */
+#define kJDWPDdmCmd 1
+
+namespace art {
+
+namespace JDWP {
+
+struct JdwpState;
+
+bool InitSocketTransport(JdwpState*, const JdwpOptions*);
+bool InitAdbTransport(JdwpState*, const JdwpOptions*);
+
+/*
+ * Base class for the adb and socket JdwpNetState implementations.
+ */
+class JdwpNetStateBase {
+ public:
+ JdwpNetStateBase(JdwpState*);
+ virtual ~JdwpNetStateBase();
+
+ virtual bool Accept() = 0;
+ virtual bool Establish(const JdwpOptions*) = 0;
+ virtual void Shutdown() = 0;
+ virtual bool ProcessIncoming() = 0;
+
+ void ConsumeBytes(size_t byte_count);
+
+ bool IsConnected();
+
+ bool IsAwaitingHandshake();
+
+ void Close();
+
+ ssize_t WritePacket(ExpandBuf* pReply);
+ ssize_t WriteBufferedPacket(const iovec* iov, int iov_count);
+
+ int clientSock; // Active connection to debugger.
+
+ int wake_pipe_[2]; // Used to break out of select.
+
+ uint8_t input_buffer_[8192];
+ size_t input_count_;
+
+ protected:
+ bool HaveFullPacket();
+
+ bool MakePipe();
+ void WakePipe();
+
+ void SetAwaitingHandshake(bool new_state);
+
+ JdwpState* state_;
+
+ private:
+ // Used to serialize writes to the socket.
+ Mutex socket_lock_;
+
+ // Are we waiting for the JDWP handshake?
+ bool awaiting_handshake_;
+};
+
+} // namespace JDWP
+
+} // namespace art
+
+#endif // ART_JDWP_JDWPPRIV_H_
diff --git a/runtime/jdwp/jdwp_request.cc b/runtime/jdwp/jdwp_request.cc
new file mode 100644
index 0000000000..440b51b6e3
--- /dev/null
+++ b/runtime/jdwp/jdwp_request.cc
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2013 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 "jdwp/jdwp.h"
+
+#include "base/stringprintf.h"
+#include "jdwp/jdwp_priv.h"
+
+namespace art {
+
+namespace JDWP {
+
+Request::Request(const uint8_t* bytes, uint32_t available) : p_(bytes) {
+ byte_count_ = Read4BE();
+ end_ = bytes + byte_count_;
+ CHECK_LE(byte_count_, available);
+
+ id_ = Read4BE();
+ int8_t flags = Read1();
+ if ((flags & kJDWPFlagReply) != 0) {
+ LOG(FATAL) << "reply?!";
+ }
+
+ command_set_ = Read1();
+ command_ = Read1();
+}
+
+Request::~Request() {
+}
+
+void Request::CheckConsumed() {
+ if (p_ < end_) {
+ CHECK(p_ == end_) << "read too few bytes: " << (end_ - p_);
+ } else if (p_ > end_) {
+ CHECK(p_ == end_) << "read too many bytes: " << (p_ - end_);
+ }
+}
+
+std::string Request::ReadUtf8String() {
+ uint32_t length = Read4BE();
+ std::string s;
+ s.resize(length);
+ memcpy(&s[0], p_, length);
+ p_ += length;
+ VLOG(jdwp) << " string \"" << s << "\"";
+ return s;
+}
+
+// Helper function: read a variable-width value from the input buffer.
+uint64_t Request::ReadValue(size_t width) {
+ uint64_t value = -1;
+ switch (width) {
+ case 1: value = Read1(); break;
+ case 2: value = Read2BE(); break;
+ case 4: value = Read4BE(); break;
+ case 8: value = Read8BE(); break;
+ default: LOG(FATAL) << width; break;
+ }
+ return value;
+}
+
+int32_t Request::ReadSigned32(const char* what) {
+ int32_t value = static_cast<int32_t>(Read4BE());
+ VLOG(jdwp) << " " << what << " " << value;
+ return value;
+}
+
+uint32_t Request::ReadUnsigned32(const char* what) {
+ uint32_t value = Read4BE();
+ VLOG(jdwp) << " " << what << " " << value;
+ return value;
+}
+
+FieldId Request::ReadFieldId() {
+ FieldId id = Read4BE();
+ VLOG(jdwp) << " field id " << DescribeField(id);
+ return id;
+}
+
+MethodId Request::ReadMethodId() {
+ MethodId id = Read4BE();
+ VLOG(jdwp) << " method id " << DescribeMethod(id);
+ return id;
+}
+
+ObjectId Request::ReadObjectId(const char* specific_kind) {
+ ObjectId id = Read8BE();
+ VLOG(jdwp) << StringPrintf(" %s id %#llx", specific_kind, id);
+ return id;
+}
+
+ObjectId Request::ReadArrayId() {
+ return ReadObjectId("array");
+}
+
+ObjectId Request::ReadObjectId() {
+ return ReadObjectId("object");
+}
+
+ObjectId Request::ReadThreadId() {
+ return ReadObjectId("thread");
+}
+
+ObjectId Request::ReadThreadGroupId() {
+ return ReadObjectId("thread group");
+}
+
+RefTypeId Request::ReadRefTypeId() {
+ RefTypeId id = Read8BE();
+ VLOG(jdwp) << " ref type id " << DescribeRefTypeId(id);
+ return id;
+}
+
+FrameId Request::ReadFrameId() {
+ FrameId id = Read8BE();
+ VLOG(jdwp) << " frame id " << id;
+ return id;
+}
+
+JdwpTag Request::ReadTag() {
+ return ReadEnum1<JdwpTag>("tag");
+}
+
+JdwpTypeTag Request::ReadTypeTag() {
+ return ReadEnum1<JdwpTypeTag>("type tag");
+}
+
+JdwpLocation Request::ReadLocation() {
+ JdwpLocation location;
+ memset(&location, 0, sizeof(location)); // Allows memcmp(3) later.
+ location.type_tag = ReadTypeTag();
+ location.class_id = ReadObjectId("class");
+ location.method_id = ReadMethodId();
+ location.dex_pc = Read8BE();
+ VLOG(jdwp) << " location " << location;
+ return location;
+}
+
+JdwpModKind Request::ReadModKind() {
+ return ReadEnum1<JdwpModKind>("mod kind");
+}
+
+uint8_t Request::Read1() {
+ return *p_++;
+}
+
+uint16_t Request::Read2BE() {
+ uint16_t result = p_[0] << 8 | p_[1];
+ p_ += 2;
+ return result;
+}
+
+uint32_t Request::Read4BE() {
+ uint32_t result = p_[0] << 24;
+ result |= p_[1] << 16;
+ result |= p_[2] << 8;
+ result |= p_[3];
+ p_ += 4;
+ return result;
+}
+
+uint64_t Request::Read8BE() {
+ uint64_t high = Read4BE();
+ uint64_t low = Read4BE();
+ return (high << 32) | low;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/jdwp_socket.cc b/runtime/jdwp/jdwp_socket.cc
new file mode 100644
index 0000000000..08b4859762
--- /dev/null
+++ b/runtime/jdwp/jdwp_socket.cc
@@ -0,0 +1,502 @@
+/*
+ * 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 <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "jdwp/jdwp_priv.h"
+
+#define kBasePort 8000
+#define kMaxPort 8040
+
+namespace art {
+
+namespace JDWP {
+
+/*
+ * JDWP network state.
+ *
+ * We only talk to one debugger at a time.
+ */
+struct JdwpSocketState : public JdwpNetStateBase {
+ uint16_t listenPort;
+ int listenSock; /* listen for connection from debugger */
+
+ JdwpSocketState(JdwpState* state) : JdwpNetStateBase(state) {
+ listenPort = 0;
+ listenSock = -1;
+ }
+
+ virtual bool Accept();
+ virtual bool Establish(const JdwpOptions*);
+ virtual void Shutdown();
+ virtual bool ProcessIncoming();
+
+ private:
+ in_addr remote_addr_;
+ uint16_t remote_port_;
+};
+
+static JdwpSocketState* SocketStartup(JdwpState* state, uint16_t port, bool probe);
+
+/*
+ * Set up some stuff for transport=dt_socket.
+ */
+bool InitSocketTransport(JdwpState* state, const JdwpOptions* options) {
+ uint16_t port = options->port;
+
+ if (options->server) {
+ if (options->port != 0) {
+ /* try only the specified port */
+ state->netState = SocketStartup(state, port, false);
+ } else {
+ /* scan through a range of ports, binding to the first available */
+ for (port = kBasePort; port <= kMaxPort; port++) {
+ state->netState = SocketStartup(state, port, true);
+ if (state->netState != NULL) {
+ break;
+ }
+ }
+ }
+ if (state->netState == NULL) {
+ LOG(ERROR) << "JDWP net startup failed (req port=" << options->port << ")";
+ return false;
+ }
+ } else {
+ state->netState = SocketStartup(state, 0, false);
+ }
+
+ if (options->suspend) {
+ LOG(INFO) << "JDWP will wait for debugger on port " << port;
+ } else {
+ LOG(INFO) << "JDWP will " << (options->server ? "listen" : "connect") << " on port " << port;
+ }
+
+ return true;
+}
+
+/*
+ * Initialize JDWP stuff.
+ *
+ * Allocates a new state structure. If "port" is non-zero, this also
+ * tries to bind to a listen port. If "port" is 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 JdwpSocketState* SocketStartup(JdwpState* state, uint16_t port, bool probe) {
+ JdwpSocketState* netState = new JdwpSocketState(state);
+ if (port == 0) {
+ return netState;
+ }
+
+ netState->listenSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (netState->listenSock < 0) {
+ PLOG(probe ? ERROR : FATAL) << "Socket create failed";
+ goto fail;
+ }
+
+ /* allow immediate re-use */
+ {
+ int one = 1;
+ if (setsockopt(netState->listenSock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
+ PLOG(probe ? ERROR : FATAL) << "setsockopt(SO_REUSEADDR) failed";
+ goto fail;
+ }
+ }
+
+ union {
+ sockaddr_in addrInet;
+ 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(probe ? ERROR : FATAL) << "Attempt to bind to port " << port << " failed";
+ goto fail;
+ }
+
+ netState->listenPort = port;
+
+ if (listen(netState->listenSock, 5) != 0) {
+ PLOG(probe ? ERROR : FATAL) << "Listen failed";
+ goto fail;
+ }
+
+ return netState;
+
+ fail:
+ netState->Shutdown();
+ delete netState;
+ return NULL;
+}
+
+/*
+ * Shut down JDWP listener. Don't free state.
+ *
+ * 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.)
+ */
+void JdwpSocketState::Shutdown() {
+ int listenSock = this->listenSock;
+ int clientSock = this->clientSock;
+
+ /* clear these out so it doesn't wake up and try to reuse them */
+ this->listenSock = this->clientSock = -1;
+
+ /* "shutdown" dislodges blocking read() and accept() calls */
+ if (listenSock != -1) {
+ shutdown(listenSock, SHUT_RDWR);
+ close(listenSock);
+ }
+ if (clientSock != -1) {
+ shutdown(clientSock, SHUT_RDWR);
+ close(clientSock);
+ }
+
+ WakePipe();
+}
+
+/*
+ * 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.
+ */
+bool JdwpSocketState::Accept() {
+ union {
+ sockaddr_in addrInet;
+ sockaddr addrPlain;
+ } addr;
+ socklen_t addrlen;
+ int sock;
+
+ if (listenSock < 0) {
+ return false; /* you're not listening! */
+ }
+
+ CHECK(clientSock == -1); /* must not already be talking */
+
+ addrlen = sizeof(addr);
+ do {
+ sock = accept(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) {
+ if (VLOG_IS_ON(jdwp)) {
+ PLOG(ERROR) << "accept failed";
+ }
+ } else {
+ PLOG(ERROR) << "accept failed";
+ return false;
+ }
+ }
+ } while (sock < 0);
+
+ remote_addr_ = addr.addrInet.sin_addr;
+ remote_port_ = ntohs(addr.addrInet.sin_port);
+ VLOG(jdwp) << "+++ accepted connection from " << inet_ntoa(remote_addr_) << ":" << remote_port_;
+
+ clientSock = sock;
+ SetAwaitingHandshake(true);
+ input_count_ = 0;
+
+ VLOG(jdwp) << "Setting TCP_NODELAY on accepted socket";
+ SetNoDelay(clientSock);
+
+ if (!MakePipe()) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Create a connection to a waiting debugger.
+ */
+bool JdwpSocketState::Establish(const JdwpOptions* options) {
+ union {
+ sockaddr_in addrInet;
+ sockaddr addrPlain;
+ } addr;
+ hostent* pEntry;
+
+ CHECK(!options->server);
+ CHECK(!options->host.empty());
+ CHECK_NE(options->port, 0);
+
+ /*
+ * Start by resolving the host name.
+ */
+#ifdef HAVE_GETHOSTBYNAME_R
+ hostent he;
+ char auxBuf[128];
+ int error;
+ int cc = gethostbyname_r(options->host.c_str(), &he, auxBuf, sizeof(auxBuf), &pEntry, &error);
+ if (cc != 0) {
+ LOG(WARNING) << "gethostbyname_r('" << options->host << "') failed: " << hstrerror(error);
+ return false;
+ }
+#else
+ h_errno = 0;
+ pEntry = gethostbyname(options->host.c_str());
+ if (pEntry == NULL) {
+ PLOG(WARNING) << "gethostbyname('" << options->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(options->port);
+
+ LOG(INFO) << "Connecting out to " << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port);
+
+ /*
+ * Create a socket.
+ */
+ clientSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (clientSock < 0) {
+ PLOG(ERROR) << "Unable to create socket";
+ return false;
+ }
+
+ /*
+ * Try to connect.
+ */
+ if (connect(clientSock, &addr.addrPlain, sizeof(addr)) != 0) {
+ PLOG(ERROR) << "Unable to connect to " << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port);
+ close(clientSock);
+ clientSock = -1;
+ return false;
+ }
+
+ LOG(INFO) << "Connection established to " << options->host << " (" << inet_ntoa(addr.addrInet.sin_addr) << ":" << ntohs(addr.addrInet.sin_port) << ")";
+ SetAwaitingHandshake(true);
+ input_count_ = 0;
+
+ SetNoDelay(clientSock);
+
+ if (!MakePipe()) {
+ return false;
+ }
+
+ 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.
+ */
+bool JdwpSocketState::ProcessIncoming() {
+ int readCount;
+
+ CHECK(clientSock != -1);
+
+ if (!HaveFullPacket()) {
+ /* 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 = listenSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ }
+ fd = clientSock;
+ if (fd >= 0) {
+ FD_SET(fd, &readfds);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+ }
+ fd = wake_pipe_[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) {
+ VLOG(jdwp) << "+++ 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 ("wake pipe") 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 (wake_pipe_[0] >= 0 && FD_ISSET(wake_pipe_[0], &readfds)) {
+ if (listenSock >= 0) {
+ LOG(ERROR) << "Exit wake set, but not exiting?";
+ } else {
+ LOG(DEBUG) << "Got wake-up signal, bailing out of select";
+ }
+ goto fail;
+ }
+ if (listenSock >= 0 && FD_ISSET(listenSock, &readfds)) {
+ LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
+ union {
+ sockaddr_in addrInet;
+ sockaddr addrPlain;
+ } addr;
+ socklen_t addrlen;
+ int tmpSock;
+ tmpSock = accept(listenSock, &addr.addrPlain, &addrlen);
+ if (tmpSock < 0) {
+ LOG(INFO) << "Weird -- accept failed";
+ } else {
+ close(tmpSock);
+ }
+ }
+ if (clientSock >= 0 && FD_ISSET(clientSock, &readfds)) {
+ readCount = read(clientSock, input_buffer_ + input_count_, sizeof(input_buffer_) - input_count_);
+ 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 */
+ VLOG(jdwp) << "+++ peer disconnected";
+ goto fail;
+ } else {
+ break;
+ }
+ }
+ }
+
+ input_count_ += readCount;
+ if (!HaveFullPacket()) {
+ 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 (IsAwaitingHandshake()) {
+ if (memcmp(input_buffer_, kMagicHandshake, kMagicHandshakeLen) != 0) {
+ LOG(ERROR) << StringPrintf("ERROR: bad handshake '%.14s'", input_buffer_);
+ goto fail;
+ }
+
+ errno = 0;
+ int cc = TEMP_FAILURE_RETRY(write(clientSock, input_buffer_, kMagicHandshakeLen));
+ if (cc != kMagicHandshakeLen) {
+ PLOG(ERROR) << "Failed writing handshake bytes (" << cc << " of " << kMagicHandshakeLen << ")";
+ goto fail;
+ }
+
+ ConsumeBytes(kMagicHandshakeLen);
+ SetAwaitingHandshake(false);
+ VLOG(jdwp) << "+++ handshake complete";
+ return true;
+ }
+
+ /*
+ * Handle this packet.
+ */
+ return state_->HandlePacket();
+
+ fail:
+ Close();
+ return false;
+}
+
+} // namespace JDWP
+
+} // namespace art
diff --git a/runtime/jdwp/object_registry.cc b/runtime/jdwp/object_registry.cc
new file mode 100644
index 0000000000..54e7a8e486
--- /dev/null
+++ b/runtime/jdwp/object_registry.cc
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 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 "object_registry.h"
+
+#include "scoped_thread_state_change.h"
+
+namespace art {
+
+mirror::Object* const ObjectRegistry::kInvalidObject = reinterpret_cast<mirror::Object*>(1);
+
+std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs) {
+ os << "ObjectRegistryEntry[" << rhs.jni_reference_type
+ << ",reference=" << rhs.jni_reference
+ << ",count=" << rhs.reference_count
+ << ",id=" << rhs.id << "]";
+ return os;
+}
+
+ObjectRegistry::ObjectRegistry()
+ : lock_("ObjectRegistry lock", kJdwpObjectRegistryLock), next_id_(1) {
+}
+
+JDWP::RefTypeId ObjectRegistry::AddRefType(mirror::Class* c) {
+ return InternalAdd(c);
+}
+
+JDWP::ObjectId ObjectRegistry::Add(mirror::Object* o) {
+ return InternalAdd(o);
+}
+
+JDWP::ObjectId ObjectRegistry::InternalAdd(mirror::Object* o) {
+ if (o == NULL) {
+ return 0;
+ }
+
+ ScopedObjectAccessUnchecked soa(Thread::Current());
+ MutexLock mu(soa.Self(), lock_);
+ ObjectRegistryEntry dummy;
+ dummy.jni_reference_type = JNIWeakGlobalRefType;
+ dummy.jni_reference = NULL;
+ dummy.reference_count = 0;
+ dummy.id = 0;
+ std::pair<object_iterator, bool> result = object_to_entry_.insert(std::make_pair(o, dummy));
+ ObjectRegistryEntry& entry = result.first->second;
+ if (!result.second) {
+ // This object was already in our map.
+ entry.reference_count += 1;
+ return entry.id;
+ }
+
+ // This object isn't in the registry yet, so add it.
+ JNIEnv* env = soa.Env();
+
+ jobject local_reference = soa.AddLocalReference<jobject>(o);
+
+ entry.jni_reference_type = JNIWeakGlobalRefType;
+ entry.jni_reference = env->NewWeakGlobalRef(local_reference);
+ entry.reference_count = 1;
+ entry.id = next_id_++;
+
+ id_to_entry_.Put(entry.id, &entry);
+
+ env->DeleteLocalRef(local_reference);
+
+ return entry.id;
+}
+
+bool ObjectRegistry::Contains(mirror::Object* o) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ return (object_to_entry_.find(o) != object_to_entry_.end());
+}
+
+void ObjectRegistry::Clear() {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ VLOG(jdwp) << "Object registry contained " << object_to_entry_.size() << " entries";
+
+ // Delete all the JNI references.
+ JNIEnv* env = self->GetJniEnv();
+ for (object_iterator it = object_to_entry_.begin(); it != object_to_entry_.end(); ++it) {
+ ObjectRegistryEntry& entry = (it->second);
+ if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+ env->DeleteWeakGlobalRef(entry.jni_reference);
+ } else {
+ env->DeleteGlobalRef(entry.jni_reference);
+ }
+ }
+
+ // Clear the maps.
+ object_to_entry_.clear();
+ id_to_entry_.clear();
+}
+
+mirror::Object* ObjectRegistry::InternalGet(JDWP::ObjectId id) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ id_iterator it = id_to_entry_.find(id);
+ if (it == id_to_entry_.end()) {
+ return kInvalidObject;
+ }
+ ObjectRegistryEntry& entry = *(it->second);
+ return self->DecodeJObject(entry.jni_reference);
+}
+
+jobject ObjectRegistry::GetJObject(JDWP::ObjectId id) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ id_iterator it = id_to_entry_.find(id);
+ CHECK(it != id_to_entry_.end()) << id;
+ ObjectRegistryEntry& entry = *(it->second);
+ return entry.jni_reference;
+}
+
+void ObjectRegistry::DisableCollection(JDWP::ObjectId id) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ id_iterator it = id_to_entry_.find(id);
+ if (it == id_to_entry_.end()) {
+ return;
+ }
+ Promote(*(it->second));
+}
+
+void ObjectRegistry::EnableCollection(JDWP::ObjectId id) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ id_iterator it = id_to_entry_.find(id);
+ if (it == id_to_entry_.end()) {
+ return;
+ }
+ Demote(*(it->second));
+}
+
+void ObjectRegistry::Demote(ObjectRegistryEntry& entry) {
+ if (entry.jni_reference_type == JNIGlobalRefType) {
+ Thread* self = Thread::Current();
+ JNIEnv* env = self->GetJniEnv();
+ jobject global = entry.jni_reference;
+ entry.jni_reference = env->NewWeakGlobalRef(entry.jni_reference);
+ entry.jni_reference_type = JNIWeakGlobalRefType;
+ env->DeleteGlobalRef(global);
+ }
+}
+
+void ObjectRegistry::Promote(ObjectRegistryEntry& entry) {
+ if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+ Thread* self = Thread::Current();
+ JNIEnv* env = self->GetJniEnv();
+ jobject weak = entry.jni_reference;
+ entry.jni_reference = env->NewGlobalRef(entry.jni_reference);
+ entry.jni_reference_type = JNIGlobalRefType;
+ env->DeleteWeakGlobalRef(weak);
+ }
+}
+
+bool ObjectRegistry::IsCollected(JDWP::ObjectId id) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ id_iterator it = id_to_entry_.find(id);
+ if (it == id_to_entry_.end()) {
+ return true; // TODO: can we report that this was an invalid id?
+ }
+
+ ObjectRegistryEntry& entry = *(it->second);
+ if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+ JNIEnv* env = self->GetJniEnv();
+ return env->IsSameObject(entry.jni_reference, NULL); // Has the jweak been collected?
+ } else {
+ return false; // We hold a strong reference, so we know this is live.
+ }
+}
+
+void ObjectRegistry::DisposeObject(JDWP::ObjectId id, uint32_t reference_count) {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, lock_);
+ id_iterator it = id_to_entry_.find(id);
+ if (it == id_to_entry_.end()) {
+ return;
+ }
+
+ ObjectRegistryEntry& entry = *(it->second);
+ entry.reference_count -= reference_count;
+ if (entry.reference_count <= 0) {
+ JNIEnv* env = self->GetJniEnv();
+ mirror::Object* object = self->DecodeJObject(entry.jni_reference);
+ if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+ env->DeleteWeakGlobalRef(entry.jni_reference);
+ } else {
+ env->DeleteGlobalRef(entry.jni_reference);
+ }
+ object_to_entry_.erase(object);
+ id_to_entry_.erase(id);
+ }
+}
+
+} // namespace art
diff --git a/runtime/jdwp/object_registry.h b/runtime/jdwp/object_registry.h
new file mode 100644
index 0000000000..d0ea59da71
--- /dev/null
+++ b/runtime/jdwp/object_registry.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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 <stdint.h>
+
+#include <map>
+
+#include "jdwp/jdwp.h"
+#include "mirror/class.h"
+#include "mirror/class-inl.h"
+#include "mirror/field-inl.h"
+#include "mirror/object-inl.h"
+#include "safe_map.h"
+
+namespace art {
+
+struct ObjectRegistryEntry {
+ // Is jni_reference a weak global or a regular global reference?
+ jobjectRefType jni_reference_type;
+
+ // The reference itself.
+ jobject jni_reference;
+
+ // A reference count, so we can implement DisposeObject.
+ int32_t reference_count;
+
+ // The corresponding id, so we only need one map lookup in Add.
+ JDWP::ObjectId id;
+};
+std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs);
+
+// Tracks those objects currently known to the debugger, so we can use consistent ids when
+// referring to them. Normally we keep JNI weak global references to objects, so they can
+// still be garbage collected. The debugger can ask us to retain objects, though, so we can
+// also promote references to regular JNI global references (and demote them back again if
+// the debugger tells us that's okay).
+class ObjectRegistry {
+ public:
+ ObjectRegistry();
+
+ JDWP::ObjectId Add(mirror::Object* o) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ JDWP::RefTypeId AddRefType(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ template<typename T> T Get(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ if (id == 0) {
+ return NULL;
+ }
+ return reinterpret_cast<T>(InternalGet(id));
+ }
+
+ bool Contains(mirror::Object* o) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ void Clear() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ void DisableCollection(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ void EnableCollection(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ bool IsCollected(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ void DisposeObject(JDWP::ObjectId id, uint32_t reference_count)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ // Returned by Get when passed an invalid object id.
+ static mirror::Object* const kInvalidObject;
+
+ // This is needed to get the jobject instead of the Object*.
+ // Avoid using this and use standard Get when possible.
+ jobject GetJObject(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+ private:
+ JDWP::ObjectId InternalAdd(mirror::Object* o) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ mirror::Object* InternalGet(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+ void Demote(ObjectRegistryEntry& entry) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, lock_);
+ void Promote(ObjectRegistryEntry& entry) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, lock_);
+
+ Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+
+ typedef std::map<mirror::Object*, ObjectRegistryEntry>::iterator object_iterator;
+ std::map<mirror::Object*, ObjectRegistryEntry> object_to_entry_ GUARDED_BY(lock_);
+
+ typedef SafeMap<JDWP::ObjectId, ObjectRegistryEntry*>::iterator id_iterator;
+ SafeMap<JDWP::ObjectId, ObjectRegistryEntry*> id_to_entry_ GUARDED_BY(lock_);
+
+ size_t next_id_ GUARDED_BY(lock_);
+};
+
+} // namespace art