Merge "Make GetState() handle overflowed state and 0 path to root"
diff --git a/Android.bp b/Android.bp
index 295ae4c..1978606 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,6 +20,7 @@
 ]
 
 subdirs = [
+    "adbconnection",
     "benchmark",
     "build",
     "cmdline",
@@ -31,6 +32,8 @@
     "dexlist",
     "dexoptanalyzer",
     "disassembler",
+    "dt_fd_forward",
+    "dt_fd_forward/export",
     "imgdiag",
     "oatdump",
     "openjdkjvm",
diff --git a/Android.mk b/Android.mk
index 174cde3..cb0b709 100644
--- a/Android.mk
+++ b/Android.mk
@@ -370,6 +370,7 @@
     libopenjdkjvmti \
     patchoat \
     profman \
+    libadbconnection \
 
 # For nosy apps, we provide a fake library that avoids namespace issues and gives some warnings.
 LOCAL_REQUIRED_MODULES += libart_fake
@@ -395,6 +396,7 @@
     libopenjdkjvmtid \
     patchoatd \
     profmand \
+    libadbconnectiond \
 
 endif
 endif
diff --git a/adbconnection/Android.bp b/adbconnection/Android.bp
new file mode 100644
index 0000000..441b706
--- /dev/null
+++ b/adbconnection/Android.bp
@@ -0,0 +1,80 @@
+//
+// Copyright (C) 2017 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.
+//
+
+// Build variants {target,host} x {debug,ndebug} x {32,64}
+
+cc_defaults {
+    name: "adbconnection-defaults",
+    host_supported: true,
+    srcs: ["adbconnection.cc"],
+    defaults: ["art_defaults"],
+
+    // Note that this tool needs to be built for both 32-bit and 64-bit since it requires
+    // to be same ISA as what it is attached to.
+    compile_multilib: "both",
+
+    shared_libs: [
+        "libbase",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libcutils",
+            ],
+        },
+        host: {
+        },
+        darwin: {
+            enabled: false,
+        },
+    },
+    header_libs: [
+        "libnativehelper_header_only",
+        "dt_fd_forward_export",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    symlink_preferred_arch: true,
+    required: [
+        "libjdwp",
+        "libdt_fd_forward",
+    ],
+}
+
+art_cc_library {
+    name: "libadbconnection",
+    defaults: ["adbconnection-defaults"],
+    shared_libs: [
+        "libart",
+    ],
+}
+
+art_cc_library {
+    name: "libadbconnectiond",
+    defaults: [
+        "art_debug_defaults",
+        "adbconnection-defaults",
+    ],
+    shared_libs: [
+        "libartd",
+    ],
+}
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
new file mode 100644
index 0000000..2a9982a
--- /dev/null
+++ b/adbconnection/adbconnection.cc
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2017 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 <array>
+
+#include "adbconnection.h"
+
+#include "android-base/endian.h"
+#include "android-base/stringprintf.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "mirror/throwable.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime-inl.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "well_known_classes.h"
+
+#include "jdwp/jdwp_priv.h"
+
+#include "fd_transport.h"
+
+#include "poll.h"
+
+#ifdef ART_TARGET_ANDROID
+#include "cutils/sockets.h"
+#endif
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/eventfd.h>
+#include <jni.h>
+
+namespace adbconnection {
+
+using dt_fd_forward::kListenStartMessage;
+using dt_fd_forward::kListenEndMessage;
+using dt_fd_forward::kAcceptMessage;
+using dt_fd_forward::kCloseMessage;
+
+using android::base::StringPrintf;
+
+static constexpr int kEventfdLocked = 0;
+static constexpr int kEventfdUnlocked = 1;
+static constexpr int kControlSockSendTimeout = 10;
+
+static AdbConnectionState* gState;
+
+static bool IsDebuggingPossible() {
+  // TODO We need to do this on IsJdwpAllowed not IsDebuggable in order to support userdebug
+  // workloads. For now we will only allow it when we are debuggable so that testing is easier.
+  return art::Runtime::Current()->IsJavaDebuggable() && art::Dbg::IsJdwpAllowed();
+}
+
+// Begin running the debugger.
+void AdbConnectionDebuggerController::StartDebugger() {
+  if (IsDebuggingPossible()) {
+    connection_->StartDebuggerThreads();
+  } else {
+    LOG(ERROR) << "Not starting debugger since process cannot load the jdwp agent.";
+  }
+}
+
+// The debugger should begin shutting down since the runtime is ending. We don't actually do
+// anything here. The real shutdown has already happened as far as the agent is concerned.
+void AdbConnectionDebuggerController::StopDebugger() { }
+
+bool AdbConnectionDebuggerController::IsDebuggerConfigured() {
+  return IsDebuggingPossible() && !art::Runtime::Current()->GetJdwpOptions().empty();
+}
+
+void AdbConnectionDdmCallback::DdmPublishChunk(uint32_t type,
+                                               const art::ArrayRef<const uint8_t>& data) {
+  connection_->PublishDdmData(type, data);
+}
+
+class ScopedEventFdLock {
+ public:
+  explicit ScopedEventFdLock(int fd) : fd_(fd), data_(0) {
+    TEMP_FAILURE_RETRY(read(fd_, &data_, sizeof(data_)));
+  }
+
+  ~ScopedEventFdLock() {
+    TEMP_FAILURE_RETRY(write(fd_, &data_, sizeof(data_)));
+  }
+
+ private:
+  int fd_;
+  uint64_t data_;
+};
+
+AdbConnectionState::AdbConnectionState(const std::string& agent_name)
+  : agent_name_(agent_name),
+    controller_(this),
+    ddm_callback_(this),
+    sleep_event_fd_(-1),
+    control_sock_(-1),
+    local_agent_control_sock_(-1),
+    remote_agent_control_sock_(-1),
+    adb_connection_socket_(-1),
+    adb_write_event_fd_(-1),
+    shutting_down_(false),
+    agent_loaded_(false),
+    agent_listening_(false),
+    next_ddm_id_(1) {
+  // Setup the addr.
+  control_addr_.controlAddrUn.sun_family = AF_UNIX;
+  control_addr_len_ = sizeof(control_addr_.controlAddrUn.sun_family) + sizeof(kJdwpControlName) - 1;
+  memcpy(control_addr_.controlAddrUn.sun_path, kJdwpControlName, sizeof(kJdwpControlName) - 1);
+
+  // Add the startup callback.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddDebuggerControlCallback(&controller_);
+}
+
+static jobject CreateAdbConnectionThread(art::Thread* thr) {
+  JNIEnv* env = thr->GetJniEnv();
+  // Move to native state to talk with the jnienv api.
+  art::ScopedThreadStateChange stsc(thr, art::kNative);
+  ScopedLocalRef<jstring> thr_name(env, env->NewStringUTF(kAdbConnectionThreadName));
+  ScopedLocalRef<jobject> thr_group(
+      env,
+      env->GetStaticObjectField(art::WellKnownClasses::java_lang_ThreadGroup,
+                                art::WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup));
+  return env->NewObject(art::WellKnownClasses::java_lang_Thread,
+                        art::WellKnownClasses::java_lang_Thread_init,
+                        thr_group.get(),
+                        thr_name.get(),
+                        /*Priority*/ 0,
+                        /*Daemon*/ true);
+}
+
+struct CallbackData {
+  AdbConnectionState* this_;
+  jobject thr_;
+};
+
+static void* CallbackFunction(void* vdata) {
+  std::unique_ptr<CallbackData> data(reinterpret_cast<CallbackData*>(vdata));
+  art::Thread* self = art::Thread::Attach(kAdbConnectionThreadName,
+                                          true,
+                                          data->thr_);
+  CHECK(self != nullptr) << "threads_being_born_ should have ensured thread could be attached.";
+  // The name in Attach() is only for logging. Set the thread name. This is important so
+  // that the thread is no longer seen as starting up.
+  {
+    art::ScopedObjectAccess soa(self);
+    self->SetThreadName(kAdbConnectionThreadName);
+  }
+
+  // Release the peer.
+  JNIEnv* env = self->GetJniEnv();
+  env->DeleteGlobalRef(data->thr_);
+  data->thr_ = nullptr;
+  {
+    // The StartThreadBirth was called in the parent thread. We let the runtime know we are up
+    // before going into the provided code.
+    art::MutexLock mu(self, *art::Locks::runtime_shutdown_lock_);
+    art::Runtime::Current()->EndThreadBirth();
+  }
+  data->this_->RunPollLoop(self);
+  int detach_result = art::Runtime::Current()->GetJavaVM()->DetachCurrentThread();
+  CHECK_EQ(detach_result, 0);
+
+  return nullptr;
+}
+
+void AdbConnectionState::StartDebuggerThreads() {
+  // First do all the final setup we need.
+  CHECK_EQ(adb_write_event_fd_.get(), -1);
+  CHECK_EQ(sleep_event_fd_.get(), -1);
+  CHECK_EQ(local_agent_control_sock_.get(), -1);
+  CHECK_EQ(remote_agent_control_sock_.get(), -1);
+
+  sleep_event_fd_.reset(eventfd(kEventfdLocked, EFD_CLOEXEC));
+  CHECK_NE(sleep_event_fd_.get(), -1) << "Unable to create wakeup eventfd.";
+  adb_write_event_fd_.reset(eventfd(kEventfdUnlocked, EFD_CLOEXEC));
+  CHECK_NE(adb_write_event_fd_.get(), -1) << "Unable to create write-lock eventfd.";
+
+  {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+    art::Runtime::Current()->GetRuntimeCallbacks()->AddDdmCallback(&ddm_callback_);
+  }
+  // Setup the socketpair we use to talk to the agent.
+  bool has_sockets;
+  do {
+    has_sockets = android::base::Socketpair(AF_UNIX,
+                                            SOCK_SEQPACKET | SOCK_CLOEXEC,
+                                            0,
+                                            &local_agent_control_sock_,
+                                            &remote_agent_control_sock_);
+  } while (!has_sockets && errno == EINTR);
+  if (!has_sockets) {
+    PLOG(FATAL) << "Unable to create socketpair for agent control!";
+  }
+
+  // Next start the threads.
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  {
+    art::Runtime* runtime = art::Runtime::Current();
+    art::MutexLock mu(self, *art::Locks::runtime_shutdown_lock_);
+    if (runtime->IsShuttingDownLocked()) {
+      // The runtime is shutting down so we cannot create new threads. This shouldn't really happen.
+      LOG(ERROR) << "The runtime is shutting down when we are trying to start up the debugger!";
+      return;
+    }
+    runtime->StartThreadBirth();
+  }
+  ScopedLocalRef<jobject> thr(soa.Env(), CreateAdbConnectionThread(soa.Self()));
+  pthread_t pthread;
+  std::unique_ptr<CallbackData> data(new CallbackData { this, soa.Env()->NewGlobalRef(thr.get()) });
+  int pthread_create_result = pthread_create(&pthread,
+                                             nullptr,
+                                             &CallbackFunction,
+                                             data.get());
+  if (pthread_create_result != 0) {
+    // If the create succeeded the other thread will call EndThreadBirth.
+    art::Runtime* runtime = art::Runtime::Current();
+    soa.Env()->DeleteGlobalRef(data->thr_);
+    LOG(ERROR) << "Failed to create thread for adb-jdwp connection manager!";
+    art::MutexLock mu(art::Thread::Current(), *art::Locks::runtime_shutdown_lock_);
+    runtime->EndThreadBirth();
+    return;
+  }
+  data.release();
+}
+
+static bool FlagsSet(int16_t data, int16_t flags) {
+  return (data & flags) == flags;
+}
+
+void AdbConnectionState::CloseFds() {
+  // Lock the write_event_fd so that concurrent PublishDdms will see that the connection is closed.
+  ScopedEventFdLock lk(adb_write_event_fd_);
+  // shutdown(adb_connection_socket_, SHUT_RDWR);
+  adb_connection_socket_.reset();
+}
+
+uint32_t AdbConnectionState::NextDdmId() {
+  // Just have a normal counter but always set the sign bit.
+  return (next_ddm_id_++) | 0x80000000;
+}
+
+void AdbConnectionState::PublishDdmData(uint32_t type, const art::ArrayRef<const uint8_t>& data) {
+  // Get the write_event early to fail fast.
+  ScopedEventFdLock lk(adb_write_event_fd_);
+  if (adb_connection_socket_ == -1) {
+    LOG(WARNING) << "Not sending ddms data of type "
+                 << StringPrintf("%c%c%c%c",
+                                 static_cast<char>(type >> 24),
+                                 static_cast<char>(type >> 16),
+                                 static_cast<char>(type >> 8),
+                                 static_cast<char>(type)) << " due to no connection!";
+    // Adb is not connected.
+    return;
+  }
+
+  // the adb_write_event_fd_ will ensure that the adb_connection_socket_ will not go away until
+  // after we have sent our data.
+  static constexpr uint32_t kDdmPacketHeaderSize =
+      kJDWPHeaderLen       // jdwp command packet size
+      + sizeof(uint32_t)   // Type
+      + sizeof(uint32_t);  // length
+  std::array<uint8_t, kDdmPacketHeaderSize> pkt;
+  uint8_t* pkt_data = pkt.data();
+
+  // Write the length first.
+  *reinterpret_cast<uint32_t*>(pkt_data) = htonl(kDdmPacketHeaderSize + data.size());
+  pkt_data += sizeof(uint32_t);
+
+  // Write the id next;
+  *reinterpret_cast<uint32_t*>(pkt_data) = htonl(NextDdmId());
+  pkt_data += sizeof(uint32_t);
+
+  // next the flags. (0 for cmd packet because DDMS).
+  *(pkt_data++) = 0;
+  // Now the cmd-set
+  *(pkt_data++) = kJDWPDdmCmdSet;
+  // Now the command
+  *(pkt_data++) = kJDWPDdmCmd;
+
+  // now the type.
+  *reinterpret_cast<uint32_t*>(pkt_data) = htonl(type);
+  pkt_data += sizeof(uint32_t);
+
+  // Now the data.size()
+  *reinterpret_cast<uint32_t*>(pkt_data) = htonl(data.size());
+  pkt_data += sizeof(uint32_t);
+
+  static uint32_t constexpr kIovSize = 2;
+  struct iovec iovs[kIovSize] = {
+    { pkt.data(), pkt.size() },
+    { const_cast<uint8_t*>(data.data()), data.size() },
+  };
+  // now pkt_header has the header.
+  // use writev to send the actual data.
+  ssize_t res = TEMP_FAILURE_RETRY(writev(adb_connection_socket_, iovs, kIovSize));
+  if (static_cast<size_t>(res) != (kDdmPacketHeaderSize + data.size())) {
+    PLOG(ERROR) << StringPrintf("Failed to send DDMS packet %c%c%c%c to debugger (%zd of %zu)",
+                                static_cast<char>(type >> 24),
+                                static_cast<char>(type >> 16),
+                                static_cast<char>(type >> 8),
+                                static_cast<char>(type),
+                                res, data.size() + kDdmPacketHeaderSize);
+  } else {
+    VLOG(jdwp) << StringPrintf("sent DDMS packet %c%c%c%c to debugger %zu",
+                               static_cast<char>(type >> 24),
+                               static_cast<char>(type >> 16),
+                               static_cast<char>(type >> 8),
+                               static_cast<char>(type),
+                               data.size() + kDdmPacketHeaderSize);
+  }
+}
+
+void AdbConnectionState::SendAgentFds() {
+  // TODO
+  DCHECK(!sent_agent_fds_);
+  char dummy = '!';
+  union {
+    cmsghdr cm;
+    char buffer[CMSG_SPACE(dt_fd_forward::FdSet::kDataLength)];
+  } cm_un;
+  iovec iov;
+  iov.iov_base       = &dummy;
+  iov.iov_len        = 1;
+
+  msghdr msg;
+  msg.msg_name       = nullptr;
+  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   = CMSG_LEN(dt_fd_forward::FdSet::kDataLength);
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type  = SCM_RIGHTS;
+
+  // Duplicate the fds before sending them.
+  android::base::unique_fd read_fd(dup(adb_connection_socket_));
+  CHECK_NE(read_fd.get(), -1) << "Failed to dup read_fd_: " << strerror(errno);
+  android::base::unique_fd write_fd(dup(adb_connection_socket_));
+  CHECK_NE(write_fd.get(), -1) << "Failed to dup write_fd: " << strerror(errno);
+  android::base::unique_fd write_lock_fd(dup(adb_write_event_fd_));
+  CHECK_NE(write_lock_fd.get(), -1) << "Failed to dup write_lock_fd: " << strerror(errno);
+
+  dt_fd_forward::FdSet {
+    read_fd.get(), write_fd.get(), write_lock_fd.get()
+  }.WriteData(CMSG_DATA(cmsg));
+
+  int res = TEMP_FAILURE_RETRY(sendmsg(local_agent_control_sock_, &msg, MSG_EOR));
+  if (res < 0) {
+    PLOG(ERROR) << "Failed to send agent adb connection fds.";
+  } else {
+    sent_agent_fds_ = true;
+    VLOG(jdwp) << "Fds have been sent to jdwp agent!";
+  }
+}
+
+android::base::unique_fd AdbConnectionState::ReadFdFromAdb() {
+  // We don't actually care about the data that is sent. We do need to receive something though.
+  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       = nullptr;
+  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;
+  (reinterpret_cast<int*>(CMSG_DATA(cmsg)))[0] = -1;
+
+  int rc = TEMP_FAILURE_RETRY(recvmsg(control_sock_, &msg, 0));
+
+  if (rc <= 0) {
+    PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << control_sock_ << ")";
+    return android::base::unique_fd(-1);
+  } else {
+    VLOG(jdwp) << "Fds have been received from ADB!";
+  }
+
+  return android::base::unique_fd((reinterpret_cast<int*>(CMSG_DATA(cmsg)))[0]);
+}
+
+bool AdbConnectionState::SetupAdbConnection() {
+  int        sleep_ms     = 500;
+  const int  sleep_max_ms = 2*1000;
+  char       buff[sizeof(pid_t) + 1];
+
+  android::base::unique_fd sock(socket(AF_UNIX, SOCK_SEQPACKET, 0));
+  if (sock < 0) {
+    PLOG(ERROR) << "Could not create ADB control socket";
+    return false;
+  }
+  struct timeval timeout;
+  timeout.tv_sec = kControlSockSendTimeout;
+  timeout.tv_usec = 0;
+  setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
+  snprintf(buff, sizeof(buff), "%04x", getpid());
+  buff[sizeof(pid_t)] = 0;
+
+  while (!shutting_down_) {
+    // 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(sock, &control_addr_.controlAddrPlain, control_addr_len_);
+    if (ret == 0) {
+      bool trusted = sock >= 0;
+#ifdef ART_TARGET_ANDROID
+      // Needed for socket_peer_is_trusted.
+      trusted = trusted && socket_peer_is_trusted(sock);
+#endif
+      if (!trusted) {
+        LOG(ERROR) << "adb socket is not trusted. Aborting connection.";
+        if (sock >= 0 && shutdown(sock, SHUT_RDWR)) {
+          PLOG(ERROR) << "trouble shutting down socket";
+        }
+        return false;
+      }
+      /* now try to send our pid to the ADB daemon */
+      ret = TEMP_FAILURE_RETRY(send(sock, buff, sizeof(pid_t), 0));
+      if (ret == sizeof(pid_t)) {
+        LOG(INFO) << "PID " << getpid() << " send to adb";
+        control_sock_ = std::move(sock);
+        return true;
+      } else {
+        PLOG(ERROR) << "Weird, can't send JDWP process pid to ADB. Aborting connection.";
+        return false;
+      }
+    } else {
+      PLOG(ERROR) << "Can't connect to ADB control socket. Will retry.";
+
+      usleep(sleep_ms * 1000);
+
+      sleep_ms += (sleep_ms >> 1);
+      if (sleep_ms > sleep_max_ms) {
+        sleep_ms = sleep_max_ms;
+      }
+    }
+  }
+  return false;
+}
+
+void AdbConnectionState::RunPollLoop(art::Thread* self) {
+  CHECK_EQ(self->GetState(), art::kNative);
+  art::Locks::mutator_lock_->AssertNotHeld(self);
+  self->SetState(art::kWaitingInMainDebuggerLoop);
+  // shutting_down_ set by StopDebuggerThreads
+  while (!shutting_down_) {
+    // First get the control_sock_ from adb if we don't have one. We only need to do this once.
+    if (control_sock_ == -1 && !SetupAdbConnection()) {
+      LOG(ERROR) << "Failed to setup adb connection.";
+      return;
+    }
+    while (!shutting_down_ && control_sock_ != -1) {
+      struct pollfd pollfds[4] = {
+        { sleep_event_fd_, POLLIN, 0 },
+        // -1 as an fd causes it to be ignored by poll
+        { (agent_loaded_ ? local_agent_control_sock_ : -1), POLLIN, 0 },
+        // Check for the control_sock_ actually going away. Only do this if we don't have an active
+        // connection.
+        { (adb_connection_socket_ == -1 ? control_sock_ : -1), POLLIN | POLLRDHUP, 0 },
+        // if we have not loaded the agent either the adb_connection_socket_ is -1 meaning we don't
+        // have a real connection yet or the socket through adb needs to be listened to for incoming
+        // data that the agent can handle.
+        { ((!agent_has_socket_ && !sent_agent_fds_) ? adb_connection_socket_ : -1), POLLIN, 0 }
+      };
+      int res = TEMP_FAILURE_RETRY(poll(pollfds, 4, -1));
+      if (res < 0) {
+        PLOG(ERROR) << "Failed to poll!";
+        return;
+      }
+      // We don't actually care about doing this we just use it to wake us up.
+      // const struct pollfd& sleep_event_poll     = pollfds[0];
+      const struct pollfd& agent_control_sock_poll = pollfds[1];
+      const struct pollfd& control_sock_poll       = pollfds[2];
+      const struct pollfd& adb_socket_poll         = pollfds[3];
+      if (FlagsSet(agent_control_sock_poll.revents, POLLIN)) {
+        DCHECK(agent_loaded_);
+        char buf[257];
+        res = TEMP_FAILURE_RETRY(recv(local_agent_control_sock_, buf, sizeof(buf) - 1, 0));
+        if (res < 0) {
+          PLOG(ERROR) << "Failed to read message from agent control socket! Retrying";
+          continue;
+        } else {
+          buf[res + 1] = '\0';
+          VLOG(jdwp) << "Local agent control sock has data: " << static_cast<const char*>(buf);
+        }
+        if (memcmp(kListenStartMessage, buf, sizeof(kListenStartMessage)) == 0) {
+          agent_listening_ = true;
+          if (adb_connection_socket_ != -1) {
+            SendAgentFds();
+          }
+        } else if (memcmp(kListenEndMessage, buf, sizeof(kListenEndMessage)) == 0) {
+          agent_listening_ = false;
+        } else if (memcmp(kCloseMessage, buf, sizeof(kCloseMessage)) == 0) {
+          CloseFds();
+          agent_has_socket_ = false;
+        } else if (memcmp(kAcceptMessage, buf, sizeof(kAcceptMessage)) == 0) {
+          agent_has_socket_ = true;
+          sent_agent_fds_ = false;
+        } else {
+          LOG(ERROR) << "Unknown message received from debugger! '" << std::string(buf) << "'";
+        }
+      } else if (FlagsSet(control_sock_poll.revents, POLLIN)) {
+        bool maybe_send_fds = false;
+        {
+          // Hold onto this lock so that concurrent ddm publishes don't try to use an illegal fd.
+          ScopedEventFdLock sefdl(adb_write_event_fd_);
+          android::base::unique_fd new_fd(ReadFdFromAdb());
+          if (new_fd == -1) {
+            // Something went wrong. We need to retry getting the control socket.
+            PLOG(ERROR) << "Something went wrong getting fds from adb. Retry!";
+            control_sock_.reset();
+            break;
+          } else if (adb_connection_socket_ != -1) {
+            // We already have a connection.
+            VLOG(jdwp) << "Ignoring second debugger. Accept then drop!";
+            if (new_fd >= 0) {
+              new_fd.reset();
+            }
+          } else {
+            VLOG(jdwp) << "Adb connection established with fd " << new_fd;
+            adb_connection_socket_ = std::move(new_fd);
+            maybe_send_fds = true;
+          }
+        }
+        if (maybe_send_fds && agent_loaded_ && agent_listening_) {
+          VLOG(jdwp) << "Sending fds as soon as we received them.";
+          SendAgentFds();
+        }
+      } else if (FlagsSet(control_sock_poll.revents, POLLRDHUP)) {
+        // The other end of the adb connection just dropped it.
+        // Reset the connection since we don't have an active socket through the adb server.
+        DCHECK(!agent_has_socket_) << "We shouldn't be doing anything if there is already a "
+                                   << "connection active";
+        control_sock_.reset();
+        break;
+      } else if (FlagsSet(adb_socket_poll.revents, POLLIN)) {
+        DCHECK(!agent_has_socket_);
+        if (!agent_loaded_) {
+          DCHECK(!agent_listening_);
+          // Load the agent now!
+          self->AssertNoPendingException();
+          art::Runtime::Current()->AttachAgent(MakeAgentArg());
+          if (self->IsExceptionPending()) {
+            LOG(ERROR) << "Failed to load agent " << agent_name_;
+            art::ScopedObjectAccess soa(self);
+            self->GetException()->Dump();
+            self->ClearException();
+            return;
+          }
+          agent_loaded_ = true;
+        } else if (agent_listening_ && !sent_agent_fds_) {
+          VLOG(jdwp) << "Sending agent fds again on data.";
+          SendAgentFds();
+        }
+      } else {
+        VLOG(jdwp) << "Woke up poll without anything to do!";
+      }
+    }
+  }
+}
+
+std::string AdbConnectionState::MakeAgentArg() {
+  // TODO Get this from something user settable?
+  const std::string& opts = art::Runtime::Current()->GetJdwpOptions();
+  return agent_name_ + "=" + opts + (opts.empty() ? "" : ",")
+      + "transport=dt_fd_forward,address=" + std::to_string(remote_agent_control_sock_);
+}
+
+void AdbConnectionState::StopDebuggerThreads() {
+  // The regular agent system will take care of unloading the agent (if needed).
+  shutting_down_ = true;
+  // Wakeup the poll loop.
+  uint64_t data = 1;
+  TEMP_FAILURE_RETRY(write(sleep_event_fd_, &data, sizeof(data)));
+}
+
+// The plugin initialization function.
+extern "C" bool ArtPlugin_Initialize() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  DCHECK(art::Runtime::Current()->GetJdwpProvider() == art::JdwpProvider::kAdbConnection);
+  // TODO Provide some way for apps to set this maybe?
+  gState = new AdbConnectionState(kDefaultJdwpAgentName);
+  CHECK(gState != nullptr);
+  return true;
+}
+
+extern "C" bool ArtPlugin_Deinitialize() {
+  CHECK(gState != nullptr);
+  // Just do this a second time?
+  // TODO I don't think this should be needed.
+  gState->StopDebuggerThreads();
+  delete gState;
+  return true;
+}
+
+}  // namespace adbconnection
diff --git a/adbconnection/adbconnection.h b/adbconnection/adbconnection.h
new file mode 100644
index 0000000..28a5a05
--- /dev/null
+++ b/adbconnection/adbconnection.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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_ADBCONNECTION_ADBCONNECTION_H_
+#define ART_ADBCONNECTION_ADBCONNECTION_H_
+
+#include <stdint.h>
+#include <vector>
+#include <limits>
+
+#include "android-base/unique_fd.h"
+
+#include "base/mutex.h"
+#include "runtime_callbacks.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <jni.h>
+
+namespace adbconnection {
+
+static constexpr char kJdwpControlName[] = "\0jdwp-control";
+static constexpr char kAdbConnectionThreadName[] = "ADB-JDWP Connection Control Thread";
+
+// The default jdwp agent name.
+static constexpr char kDefaultJdwpAgentName[] = "libjdwp.so";
+
+class AdbConnectionState;
+
+struct AdbConnectionDebuggerController : public art::DebuggerControlCallback {
+  explicit AdbConnectionDebuggerController(AdbConnectionState* connection)
+      : connection_(connection) {}
+
+  // Begin running the debugger.
+  void StartDebugger() OVERRIDE;
+
+  // The debugger should begin shutting down since the runtime is ending.
+  void StopDebugger() OVERRIDE;
+
+  bool IsDebuggerConfigured() OVERRIDE;
+
+ private:
+  AdbConnectionState* connection_;
+};
+
+struct AdbConnectionDdmCallback : public art::DdmCallback {
+  explicit AdbConnectionDdmCallback(AdbConnectionState* connection) : connection_(connection) {}
+
+  void DdmPublishChunk(uint32_t type,
+                       const art::ArrayRef<const uint8_t>& data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+  AdbConnectionState* connection_;
+};
+
+class AdbConnectionState {
+ public:
+  explicit AdbConnectionState(const std::string& agent_name);
+
+  // Called on the listening thread to start dealing with new input. thr is used to attach the new
+  // thread to the runtime.
+  void RunPollLoop(art::Thread* self);
+
+  // Sends ddms data over the socket, if there is one. This data is sent even if we haven't finished
+  // hand-shaking yet.
+  void PublishDdmData(uint32_t type, const art::ArrayRef<const uint8_t>& data);
+
+  // Stops debugger threads during shutdown.
+  void StopDebuggerThreads();
+
+ private:
+  uint32_t NextDdmId();
+
+  void StartDebuggerThreads();
+
+  // Tell adbd about the new runtime.
+  bool SetupAdbConnection();
+
+  std::string MakeAgentArg();
+
+  android::base::unique_fd ReadFdFromAdb();
+
+  void SendAgentFds();
+
+  void CloseFds();
+
+  std::string agent_name_;
+
+  AdbConnectionDebuggerController controller_;
+  AdbConnectionDdmCallback ddm_callback_;
+
+  // Eventfd used to allow the StopDebuggerThreads function to wake up sleeping threads
+  android::base::unique_fd sleep_event_fd_;
+
+  // Socket that we use to talk to adbd.
+  android::base::unique_fd control_sock_;
+
+  // Socket that we use to talk to the agent (if it's loaded).
+  android::base::unique_fd local_agent_control_sock_;
+
+  // The fd of the socket the agent uses to talk to us. We need to keep it around in order to clean
+  // it up when the runtime goes away.
+  android::base::unique_fd remote_agent_control_sock_;
+
+  // The fd that is forwarded through adb to the client. This is guarded by the
+  // adb_write_event_fd_.
+  android::base::unique_fd adb_connection_socket_;
+
+  // The fd we send to the agent to let us synchronize access to the shared adb_connection_socket_.
+  // This is also used as a general lock for the adb_connection_socket_ on any threads other than
+  // the poll thread.
+  android::base::unique_fd adb_write_event_fd_;
+
+  std::atomic<bool> shutting_down_;
+
+  // True if we have loaded the agent library.
+  std::atomic<bool> agent_loaded_;
+
+  // True if the dt_fd_forward transport is listening for a new communication channel.
+  std::atomic<bool> agent_listening_;
+
+  // True if the dt_fd_forward transport has the socket. If so we don't do anything to the agent or
+  // the adb connection socket until connection goes away.
+  std::atomic<bool> agent_has_socket_;
+
+  std::atomic<bool> sent_agent_fds_;
+
+  std::atomic<uint32_t> next_ddm_id_;
+
+  socklen_t control_addr_len_;
+  union {
+    sockaddr_un controlAddrUn;
+    sockaddr controlAddrPlain;
+  } control_addr_;
+
+  friend struct AdbConnectionDebuggerController;
+};
+
+}  // namespace adbconnection
+
+#endif  // ART_ADBCONNECTION_ADBCONNECTION_H_
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index c438c54..5d67206 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -20,6 +20,7 @@
 
 #include "gtest/gtest.h"
 
+#include "jdwp_provider.h"
 #include "experimental_flags.h"
 #include "parsed_options.h"
 #include "runtime.h"
@@ -364,48 +365,40 @@
 }  // TEST_F
 
 /*
- * {"-Xrunjdwp:_", "-agentlib:jdwp=_"}
+ * { "-XjdwpProvider:_" }
  */
-TEST_F(CmdlineParserTest, TestJdwpOptions) {
-  /*
-   * Test success
-   */
+TEST_F(CmdlineParserTest, TestJdwpProviderEmpty) {
   {
-    /*
-     * "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
-     */
-    JDWP::JdwpOptions opt = JDWP::JdwpOptions();
-    opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket;
-    opt.port = 8000;
-    opt.server = true;
-
-    const char *opt_args = "-Xrunjdwp:transport=dt_socket,address=8000,server=y";
-
-    EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions);
+    EXPECT_SINGLE_PARSE_DEFAULT_VALUE(JdwpProvider::kInternal, "", M::JdwpProvider);
   }
+}  // TEST_F
 
-  {
-    /*
-     * "Example: -agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n\n");
-     */
-    JDWP::JdwpOptions opt = JDWP::JdwpOptions();
-    opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket;
-    opt.host = "localhost";
-    opt.port = 6500;
-    opt.server = false;
+TEST_F(CmdlineParserTest, TestJdwpProviderDefault) {
+  const char* opt_args = "-XjdwpProvider:default";
+  EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider);
+}  // TEST_F
 
-    const char *opt_args = "-agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n";
+TEST_F(CmdlineParserTest, TestJdwpProviderInternal) {
+  const char* opt_args = "-XjdwpProvider:internal";
+  EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider);
+}  // TEST_F
 
-    EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions);
-  }
+TEST_F(CmdlineParserTest, TestJdwpProviderNone) {
+  const char* opt_args = "-XjdwpProvider:none";
+  EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kNone, opt_args, M::JdwpProvider);
+}  // TEST_F
 
-  /*
-   * Test failures
-   */
-  EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:help", CmdlineResult::kUsage);  // usage for help only
-  EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:blabla", CmdlineResult::kFailure);  // invalid subarg
-  EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=help", CmdlineResult::kUsage);  // usage for help only
-  EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=blabla", CmdlineResult::kFailure);  // invalid subarg
+TEST_F(CmdlineParserTest, TestJdwpProviderAdbconnection) {
+  const char* opt_args = "-XjdwpProvider:adbconnection";
+  EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kAdbConnection, opt_args, M::JdwpProvider);
+}  // TEST_F
+
+TEST_F(CmdlineParserTest, TestJdwpProviderHelp) {
+  EXPECT_SINGLE_PARSE_FAIL("-XjdwpProvider:help", CmdlineResult::kUsage);
+}  // TEST_F
+
+TEST_F(CmdlineParserTest, TestJdwpProviderFail) {
+  EXPECT_SINGLE_PARSE_FAIL("-XjdwpProvider:blablabla", CmdlineResult::kFailure);
 }  // TEST_F
 
 /*
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index f12ef97..529fe2b 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -34,6 +34,7 @@
 #include "gc/collector_type.h"
 #include "gc/space/large_object_space.h"
 #include "jdwp/jdwp.h"
+#include "jdwp_provider.h"
 #include "jit/profile_saver_options.h"
 #include "plugin.h"
 #include "read_barrier_config.h"
@@ -64,123 +65,30 @@
 };
 
 template <>
-struct CmdlineType<JDWP::JdwpOptions> : CmdlineTypeParser<JDWP::JdwpOptions> {
+struct CmdlineType<JdwpProvider> : CmdlineTypeParser<JdwpProvider> {
   /*
-   * Handle one of the JDWP name/value pairs.
-   *
-   * JDWP options are:
-   *  help: if specified, show help message and bail
-   *  transport: may be dt_socket or dt_shmem
-   *  address: for dt_socket, "host:port", or just "port" when listening
-   *  server: if "y", wait for debugger to attach; if "n", attach to debugger
-   *  timeout: how long to wait for debugger to connect / listen
-   *
-   * Useful with server=n (these aren't supported yet):
-   *  onthrow=<exception-name>: connect to debugger when exception thrown
-   *  onuncaught=y|n: connect to debugger when uncaught exception thrown
-   *  launch=<command-line>: launch the debugger itself
-   *
-   * The "transport" option is required, as is "address" if server=n.
+   * Handle a single JDWP provider name. Must be either 'internal', 'default', or the file name of
+   * an agent. A plugin will make use of this and the jdwpOptions to set up jdwp when appropriate.
    */
-  Result Parse(const std::string& options) {
-    VLOG(jdwp) << "ParseJdwpOptions: " << options;
-
-    if (options == "help") {
+  Result Parse(const std::string& option) {
+    if (option == "help") {
       return Result::Usage(
-          "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
-          "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n");
-    }
-
-    const std::string s;
-
-    std::vector<std::string> pairs;
-    Split(options, ',', &pairs);
-
-    JDWP::JdwpOptions jdwp_options;
-
-    for (const std::string& jdwp_option : pairs) {
-      std::string::size_type equals_pos = jdwp_option.find('=');
-      if (equals_pos == std::string::npos) {
-        return Result::Failure(s +
-            "Can't parse JDWP option '" + jdwp_option + "' in '" + options + "'");
-      }
-
-      Result parse_attempt = ParseJdwpOption(jdwp_option.substr(0, equals_pos),
-                                             jdwp_option.substr(equals_pos + 1),
-                                             &jdwp_options);
-      if (parse_attempt.IsError()) {
-        // We fail to parse this JDWP option.
-        return parse_attempt;
-      }
-    }
-
-    if (jdwp_options.transport == JDWP::kJdwpTransportUnknown) {
-      return Result::Failure(s + "Must specify JDWP transport: " + options);
-    }
-    if (!jdwp_options.server && (jdwp_options.host.empty() || jdwp_options.port == 0)) {
-      return Result::Failure(s + "Must specify JDWP host and port when server=n: " + options);
-    }
-
-    return Result::Success(std::move(jdwp_options));
-  }
-
-  Result ParseJdwpOption(const std::string& name, const std::string& value,
-                         JDWP::JdwpOptions* jdwp_options) {
-    if (name == "transport") {
-      if (value == "dt_socket") {
-        jdwp_options->transport = JDWP::kJdwpTransportSocket;
-      } else if (value == "dt_android_adb") {
-        jdwp_options->transport = JDWP::kJdwpTransportAndroidAdb;
-      } else {
-        return Result::Failure("JDWP transport not supported: " + value);
-      }
-    } else if (name == "server") {
-      if (value == "n") {
-        jdwp_options->server = false;
-      } else if (value == "y") {
-        jdwp_options->server = true;
-      } else {
-        return Result::Failure("JDWP option 'server' must be 'y' or 'n'");
-      }
-    } else if (name == "suspend") {
-      if (value == "n") {
-        jdwp_options->suspend = false;
-      } else if (value == "y") {
-        jdwp_options->suspend = true;
-      } else {
-        return Result::Failure("JDWP option 'suspend' must be 'y' or 'n'");
-      }
-    } else if (name == "address") {
-      /* this is either <port> or <host>:<port> */
-      std::string port_string;
-      jdwp_options->host.clear();
-      std::string::size_type colon = value.find(':');
-      if (colon != std::string::npos) {
-        jdwp_options->host = value.substr(0, colon);
-        port_string = value.substr(colon + 1);
-      } else {
-        port_string = value;
-      }
-      if (port_string.empty()) {
-        return Result::Failure("JDWP address missing port: " + value);
-      }
-      char* end;
-      uint64_t port = strtoul(port_string.c_str(), &end, 10);
-      if (*end != '\0' || port > 0xffff) {
-        return Result::Failure("JDWP address has junk in port field: " + value);
-      }
-      jdwp_options->port = port;
-    } else if (name == "launch" || name == "onthrow" || name == "oncaught" || name == "timeout") {
-      /* valid but unsupported */
-      LOG(INFO) << "Ignoring JDWP option '" << name << "'='" << value << "'";
+          "Example: -XjdwpProvider:none to disable JDWP\n"
+          "Example: -XjdwpProvider:internal for internal jdwp implementation\n"
+          "Example: -XjdwpProvider:adbconnection for adb connection mediated jdwp implementation\n"
+          "Example: -XjdwpProvider:default for the default jdwp implementation"
+          " (currently internal)\n");
+    } else if (option == "internal" || option == "default") {
+      return Result::Success(JdwpProvider::kInternal);
+    } else if (option == "adbconnection") {
+      return Result::Success(JdwpProvider::kAdbConnection);
+    } else if (option == "none") {
+      return Result::Success(JdwpProvider::kNone);
     } else {
-      LOG(INFO) << "Ignoring unrecognized JDWP option '" << name << "'='" << value << "'";
+      return Result::Failure(std::string("not a valid jdwp provider: ") + option);
     }
-
-    return Result::SuccessNoValue();
   }
-
-  static const char* Name() { return "JdwpOptions"; }
+  static const char* Name() { return "JdwpProvider"; }
 };
 
 template <size_t Divisor>
diff --git a/dt_fd_forward/Android.bp b/dt_fd_forward/Android.bp
new file mode 100644
index 0000000..1ba2323
--- /dev/null
+++ b/dt_fd_forward/Android.bp
@@ -0,0 +1,66 @@
+//
+// Copyright (C) 2017 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.
+//
+
+// Build variants {target,host} x {debug,ndebug} x {32,64}
+
+cc_defaults {
+    name: "dt_fd_forward-defaults",
+    host_supported: true,
+    srcs: ["dt_fd_forward.cc"],
+    defaults: ["art_defaults"],
+
+    // Note that this tool needs to be built for both 32-bit and 64-bit since it needs to be same
+    // ISA as what it is attached to.
+    compile_multilib: "both",
+
+    shared_libs: [
+        "libbase",
+    ],
+    target: {
+        android: {
+        },
+        host: {
+        },
+        darwin: {
+            enabled: false,
+        },
+    },
+    header_libs: [
+        "javavm_headers",
+        "dt_fd_forward_export",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+}
+
+art_cc_library {
+    name: "libdt_fd_forward",
+    defaults: ["dt_fd_forward-defaults"],
+}
+
+art_cc_library {
+    name: "libdt_fd_forwardd",
+    defaults: [
+        "art_debug_defaults",
+        "dt_fd_forward-defaults",
+    ],
+}
diff --git a/dt_fd_forward/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/dt_fd_forward/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dt_fd_forward/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/dt_fd_forward/NOTICE b/dt_fd_forward/NOTICE
new file mode 100644
index 0000000..4fd88fa
--- /dev/null
+++ b/dt_fd_forward/NOTICE
@@ -0,0 +1,30 @@
+Copyright (C) 2017 The Android Open Source Project
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This file implements interfaces from the file jdwpTransport.h. This
+implementation is licensed under the same terms as the file
+jdwpTransport.h. The copyright and license information for the file
+jdwpTransport.h follows.
+
+Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+
+This code is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License version 2 only, as
+published by the Free Software Foundation.  Oracle designates this
+particular file as subject to the "Classpath" exception as provided
+by Oracle in the LICENSE file that accompanied this code.
+
+This code is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+version 2 for more details (a copy is included in the LICENSE file that
+accompanied this code).
+
+You should have received a copy of the GNU General Public License version
+2 along with this work; if not, write to the Free Software Foundation,
+Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+or visit www.oracle.com if you need additional information or have any
+questions.
diff --git a/dt_fd_forward/README.md b/dt_fd_forward/README.md
new file mode 100644
index 0000000..c7ac840
--- /dev/null
+++ b/dt_fd_forward/README.md
@@ -0,0 +1,32 @@
+# dt_fd_forward
+
+dt_fd_forward is a jdwpTransport library. It implements the [Java Debug Wire
+Protocol Transport Interface
+(jdwpTransport)](https://docs.oracle.com/javase/7/docs/technotes/guides/jpda/jdwpTransport.html).
+It allows one to handle and proxy JDWP traffic by supplying the implementation
+for Attach. This transport requires an address. The address is a single integer
+value that is the file-descriptor of an open AF\_UNIX socket.
+
+When this transport begins listening or attaching it will send the
+null-terminated string "dt_fd_forward:START-LISTEN\0" over the given socket.
+
+When this transport stops listening for connections it will send the
+null-terminated string "dt_fd_forward:END-LISTEN\0" over the socket.
+
+When this transport has successfully received fds from the proxy it sends the
+message "dt_fd_forward:ATTACHED\0" over the socket.
+
+When this transport has closed its copies of the fds it will send the proxy the
+message "dt_fd_forward:CLOSING\0" over the socket.
+
+When this transport accepts or attaches to a connection it will read from the
+socket a 1 byte message and 3 file-descriptors. The file descriptors are, in
+order, an fd that will be read from to get incoming JDWP packets (read\_fd\_),
+an fd that outgoing JDWP packets will be written to (write\_fd\_), and an
+_eventfd_ (write\_lock\_fd\_). The eventfd should not have any flags set. Prior
+to writing any data to write\_fd\_ the transport will _read_ from the
+write\_lock\_fd\_ and after finishing the write it will _write_ to it. This
+allows one to safely multiplex data on the write\_fd\_.
+
+This transport implements no optional capabilities, though this may change in
+the future.
diff --git a/dt_fd_forward/dt_fd_forward.cc b/dt_fd_forward/dt_fd_forward.cc
new file mode 100644
index 0000000..cf3088d
--- /dev/null
+++ b/dt_fd_forward/dt_fd_forward.cc
@@ -0,0 +1,761 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jdwpTransport.h. This
+ * implementation is licensed under the same terms as the file
+ * jdwpTransport.h. The copyright and license information for the file
+ * jdwpTransport.h follows.
+ *
+ * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "dt_fd_forward.h"
+
+#include <string>
+#include <vector>
+
+#include <android-base/endian.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+
+#include <sys/ioctl.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <jni.h>
+#include <jdwpTransport.h>
+
+namespace dt_fd_forward {
+
+// Helper that puts line-number in error message.
+#define DT_IO_ERROR(f) \
+    SetLastError(::android::base::StringPrintf("%s:%d - %s: %s", \
+                                               __FILE__, __LINE__, f, strerror(errno)))
+
+extern const jdwpTransportNativeInterface_ gTransportInterface;
+
+template <typename T> static T HostToNetwork(T in);
+template <typename T> static T NetworkToHost(T in);
+
+template<> int8_t HostToNetwork(int8_t in) { return in; }
+template<> int8_t NetworkToHost(int8_t in) { return in; }
+template<> int16_t HostToNetwork(int16_t in) { return htons(in); }
+template<> int16_t NetworkToHost(int16_t in) { return ntohs(in); }
+template<> int32_t HostToNetwork(int32_t in) { return htonl(in); }
+template<> int32_t NetworkToHost(int32_t in) { return ntohl(in); }
+
+FdForwardTransport::FdForwardTransport(jdwpTransportCallback* cb)
+    : mem_(*cb),
+      read_fd_(-1),
+      write_fd_(-1),
+      wakeup_fd_(eventfd(0, EFD_NONBLOCK)),
+      listen_fd_(-1),
+      close_notify_fd_(-1),
+      state_(TransportState::kClosed),
+      current_seq_num_(0) {}
+
+FdForwardTransport::~FdForwardTransport() { }
+
+bool FdForwardTransport::ChangeState(TransportState old_state, TransportState new_state) {
+  if (old_state == state_) {
+    state_ = new_state;
+    state_cv_.notify_all();
+    return true;
+  } else {
+    return false;
+  }
+}
+
+jdwpTransportError FdForwardTransport::PerformAttach(int listen_fd) {
+  jdwpTransportError err = SetupListen(listen_fd);
+  if (err != OK) {
+    return OK;
+  }
+  err = Accept();
+  StopListening();
+  return err;
+}
+
+static void SendListenMessage(const android::base::unique_fd& fd) {
+  TEMP_FAILURE_RETRY(send(fd, kListenStartMessage, sizeof(kListenStartMessage), MSG_EOR));
+}
+
+jdwpTransportError FdForwardTransport::SetupListen(int listen_fd) {
+  std::lock_guard<std::mutex> lk(state_mutex_);
+  if (!ChangeState(TransportState::kClosed, TransportState::kListenSetup)) {
+    return ERR(ILLEGAL_STATE);
+  } else {
+    listen_fd_.reset(dup(listen_fd));
+    SendListenMessage(listen_fd_);
+    CHECK(ChangeState(TransportState::kListenSetup, TransportState::kListening));
+    return OK;
+  }
+}
+
+static void SendListenEndMessage(const android::base::unique_fd& fd) {
+  TEMP_FAILURE_RETRY(send(fd, kListenEndMessage, sizeof(kListenEndMessage), MSG_EOR));
+}
+
+jdwpTransportError FdForwardTransport::StopListening() {
+  std::lock_guard<std::mutex> lk(state_mutex_);
+  if (listen_fd_ != -1) {
+    SendListenEndMessage(listen_fd_);
+  }
+  // Don't close the listen_fd_ since we might need it for later calls to listen.
+  if (ChangeState(TransportState::kListening, TransportState::kClosed) ||
+      state_ == TransportState::kOpen) {
+    listen_fd_.reset();
+  }
+  return OK;
+}
+
+// Last error message.
+thread_local std::string global_last_error_;
+
+void FdForwardTransport::SetLastError(const std::string& desc) {
+  LOG(ERROR) << desc;
+  global_last_error_ = desc;
+}
+
+IOResult FdForwardTransport::ReadFullyWithoutChecks(void* data, size_t ndata) {
+  uint8_t* bdata = reinterpret_cast<uint8_t*>(data);
+  size_t nbytes = 0;
+  while (nbytes < ndata) {
+    int res = TEMP_FAILURE_RETRY(read(read_fd_, bdata + nbytes, ndata - nbytes));
+    if (res < 0) {
+      DT_IO_ERROR("Failed read()");
+      return IOResult::kError;
+    } else if (res == 0) {
+      return IOResult::kEOF;
+    } else {
+      nbytes += res;
+    }
+  }
+  return IOResult::kOk;
+}
+
+IOResult FdForwardTransport::ReadUpToMax(void* data, size_t ndata, /*out*/size_t* read_amount) {
+  CHECK_GE(read_fd_.get(), 0);
+  int avail;
+  int res = ioctl(read_fd_, FIONREAD, &avail);
+  if (res < 0) {
+    DT_IO_ERROR("Failed ioctl(read_fd_, FIONREAD, &avail)");
+    return IOResult::kError;
+  }
+  size_t to_read = std::min(static_cast<size_t>(avail), ndata);
+  *read_amount = to_read;
+  if (*read_amount == 0) {
+    // Check if the read would cause an EOF.
+    struct pollfd pollfd = { read_fd_, POLLRDHUP, 0 };
+    res = poll(&pollfd, /*nfds*/1, /*timeout*/0);
+    if (res < 0 || (pollfd.revents & POLLERR) == POLLERR) {
+      DT_IO_ERROR("Failed poll on read fd.");
+      return IOResult::kError;
+    }
+    return ((pollfd.revents & (POLLRDHUP | POLLHUP)) == 0) ? IOResult::kOk : IOResult::kEOF;
+  }
+
+  return ReadFullyWithoutChecks(data, to_read);
+}
+
+IOResult FdForwardTransport::ReadFully(void* data, size_t ndata) {
+  uint64_t seq_num = current_seq_num_;
+  size_t nbytes = 0;
+  while (nbytes < ndata) {
+    size_t read_len;
+    struct pollfd pollfds[2];
+    {
+      std::lock_guard<std::mutex> lk(state_mutex_);
+      // Operations in this block must not cause an unbounded pause.
+      if (state_ != TransportState::kOpen || seq_num != current_seq_num_) {
+        // Async-close occurred!
+        return IOResult::kInterrupt;
+      } else {
+        CHECK_GE(read_fd_.get(), 0);
+      }
+      IOResult res = ReadUpToMax(reinterpret_cast<uint8_t*>(data) + nbytes,
+                                 ndata - nbytes,
+                                 /*out*/&read_len);
+      if (res != IOResult::kOk) {
+        return res;
+      } else {
+        nbytes += read_len;
+      }
+
+      pollfds[0] = { read_fd_, POLLRDHUP | POLLIN, 0 };
+      pollfds[1] = { wakeup_fd_, POLLIN, 0 };
+    }
+    if (read_len == 0) {
+      // No more data. Sleep without locks until more is available. We don't actually check for any
+      // errors since possible ones are (1) the read_fd_ is closed or wakeup happens which are both
+      // fine since the wakeup_fd_ or the poll failing will wake us up.
+      int poll_res = poll(pollfds, 2, -1);
+      if (poll_res < 0) {
+        DT_IO_ERROR("Failed to poll!");
+      }
+      // Clear the wakeup_fd regardless.
+      uint64_t val;
+      int unused = read(wakeup_fd_, &val, sizeof(val));
+      DCHECK(unused == sizeof(val) || errno == EAGAIN);
+      if (poll_res < 0) {
+        return IOResult::kError;
+      }
+    }
+  }
+  return IOResult::kOk;
+}
+
+// A helper that allows us to lock the eventfd 'fd'.
+class ScopedEventFdLock {
+ public:
+  explicit ScopedEventFdLock(const android::base::unique_fd& fd) : fd_(fd), data_(0) {
+    TEMP_FAILURE_RETRY(read(fd_, &data_, sizeof(data_)));
+  }
+
+  ~ScopedEventFdLock() {
+    TEMP_FAILURE_RETRY(write(fd_, &data_, sizeof(data_)));
+  }
+
+ private:
+  const android::base::unique_fd& fd_;
+  uint64_t data_;
+};
+
+IOResult FdForwardTransport::WriteFullyWithoutChecks(const void* data, size_t ndata) {
+  ScopedEventFdLock sefdl(write_lock_fd_);
+  const uint8_t* bdata = static_cast<const uint8_t*>(data);
+  size_t nbytes = 0;
+  while (nbytes < ndata) {
+    int res = TEMP_FAILURE_RETRY(write(write_fd_, bdata + nbytes, ndata - nbytes));
+    if (res < 0) {
+      DT_IO_ERROR("Failed write()");
+      return IOResult::kError;
+    } else if (res == 0) {
+      return IOResult::kEOF;
+    } else {
+      nbytes += res;
+    }
+  }
+  return IOResult::kOk;
+}
+
+IOResult FdForwardTransport::WriteFully(const void* data, size_t ndata) {
+  std::lock_guard<std::mutex> lk(state_mutex_);
+  if (state_ != TransportState::kOpen) {
+    return IOResult::kInterrupt;
+  }
+  return WriteFullyWithoutChecks(data, ndata);
+}
+
+static void SendAcceptMessage(int fd) {
+  TEMP_FAILURE_RETRY(send(fd, kAcceptMessage, sizeof(kAcceptMessage), MSG_EOR));
+}
+
+IOResult FdForwardTransport::ReceiveFdsFromSocket() {
+  union {
+    cmsghdr cm;
+    uint8_t buffer[CMSG_SPACE(sizeof(FdSet))];
+  } msg_union;
+  // We don't actually care about the data. Only FDs. We need to have an iovec anyway to tell if we
+  // got the values or not though.
+  char dummy = '\0';
+  iovec iov;
+  iov.iov_base = &dummy;
+  iov.iov_len  = sizeof(dummy);
+
+  msghdr msg;
+  memset(&msg, 0, sizeof(msg));
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  msg.msg_control = msg_union.buffer;
+  msg.msg_controllen = sizeof(msg_union.buffer);
+
+  cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+  cmsg->cmsg_len   = msg.msg_controllen;
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type  = SCM_RIGHTS;
+  memset(reinterpret_cast<int*>(CMSG_DATA(cmsg)), -1, FdSet::kDataLength);
+
+  int res = TEMP_FAILURE_RETRY(recvmsg(listen_fd_, &msg, 0));
+  if (res <= 0) {
+    DT_IO_ERROR("Failed to receive fds!");
+    return IOResult::kError;
+  }
+  FdSet out_fds = FdSet::ReadData(CMSG_DATA(cmsg));
+  if (out_fds.read_fd_ < 0 || out_fds.write_fd_ < 0 || out_fds.write_lock_fd_ < 0) {
+    DT_IO_ERROR("Received fds were invalid!");
+    if (out_fds.read_fd_ >= 0) {
+      close(out_fds.read_fd_);
+    }
+    if (out_fds.write_fd_ >= 0) {
+      close(out_fds.write_fd_);
+    }
+    if (out_fds.write_lock_fd_ >= 0) {
+      close(out_fds.write_lock_fd_);
+    }
+    return IOResult::kError;
+  }
+
+  read_fd_.reset(out_fds.read_fd_);
+  write_fd_.reset(out_fds.write_fd_);
+  write_lock_fd_.reset(out_fds.write_lock_fd_);
+
+  // We got the fds. Send ack.
+  close_notify_fd_.reset(dup(listen_fd_));
+  SendAcceptMessage(close_notify_fd_);
+
+  return IOResult::kOk;
+}
+
+// Accept the connection. Note that we match the behavior of other transports which is to just close
+// the connection and try again if we get a bad handshake.
+jdwpTransportError FdForwardTransport::Accept() {
+  // TODO Work with timeouts.
+  while (true) {
+    std::unique_lock<std::mutex> lk(state_mutex_);
+    while (!ChangeState(TransportState::kListening, TransportState::kOpening)) {
+      if (state_ == TransportState::kClosed ||
+          state_ == TransportState::kOpen) {
+        return ERR(ILLEGAL_STATE);
+      }
+      state_cv_.wait(lk);
+    }
+
+    DCHECK_NE(listen_fd_.get(), -1);
+    if (ReceiveFdsFromSocket() != IOResult::kOk) {
+      CHECK(ChangeState(TransportState::kOpening, TransportState::kListening));
+      return ERR(IO_ERROR);
+    }
+
+    current_seq_num_++;
+
+    // Moved to the opening state.
+    char handshake_recv[sizeof(kJdwpHandshake)];
+    memset(handshake_recv, 0, sizeof(handshake_recv));
+    IOResult res = ReadFullyWithoutChecks(handshake_recv, sizeof(handshake_recv));
+    if (res != IOResult::kOk ||
+        strncmp(handshake_recv, kJdwpHandshake, sizeof(kJdwpHandshake)) != 0) {
+      DT_IO_ERROR("Failed to read handshake");
+      CHECK(ChangeState(TransportState::kOpening, TransportState::kListening));
+      CloseFdsLocked();
+      // Retry.
+      continue;
+    }
+    res = WriteFullyWithoutChecks(kJdwpHandshake, sizeof(kJdwpHandshake));
+    if (res != IOResult::kOk) {
+      DT_IO_ERROR("Failed to write handshake");
+      CHECK(ChangeState(TransportState::kOpening, TransportState::kListening));
+      CloseFdsLocked();
+      // Retry.
+      continue;
+    }
+    break;
+  }
+  CHECK(ChangeState(TransportState::kOpening, TransportState::kOpen));
+  return OK;
+}
+
+void SendClosingMessage(int fd) {
+  if (fd >= 0) {
+    TEMP_FAILURE_RETRY(send(fd, kCloseMessage, sizeof(kCloseMessage), MSG_EOR));
+  }
+}
+
+// Actually close the fds associated with this transport.
+void FdForwardTransport::CloseFdsLocked() {
+  // We have a different set of fd's now. Increase the seq number.
+  current_seq_num_++;
+
+  // All access to these is locked under the state_mutex_ so we are safe to close these.
+  {
+    ScopedEventFdLock sefdl(write_lock_fd_);
+    if (close_notify_fd_ >= 0) {
+      SendClosingMessage(close_notify_fd_);
+    }
+    close_notify_fd_.reset();
+    read_fd_.reset();
+    write_fd_.reset();
+    close_notify_fd_.reset();
+  }
+  write_lock_fd_.reset();
+
+  // Send a wakeup in case we have any in-progress reads/writes.
+  uint64_t data = 1;
+  TEMP_FAILURE_RETRY(write(wakeup_fd_, &data, sizeof(data)));
+}
+
+jdwpTransportError FdForwardTransport::Close() {
+  std::lock_guard<std::mutex> lk(state_mutex_);
+  jdwpTransportError res =
+      ChangeState(TransportState::kOpen, TransportState::kClosed) ? OK : ERR(ILLEGAL_STATE);
+  // Send a wakeup after changing the state even if nothing actually happened.
+  uint64_t data = 1;
+  TEMP_FAILURE_RETRY(write(wakeup_fd_, &data, sizeof(data)));
+  if (res == OK) {
+    CloseFdsLocked();
+  }
+  return res;
+}
+
+// A helper class to read and parse the JDWP packet.
+class PacketReader {
+ public:
+  PacketReader(FdForwardTransport* transport, jdwpPacket* pkt)
+      : transport_(transport),
+        pkt_(pkt),
+        is_eof_(false),
+        is_err_(false) {}
+  bool ReadFully() {
+    // Zero out.
+    memset(pkt_, 0, sizeof(jdwpPacket));
+    int32_t len = ReadInt32();         // read len
+    if (is_err_) {
+      return false;
+    } else if (is_eof_) {
+      return true;
+    } else if (len < 11) {
+      transport_->DT_IO_ERROR("Packet with len < 11 received!");
+      return false;
+    }
+    pkt_->type.cmd.len = len;
+    pkt_->type.cmd.id = ReadInt32();
+    pkt_->type.cmd.flags = ReadByte();
+    if (is_err_) {
+      return false;
+    } else if (is_eof_) {
+      return true;
+    } else if ((pkt_->type.reply.flags & JDWPTRANSPORT_FLAGS_REPLY) == JDWPTRANSPORT_FLAGS_REPLY) {
+      ReadReplyPacket();
+    } else {
+      ReadCmdPacket();
+    }
+    return !is_err_;
+  }
+
+ private:
+  void ReadReplyPacket() {
+    pkt_->type.reply.errorCode = ReadInt16();
+    pkt_->type.reply.data = ReadRemaining();
+  }
+
+  void ReadCmdPacket() {
+    pkt_->type.cmd.cmdSet = ReadByte();
+    pkt_->type.cmd.cmd = ReadByte();
+    pkt_->type.cmd.data = ReadRemaining();
+  }
+
+  template <typename T>
+  T HandleResult(IOResult res, T val, T fail) {
+    switch (res) {
+      case IOResult::kError:
+        is_err_ = true;
+        return fail;
+      case IOResult::kOk:
+        return val;
+      case IOResult::kEOF:
+        is_eof_ = true;
+        pkt_->type.cmd.len = 0;
+        return fail;
+      case IOResult::kInterrupt:
+        transport_->DT_IO_ERROR("Failed to read, concurrent close!");
+        is_err_ = true;
+        return fail;
+    }
+  }
+
+  jbyte* ReadRemaining() {
+    if (is_eof_ || is_err_) {
+      return nullptr;
+    }
+    jbyte* out = nullptr;
+    jint rem = pkt_->type.cmd.len - 11;
+    CHECK_GE(rem, 0);
+    if (rem == 0) {
+      return nullptr;
+    } else {
+      out = reinterpret_cast<jbyte*>(transport_->Alloc(rem));
+      IOResult res = transport_->ReadFully(out, rem);
+      jbyte* ret = HandleResult(res, out, static_cast<jbyte*>(nullptr));
+      if (ret != out) {
+        transport_->Free(out);
+      }
+      return ret;
+    }
+  }
+
+  jbyte ReadByte() {
+    if (is_eof_ || is_err_) {
+      return -1;
+    }
+    jbyte out;
+    IOResult res = transport_->ReadFully(&out, sizeof(out));
+    return HandleResult(res, NetworkToHost(out), static_cast<jbyte>(-1));
+  }
+
+  jshort ReadInt16() {
+    if (is_eof_ || is_err_) {
+      return -1;
+    }
+    jshort out;
+    IOResult res = transport_->ReadFully(&out, sizeof(out));
+    return HandleResult(res, NetworkToHost(out), static_cast<jshort>(-1));
+  }
+
+  jint ReadInt32() {
+    if (is_eof_ || is_err_) {
+      return -1;
+    }
+    jint out;
+    IOResult res = transport_->ReadFully(&out, sizeof(out));
+    return HandleResult(res, NetworkToHost(out), -1);
+  }
+
+  FdForwardTransport* transport_;
+  jdwpPacket* pkt_;
+  bool is_eof_;
+  bool is_err_;
+};
+
+jdwpTransportError FdForwardTransport::ReadPacket(jdwpPacket* pkt) {
+  if (pkt == nullptr) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  PacketReader reader(this, pkt);
+  if (reader.ReadFully()) {
+    return OK;
+  } else {
+    return ERR(IO_ERROR);
+  }
+}
+
+// A class that writes a packet to the transport.
+class PacketWriter {
+ public:
+  PacketWriter(FdForwardTransport* transport, const jdwpPacket* pkt)
+      : transport_(transport), pkt_(pkt), data_() {}
+
+  bool WriteFully() {
+    PushInt32(pkt_->type.cmd.len);
+    PushInt32(pkt_->type.cmd.id);
+    PushByte(pkt_->type.cmd.flags);
+    if ((pkt_->type.reply.flags & JDWPTRANSPORT_FLAGS_REPLY) == JDWPTRANSPORT_FLAGS_REPLY) {
+      PushInt16(pkt_->type.reply.errorCode);
+      PushData(pkt_->type.reply.data, pkt_->type.reply.len - 11);
+    } else {
+      PushByte(pkt_->type.cmd.cmdSet);
+      PushByte(pkt_->type.cmd.cmd);
+      PushData(pkt_->type.cmd.data, pkt_->type.cmd.len - 11);
+    }
+    IOResult res = transport_->WriteFully(data_.data(), data_.size());
+    return res == IOResult::kOk;
+  }
+
+ private:
+  void PushInt32(int32_t data) {
+    data = HostToNetwork(data);
+    PushData(&data, sizeof(data));
+  }
+  void PushInt16(int16_t data) {
+    data = HostToNetwork(data);
+    PushData(&data, sizeof(data));
+  }
+  void PushByte(jbyte data) {
+    data_.push_back(HostToNetwork(data));
+  }
+
+  void PushData(void* d, size_t size) {
+    uint8_t* bytes = reinterpret_cast<uint8_t*>(d);
+    data_.insert(data_.end(), bytes, bytes + size);
+  }
+
+  FdForwardTransport* transport_;
+  const jdwpPacket* pkt_;
+  std::vector<uint8_t> data_;
+};
+
+jdwpTransportError FdForwardTransport::WritePacket(const jdwpPacket* pkt) {
+  if (pkt == nullptr) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  PacketWriter writer(this, pkt);
+  if (writer.WriteFully()) {
+    return OK;
+  } else {
+    return ERR(IO_ERROR);
+  }
+}
+
+jboolean FdForwardTransport::IsOpen() {
+  return state_ == TransportState::kOpen;
+}
+
+void* FdForwardTransport::Alloc(size_t s) {
+  return mem_.alloc(s);
+}
+
+void FdForwardTransport::Free(void* data) {
+  mem_.free(data);
+}
+
+jdwpTransportError FdForwardTransport::GetLastError(/*out*/char** err) {
+  std::string data = global_last_error_;
+  *err = reinterpret_cast<char*>(Alloc(data.size() + 1));
+  strcpy(*err, data.c_str());
+  return OK;
+}
+
+static FdForwardTransport* AsFdForward(jdwpTransportEnv* env) {
+  return reinterpret_cast<FdForwardTransport*>(env);
+}
+
+static jdwpTransportError ParseAddress(const std::string& addr,
+                                       /*out*/int* listen_sock) {
+  if (!android::base::ParseInt(addr.c_str(), listen_sock) || *listen_sock < 0) {
+    LOG(ERROR) << "address format is <fd_num> not " << addr;
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  return OK;
+}
+
+class JdwpTransportFunctions {
+ public:
+  static jdwpTransportError GetCapabilities(jdwpTransportEnv* env ATTRIBUTE_UNUSED,
+                                            /*out*/ JDWPTransportCapabilities* capabilities_ptr) {
+    // We don't support any of the optional capabilities (can_timeout_attach, can_timeout_accept,
+    // can_timeout_handshake) so just return a zeroed capabilities ptr.
+    // TODO We should maybe support these timeout options.
+    memset(capabilities_ptr, 0, sizeof(JDWPTransportCapabilities));
+    return OK;
+  }
+
+  // Address is <sock_fd>
+  static jdwpTransportError Attach(jdwpTransportEnv* env,
+                                   const char* address,
+                                   jlong attach_timeout ATTRIBUTE_UNUSED,
+                                   jlong handshake_timeout ATTRIBUTE_UNUSED) {
+    if (address == nullptr || *address == '\0') {
+      return ERR(ILLEGAL_ARGUMENT);
+    }
+    int listen_fd;
+    jdwpTransportError err = ParseAddress(address, &listen_fd);
+    if (err != OK) {
+      return err;
+    }
+    return AsFdForward(env)->PerformAttach(listen_fd);
+  }
+
+  static jdwpTransportError StartListening(jdwpTransportEnv* env,
+                                           const char* address,
+                                           /*out*/ char** actual_address) {
+    if (address == nullptr || *address == '\0') {
+      return ERR(ILLEGAL_ARGUMENT);
+    }
+    int listen_fd;
+    jdwpTransportError err = ParseAddress(address, &listen_fd);
+    if (err != OK) {
+      return err;
+    }
+    err = AsFdForward(env)->SetupListen(listen_fd);
+    if (err != OK) {
+      return err;
+    }
+    if (actual_address != nullptr) {
+      *actual_address = reinterpret_cast<char*>(AsFdForward(env)->Alloc(strlen(address) + 1));
+      memcpy(*actual_address, address, strlen(address) + 1);
+    }
+    return OK;
+  }
+
+  static jdwpTransportError StopListening(jdwpTransportEnv* env) {
+    return AsFdForward(env)->StopListening();
+  }
+
+  static jdwpTransportError Accept(jdwpTransportEnv* env,
+                                   jlong accept_timeout ATTRIBUTE_UNUSED,
+                                   jlong handshake_timeout ATTRIBUTE_UNUSED) {
+    return AsFdForward(env)->Accept();
+  }
+
+  static jboolean IsOpen(jdwpTransportEnv* env) {
+    return AsFdForward(env)->IsOpen();
+  }
+
+  static jdwpTransportError Close(jdwpTransportEnv* env) {
+    return AsFdForward(env)->Close();
+  }
+
+  static jdwpTransportError ReadPacket(jdwpTransportEnv* env, jdwpPacket *pkt) {
+    return AsFdForward(env)->ReadPacket(pkt);
+  }
+
+  static jdwpTransportError WritePacket(jdwpTransportEnv* env, const jdwpPacket* pkt) {
+    return AsFdForward(env)->WritePacket(pkt);
+  }
+
+  static jdwpTransportError GetLastError(jdwpTransportEnv* env, char** error) {
+    return AsFdForward(env)->GetLastError(error);
+  }
+};
+
+// The actual struct holding all the entrypoints into the jdwpTransport interface.
+const jdwpTransportNativeInterface_ gTransportInterface = {
+  nullptr,  // reserved1
+  JdwpTransportFunctions::GetCapabilities,
+  JdwpTransportFunctions::Attach,
+  JdwpTransportFunctions::StartListening,
+  JdwpTransportFunctions::StopListening,
+  JdwpTransportFunctions::Accept,
+  JdwpTransportFunctions::IsOpen,
+  JdwpTransportFunctions::Close,
+  JdwpTransportFunctions::ReadPacket,
+  JdwpTransportFunctions::WritePacket,
+  JdwpTransportFunctions::GetLastError,
+};
+
+extern "C"
+JNIEXPORT jint JNICALL jdwpTransport_OnLoad(JavaVM* vm ATTRIBUTE_UNUSED,
+                                            jdwpTransportCallback* cb,
+                                            jint version,
+                                            jdwpTransportEnv** /*out*/env) {
+  if (version != JDWPTRANSPORT_VERSION_1_0) {
+    LOG(ERROR) << "unknown version " << version;
+    return JNI_EVERSION;
+  }
+  void* data = cb->alloc(sizeof(FdForwardTransport));
+  if (data == nullptr) {
+    LOG(ERROR) << "Failed to allocate data for transport!";
+    return JNI_ENOMEM;
+  }
+  FdForwardTransport* transport =
+      new (data) FdForwardTransport(cb);
+  transport->functions = &gTransportInterface;
+  *env = transport;
+  return JNI_OK;
+}
+
+}  // namespace dt_fd_forward
diff --git a/dt_fd_forward/dt_fd_forward.h b/dt_fd_forward/dt_fd_forward.h
new file mode 100644
index 0000000..9303c59
--- /dev/null
+++ b/dt_fd_forward/dt_fd_forward.h
@@ -0,0 +1,154 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jdwpTransport.h. This implementation
+ * is licensed under the same terms as the file jdwpTransport.h.  The
+ * copyright and license information for the file jdwpTranport.h follows.
+ *
+ * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_DT_FD_FORWARD_DT_FD_FORWARD_H_
+#define ART_DT_FD_FORWARD_DT_FD_FORWARD_H_
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+#include <android-base/unique_fd.h>
+
+#include <arpa/inet.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <jni.h>
+#include <jvmti.h>
+#include <jdwpTransport.h>
+
+#include "fd_transport.h"
+
+namespace dt_fd_forward {
+
+static constexpr uint8_t kReplyFlag = 0x80;
+// Macro and constexpr to make error values less annoying to write.
+#define ERR(e) JDWPTRANSPORT_ERROR_ ## e
+static constexpr jdwpTransportError OK = ERR(NONE);
+
+static constexpr const char kJdwpHandshake[14] = {
+    'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'
+};  // "JDWP-Handshake"
+
+enum class TransportState {
+  kClosed,       // Main state.
+  kListenSetup,  // Transient, wait for the state to change before proceeding.
+  kListening,    // Main state.
+  kOpening,      // Transient, wait for the state to change before proceeding.
+  kOpen,         // Main state.
+};
+
+enum class IOResult {
+  kOk, kInterrupt, kError, kEOF,
+};
+
+class PacketReader;
+class PacketWriter;
+
+// TODO It would be good to get the thread-safety analysis checks working but first we would need to
+// use something other than std::mutex which does not have the annotations required.
+class FdForwardTransport : public jdwpTransportEnv {
+ public:
+  explicit FdForwardTransport(jdwpTransportCallback* cb);
+  ~FdForwardTransport();
+
+  jdwpTransportError PerformAttach(int listen_fd);
+  jdwpTransportError SetupListen(int listen_fd);
+  jdwpTransportError StopListening();
+
+  jboolean IsOpen();
+
+  jdwpTransportError WritePacket(const jdwpPacket* pkt);
+  jdwpTransportError ReadPacket(jdwpPacket* pkt);
+  jdwpTransportError Close();
+  jdwpTransportError Accept();
+  jdwpTransportError GetLastError(/*out*/char** description);
+
+  void* Alloc(size_t data);
+  void Free(void* data);
+
+ private:
+  void SetLastError(const std::string& desc);
+
+  bool ChangeState(TransportState old_state, TransportState new_state);  // REQUIRES(state_mutex_);
+
+  IOResult ReceiveFdsFromSocket();
+
+  IOResult WriteFully(const void* data, size_t ndata);  // REQUIRES(!state_mutex_);
+  IOResult WriteFullyWithoutChecks(const void* data, size_t ndata);  // REQUIRES(state_mutex_);
+  IOResult ReadFully(void* data, size_t ndata);  // REQUIRES(!state_mutex_);
+  IOResult ReadUpToMax(void* data, size_t ndata, /*out*/size_t* amount_read);
+      // REQUIRES(state_mutex_);
+  IOResult ReadFullyWithoutChecks(void* data, size_t ndata);  // REQUIRES(state_mutex_);
+
+  void CloseFdsLocked();  // REQUIRES(state_mutex_)
+
+  // The allocation/deallocation functions.
+  jdwpTransportCallback mem_;
+
+  // Input from the server;
+  android::base::unique_fd read_fd_;  // GUARDED_BY(state_mutex_);
+  // Output to the server;
+  android::base::unique_fd write_fd_;  // GUARDED_BY(state_mutex_);
+
+  // an eventfd passed with the write_fd to the transport that we will 'read' from to get a lock on
+  // the write_fd_. The other side must not hold it for unbounded time.
+  android::base::unique_fd write_lock_fd_;  // GUARDED_BY(state_mutex_);
+
+  // Eventfd we will use to wake-up paused reads for close().
+  android::base::unique_fd wakeup_fd_;
+
+  // Socket we will get the read/write fd's from.
+  android::base::unique_fd listen_fd_;
+
+  // Fd we will write close notification to. This is a dup of listen_fd_.
+  android::base::unique_fd close_notify_fd_;
+
+  TransportState state_;  // GUARDED_BY(state_mutex_);
+
+  std::mutex state_mutex_;
+  std::condition_variable state_cv_;
+
+  // A counter that we use to make sure we don't do half a read on one and half on another fd.
+  std::atomic<uint64_t> current_seq_num_;
+
+  friend class PacketReader;  // For ReadFullyWithInterrupt
+  friend class PacketWriter;  // For WriteFullyWithInterrupt
+};
+
+}  // namespace dt_fd_forward
+
+#endif  // ART_DT_FD_FORWARD_DT_FD_FORWARD_H_
diff --git a/dt_fd_forward/export/Android.bp b/dt_fd_forward/export/Android.bp
new file mode 100644
index 0000000..c3a6321
--- /dev/null
+++ b/dt_fd_forward/export/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2017 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.
+//
+
+cc_library_headers {
+    name: "dt_fd_forward_export",
+    export_include_dirs: [ "." ],
+    host_supported: true,
+    device_supported: true,
+}
diff --git a/dt_fd_forward/export/MODULE_LICENSE_APACHE2 b/dt_fd_forward/export/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dt_fd_forward/export/MODULE_LICENSE_APACHE2
diff --git a/dt_fd_forward/export/fd_transport.h b/dt_fd_forward/export/fd_transport.h
new file mode 100644
index 0000000..245f0c2
--- /dev/null
+++ b/dt_fd_forward/export/fd_transport.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2017 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_DT_FD_FORWARD_EXPORT_FD_TRANSPORT_H_
+#define ART_DT_FD_FORWARD_EXPORT_FD_TRANSPORT_H_
+
+#include <stdint.h>
+
+namespace dt_fd_forward {
+
+// The file-descriptors sent over a socket to the dt_fd_forward transport.
+struct FdSet {
+  // A fd that can be read from which provides the JDWP data.
+  int read_fd_;
+
+  // A fd that can be written to in order to provide JDWP responses and events.
+  int write_fd_;
+
+  // A eventfd that can be locked to ensure that writes to write_fd_ are atomic. This must be held
+  // when writing to write_fd_. This allows the proxy to insert packets into the response stream
+  // without having to parse it.
+  int write_lock_fd_;
+
+  static constexpr size_t kDataLength = sizeof(int) * 3;
+  void WriteData(void* buf) {
+    int* ibuf = reinterpret_cast<int*>(buf);
+    ibuf[0] = read_fd_;
+    ibuf[1] = write_fd_;
+    ibuf[2] = write_lock_fd_;
+  }
+
+  static FdSet ReadData(void* buf) {
+    int* ibuf = reinterpret_cast<int*>(buf);
+    return FdSet { ibuf[0], ibuf[1], ibuf[2] };
+  }
+};
+
+// This message is sent over the fd associated with the transport when we are listening for fds.
+static constexpr char kListenStartMessage[] = "dt_fd_forward:START-LISTEN";
+
+// This message is sent over the fd associated with the transport when we stop listening for fds.
+static constexpr char kListenEndMessage[] = "dt_fd_forward:END-LISTEN";
+
+// This message is sent over the fd associated with the transport when we have accepted a
+// connection. This is sent before any handshaking has occurred. It is simply an acknowledgment
+// that the FdSet has been received. This will be paired with a single CLOSING message when these
+// fds are closed.
+static constexpr char kAcceptMessage[] = "dt_fd_forward:ACCEPTED";
+
+// This message is sent over the fd associated with the transport when we are closing the fds. This
+// can be used by the proxy to send additional data on a dup'd fd. The write_lock_fd_ will be held
+// until the other two fds are closed and then it will be released and closed.
+static constexpr char kCloseMessage[] = "dt_fd_forward:CLOSING";
+
+}  // namespace dt_fd_forward
+
+#endif  // ART_DT_FD_FORWARD_EXPORT_FD_TRANSPORT_H_
diff --git a/patchoat/patchoat_test.cc b/patchoat/patchoat_test.cc
index dc4c78b..86e851c 100644
--- a/patchoat/patchoat_test.cc
+++ b/patchoat/patchoat_test.cc
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <dirent.h>
+#include <sys/types.h>
+
 #include <string>
 #include <vector>
 
@@ -31,6 +34,44 @@
 
 class PatchoatTest : public DexoptTest {
  public:
+  static bool ListDirFilesEndingWith(
+      const std::string& dir,
+      const std::string& suffix,
+      std::vector<std::string>* filenames,
+      std::string* error_msg) {
+    DIR* d = opendir(dir.c_str());
+    if (d == nullptr) {
+      *error_msg = "Failed to open directory";
+      return false;
+    }
+    dirent* e;
+    struct stat s;
+    size_t suffix_len = suffix.size();
+    while ((e = readdir(d)) != nullptr) {
+      if ((strcmp(e->d_name, ".") == 0) || (strcmp(e->d_name, "..") == 0)) {
+        continue;
+      }
+      size_t name_len = strlen(e->d_name);
+      if ((name_len < suffix_len) || (strcmp(&e->d_name[name_len - suffix_len], suffix.c_str()))) {
+        continue;
+      }
+      std::string basename(e->d_name);
+      std::string filename = dir + "/" + basename;
+      int stat_result = lstat(filename.c_str(), &s);
+      if (stat_result != 0) {
+        *error_msg =
+            StringPrintf("Failed to stat %s: stat returned %d", filename.c_str(), stat_result);
+        return false;
+      }
+      if (S_ISDIR(s.st_mode)) {
+        continue;
+      }
+      filenames->push_back(basename);
+    }
+    closedir(d);
+    return true;
+  }
+
   static void AddRuntimeArg(std::vector<std::string>& args, const std::string& arg) {
     args.push_back("--runtime-arg");
     args.push_back(arg);
@@ -310,30 +351,48 @@
     FAIL() << "RelocateBootImage failed: " << error_msg;
   }
 
-  // dex2oat_reloc_image_filename is the boot image relocated using dex2oat
-  // patchoat_reloc_image_filename is the boot image relocated using patchoat
-  std::string dex2oat_reloc_image_filename = dex2oat_reloc_dir + "/boot.art";
-  std::string patchoat_reloc_image_filename = dex2oat_orig_dir + "/boot.art";
-  std::replace(
-      patchoat_reloc_image_filename.begin() + 1, patchoat_reloc_image_filename.end(), '/', '@');
-  patchoat_reloc_image_filename =
-      patchoat_dir
-      + (android::base::StartsWith(patchoat_reloc_image_filename, "/") ? "" : "/")
-      + patchoat_reloc_image_filename;
+  // Assert that patchoat created the same set of .art files as dex2oat
+  std::vector<std::string> dex2oat_image_basenames;
+  std::vector<std::string> patchoat_image_basenames;
+  if (!ListDirFilesEndingWith(dex2oat_reloc_dir, ".art", &dex2oat_image_basenames, &error_msg)) {
+    FAIL() << "Failed to list *.art files in " << dex2oat_reloc_dir << ": " << error_msg;
+  }
+  if (!ListDirFilesEndingWith(patchoat_dir, ".art", &patchoat_image_basenames, &error_msg)) {
+    FAIL() << "Failed to list *.art files in " << patchoat_dir << ": " << error_msg;
+  }
+  std::sort(dex2oat_image_basenames.begin(), dex2oat_image_basenames.end());
+  std::sort(patchoat_image_basenames.begin(), patchoat_image_basenames.end());
+  // .art file names output by patchoat look like tmp@art-data-<random>-<random>@boot*.art. To
+  // compare these with .art file names output by dex2oat we retain only the part of the file name
+  // after the last @.
+  std::vector<std::string> patchoat_image_shortened_basenames(patchoat_image_basenames.size());
+  for (size_t i = 0; i < patchoat_image_basenames.size(); i++) {
+    patchoat_image_shortened_basenames[i] =
+        patchoat_image_basenames[i].substr(patchoat_image_basenames[i].find_last_of("@") + 1);
+  }
+  ASSERT_EQ(dex2oat_image_basenames, patchoat_image_shortened_basenames);
 
-  // Patch up the dex2oat-relocated image so that it looks as though it was relocated by patchoat.
-  // patchoat preserves the OAT checksum header field and sets patch delta header field.
-  if (!CopyImageChecksumAndSetPatchDelta(
-      dex2oat_orig_dir + "/boot.art",
-      dex2oat_reloc_dir + "/boot.art",
-      base_addr_delta,
-      &error_msg)) {
-    FAIL() << "Unable to copy image checksum: " << error_msg;
+  // Patch up the dex2oat-relocated image files so that it looks as though they were relocated by
+  // patchoat. patchoat preserves the OAT checksum header field and sets patch delta header field.
+  for (const std::string& image_basename : dex2oat_image_basenames) {
+    if (!CopyImageChecksumAndSetPatchDelta(
+        dex2oat_orig_dir + "/" + image_basename,
+        dex2oat_reloc_dir + "/" + image_basename,
+        base_addr_delta,
+        &error_msg)) {
+      FAIL() << "Unable to patch up " << image_basename << ": " << error_msg;
+    }
   }
 
-  // Assert that the patchoat-relocated image is identical to the dex2oat-relocated image
-  if (BinaryDiff(dex2oat_reloc_image_filename, patchoat_reloc_image_filename, &error_msg)) {
-    FAIL() << "patchoat- and dex2oat-relocated images differ: " << error_msg;
+  // Assert that the patchoat-relocated images are identical to the dex2oat-relocated images
+  for (size_t i = 0; i < dex2oat_image_basenames.size(); i++) {
+    const std::string& dex2oat_image_basename = dex2oat_image_basenames[i];
+    const std::string& dex2oat_image_filename = dex2oat_reloc_dir + "/" + dex2oat_image_basename;
+    const std::string& patchoat_image_filename = patchoat_dir + "/" + patchoat_image_basenames[i];
+    if (BinaryDiff(dex2oat_image_filename, patchoat_image_filename, &error_msg)) {
+      FAIL() << "patchoat- and dex2oat-relocated variants of " << dex2oat_image_basename
+          << " differ: " << error_msg;
+    }
   }
 
   ClearDirectory(dex2oat_orig_dir.c_str(), /*recursive*/ true);
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 6477347..1e5fe16 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -470,6 +470,7 @@
         "instrumentation.h",
         "indirect_reference_table.h",
         "invoke_type.h",
+        "jdwp_provider.h",
         "jdwp/jdwp.h",
         "jdwp/jdwp_constants.h",
         "lock_word.h",
@@ -602,6 +603,7 @@
         "intern_table_test.cc",
         "interpreter/safe_math_test.cc",
         "interpreter/unstarted_runtime_test.cc",
+        "jdwp/jdwp_options_test.cc",
         "java_vm_ext_test.cc",
         "jit/profile_compilation_info_test.cc",
         "leb128_test.cc",
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index b5ae09f..c85c233 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -328,6 +328,7 @@
 ObjectRegistry* Dbg::gRegistry = nullptr;
 DebuggerActiveMethodInspectionCallback Dbg::gDebugActiveCallback;
 DebuggerDdmCallback Dbg::gDebugDdmCallback;
+InternalDebuggerControlCallback Dbg::gDebuggerControlCallback;
 
 // Deoptimization support.
 std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_;
@@ -364,6 +365,20 @@
   return !Dbg::MethodHasAnyBreakpoints(m);
 }
 
+void InternalDebuggerControlCallback::StartDebugger() {
+  // Release the mutator lock.
+  ScopedThreadStateChange stsc(art::Thread::Current(), kNative);
+  Dbg::StartJdwp();
+}
+
+void InternalDebuggerControlCallback::StopDebugger() {
+  Dbg::StopJdwp();
+}
+
+bool InternalDebuggerControlCallback::IsDebuggerConfigured() {
+  return Dbg::IsJdwpConfigured();
+}
+
 // Breakpoints.
 static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_);
 
@@ -736,6 +751,7 @@
   CHECK_NE(jdwp_options.transport, JDWP::kJdwpTransportUnknown);
   gJdwpOptions = jdwp_options;
   gJdwpConfigured = true;
+  Runtime::Current()->GetRuntimeCallbacks()->AddDebuggerControlCallback(&gDebuggerControlCallback);
 }
 
 bool Dbg::IsJdwpConfigured() {
diff --git a/runtime/debugger.h b/runtime/debugger.h
index d5bad8d..7401813 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -53,18 +53,22 @@
 class StackVisitor;
 class Thread;
 
+struct DebuggerActiveMethodInspectionCallback : public MethodInspectionCallback {
+  bool IsMethodBeingInspected(ArtMethod* method) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsMethodSafeToJit(ArtMethod* method) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+};
+
 struct DebuggerDdmCallback : public DdmCallback {
   void DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data)
       OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
 };
 
-struct DebuggerActiveMethodInspectionCallback : public MethodInspectionCallback {
-  bool IsMethodBeingInspected(ArtMethod* m ATTRIBUTE_UNUSED)
-      OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
-  bool IsMethodSafeToJit(ArtMethod* m) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+struct InternalDebuggerControlCallback : public DebuggerControlCallback {
+  void StartDebugger() OVERRIDE;
+  void StopDebugger() OVERRIDE;
+  bool IsDebuggerConfigured() OVERRIDE;
 };
 
-
 /*
  * Invoke-during-breakpoint support.
  */
@@ -251,7 +255,8 @@
   }
 
   // Configures JDWP with parsed command-line options.
-  static void ConfigureJdwp(const JDWP::JdwpOptions& jdwp_options);
+  static void ConfigureJdwp(const JDWP::JdwpOptions& jdwp_options)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Returns true if we had -Xrunjdwp or -agentlib:jdwp= on the command line.
   static bool IsJdwpConfigured();
@@ -789,6 +794,7 @@
 
   static DebuggerActiveMethodInspectionCallback gDebugActiveCallback;
   static DebuggerDdmCallback gDebugDdmCallback;
+  static InternalDebuggerControlCallback gDebuggerControlCallback;
 
   // Indicates whether we should drop the JDWP connection because the runtime stops or the
   // debugger called VirtualMachine.Dispose.
diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
index d712b10..b491c3e 100644
--- a/runtime/jdwp/jdwp.h
+++ b/runtime/jdwp/jdwp.h
@@ -98,14 +98,15 @@
  * How we talk to the debugger.
  */
 enum JdwpTransportType {
-  kJdwpTransportUnknown = 0,
+  kJdwpTransportNone = 0,
+  kJdwpTransportUnknown,      // Unknown tranpsort
   kJdwpTransportSocket,       // transport=dt_socket
   kJdwpTransportAndroidAdb,   // transport=dt_android_adb
 };
 std::ostream& operator<<(std::ostream& os, const JdwpTransportType& rhs);
 
 struct JdwpOptions {
-  JdwpTransportType transport = kJdwpTransportUnknown;
+  JdwpTransportType transport = kJdwpTransportNone;
   bool server = false;
   bool suspend = false;
   std::string host = "";
@@ -114,6 +115,8 @@
 
 bool operator==(const JdwpOptions& lhs, const JdwpOptions& rhs);
 
+bool ParseJdwpOptions(const std::string& options, JdwpOptions* jdwp_options);
+
 struct JdwpEvent;
 class JdwpNetStateBase;
 struct ModBasket;
diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc
index e275554..63f5dc8 100644
--- a/runtime/jdwp/jdwp_main.cc
+++ b/runtime/jdwp/jdwp_main.cc
@@ -37,6 +37,119 @@
 
 static void* StartJdwpThread(void* arg);
 
+
+static bool ParseJdwpOption(const std::string& name,
+                            const std::string& value,
+                            JdwpOptions* jdwp_options) {
+  if (name == "transport") {
+    if (value == "dt_socket") {
+      jdwp_options->transport = JDWP::kJdwpTransportSocket;
+    } else if (value == "dt_android_adb") {
+      jdwp_options->transport = JDWP::kJdwpTransportAndroidAdb;
+    } else {
+      jdwp_options->transport = JDWP::kJdwpTransportUnknown;
+      LOG(ERROR) << "JDWP transport not supported: " << value;
+      return false;
+    }
+  } else if (name == "server") {
+    if (value == "n") {
+      jdwp_options->server = false;
+    } else if (value == "y") {
+      jdwp_options->server = true;
+    } else {
+      LOG(ERROR) << "JDWP option 'server' must be 'y' or 'n'";
+      return false;
+    }
+  } else if (name == "suspend") {
+    if (value == "n") {
+      jdwp_options->suspend = false;
+    } else if (value == "y") {
+      jdwp_options->suspend = true;
+    } else {
+      LOG(ERROR) << "JDWP option 'suspend' must be 'y' or 'n'";
+      return false;
+    }
+  } else if (name == "address") {
+    /* this is either <port> or <host>:<port> */
+    std::string port_string;
+    jdwp_options->host.clear();
+    std::string::size_type colon = value.find(':');
+    if (colon != std::string::npos) {
+      jdwp_options->host = value.substr(0, colon);
+      port_string = value.substr(colon + 1);
+    } else {
+      port_string = value;
+    }
+    if (port_string.empty()) {
+      LOG(ERROR) << "JDWP address missing port: " << value;
+      return false;
+    }
+    char* end;
+    uint64_t port = strtoul(port_string.c_str(), &end, 10);
+    if (*end != '\0' || port > 0xffff) {
+      LOG(ERROR) << "JDWP address has junk in port field: " << value;
+      return false;
+    }
+    jdwp_options->port = port;
+  } else if (name == "launch" || name == "onthrow" || name == "oncaught" || name == "timeout") {
+    /* valid but unsupported */
+    LOG(INFO) << "Ignoring JDWP option '" << name << "'='" << value << "'";
+  } else {
+    LOG(INFO) << "Ignoring unrecognized JDWP option '" << name << "'='" << value << "'";
+  }
+
+  return true;
+}
+
+bool ParseJdwpOptions(const std::string& options, JdwpOptions* jdwp_options) {
+  VLOG(jdwp) << "ParseJdwpOptions: " << options;
+
+  if (options == "help") {
+    LOG(ERROR) << "Example: -XjdwpOptions:transport=dt_socket,address=8000,server=y\n"
+               << "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
+               << "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n";
+    return false;
+  }
+
+  const std::string s;
+
+  std::vector<std::string> pairs;
+  Split(options, ',', &pairs);
+
+  for (const std::string& jdwp_option : pairs) {
+    std::string::size_type equals_pos = jdwp_option.find('=');
+    if (equals_pos == std::string::npos) {
+      LOG(ERROR) << s << "Can't parse JDWP option '" << jdwp_option << "' in '" << options << "'";
+      return false;
+    }
+
+    bool parse_attempt = ParseJdwpOption(jdwp_option.substr(0, equals_pos),
+                                         jdwp_option.substr(equals_pos + 1),
+                                         jdwp_options);
+    if (!parse_attempt) {
+      // We fail to parse this JDWP option.
+      return parse_attempt;
+    }
+  }
+
+  if (jdwp_options->transport == JDWP::kJdwpTransportUnknown) {
+    LOG(ERROR) << s << "Must specify JDWP transport: " << options;
+    return false;
+  }
+#if ART_TARGET_ANDROID
+  if (jdwp_options->transport == JDWP::kJdwpTransportNone) {
+    jdwp_options->transport = JDWP::kJdwpTransportAndroidAdb;
+    LOG(WARNING) << "no JDWP transport specified. Defaulting to dt_android_adb";
+  }
+#endif
+  if (!jdwp_options->server && (jdwp_options->host.empty() || jdwp_options->port == 0)) {
+    LOG(ERROR) << s << "Must specify JDWP host and port when server=n: " << options;
+    return false;
+  }
+
+  return true;
+}
+
 /*
  * JdwpNetStateBase class implementation
  */
diff --git a/runtime/jdwp/jdwp_options_test.cc b/runtime/jdwp/jdwp_options_test.cc
new file mode 100644
index 0000000..10c52e8
--- /dev/null
+++ b/runtime/jdwp/jdwp_options_test.cc
@@ -0,0 +1,79 @@
+/*
+ * 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.h"
+
+#include "gtest/gtest.h"
+
+namespace art {
+namespace JDWP {
+
+TEST(JdwpOptionsTest, Options) {
+  {
+    /*
+     * "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n"
+     */
+    JDWP::JdwpOptions opt = JDWP::JdwpOptions();
+    const char *opt_args = "transport=dt_socket,address=8000,server=y";
+
+    EXPECT_TRUE(ParseJdwpOptions(opt_args, &opt));
+    EXPECT_EQ(opt.transport, JdwpTransportType::kJdwpTransportSocket);
+    EXPECT_EQ(opt.port, 8000u);
+    EXPECT_EQ(opt.server, true);
+    EXPECT_EQ(opt.suspend, false);
+  }
+
+  {
+    /*
+     * Example: transport=dt_socket,address=localhost:6500,server=n
+     */
+    JDWP::JdwpOptions opt = JDWP::JdwpOptions();
+    const char *opt_args = "transport=dt_socket,address=localhost:6500,server=y";
+
+    EXPECT_TRUE(ParseJdwpOptions(opt_args, &opt));
+    EXPECT_EQ(opt.transport, JdwpTransportType::kJdwpTransportSocket);
+    EXPECT_EQ(opt.port, 6500u);
+    EXPECT_EQ(opt.host, "localhost");
+    EXPECT_EQ(opt.server, true);
+    EXPECT_EQ(opt.suspend, false);
+  }
+
+  {
+    /*
+     * Example: transport=dt_android_adb,server=n,suspend=y;
+     */
+    JDWP::JdwpOptions opt = JDWP::JdwpOptions();
+    const char *opt_args = "transport=dt_android_adb,server=y";
+
+    EXPECT_TRUE(ParseJdwpOptions(opt_args, &opt));
+    EXPECT_EQ(opt.transport, JdwpTransportType::kJdwpTransportAndroidAdb);
+    EXPECT_EQ(opt.port, 0xFFFF);
+    EXPECT_EQ(opt.host, "");
+    EXPECT_EQ(opt.server, true);
+    EXPECT_EQ(opt.suspend, false);
+  }
+
+  /*
+   * Test failures
+  */
+  JDWP::JdwpOptions opt = JDWP::JdwpOptions();
+  EXPECT_FALSE(ParseJdwpOptions("help", &opt));
+  EXPECT_FALSE(ParseJdwpOptions("blabla", &opt));
+  EXPECT_FALSE(ParseJdwpOptions("transport=dt_android_adb,server=n", &opt));
+}
+
+}  // namespace JDWP
+}  // namespace art
diff --git a/runtime/jdwp_provider.h b/runtime/jdwp_provider.h
new file mode 100644
index 0000000..b62e10b
--- /dev/null
+++ b/runtime/jdwp_provider.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 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_RUNTIME_JDWP_PROVIDER_H_
+#define ART_RUNTIME_JDWP_PROVIDER_H_
+
+#include <ios>
+
+#include "base/macros.h"
+#include "base/logging.h"
+
+namespace art {
+
+enum class JdwpProvider {
+  kNone,
+  kInternal,
+  kAdbConnection,
+};
+
+std::ostream& operator<<(std::ostream& os, const JdwpProvider& rhs);
+
+}  // namespace art
+#endif  // ART_RUNTIME_JDWP_PROVIDER_H_
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 88a78ab..787646d 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -157,8 +157,9 @@
   return Dbg::IsDebuggerActive();
 }
 
-static jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
-  return Dbg::IsJdwpConfigured();
+static jboolean VMDebug_isDebuggingEnabled(JNIEnv* env, jclass) {
+  ScopedObjectAccess soa(env);
+  return Runtime::Current()->GetRuntimeCallbacks()->IsDebuggerConfigured();
 }
 
 static jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index a3c0036..47309ed 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -92,8 +92,11 @@
           .IntoKey(M::CheckJni)
       .Define("-Xjniopts:forcecopy")
           .IntoKey(M::JniOptsForceCopy)
-      .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_"})
-          .WithType<JDWP::JdwpOptions>()
+      .Define("-XjdwpProvider:_")
+          .WithType<JdwpProvider>()
+          .IntoKey(M::JdwpProvider)
+      .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_", "-XjdwpOptions:_"})
+          .WithType<std::string>()
           .IntoKey(M::JdwpOptions)
       // TODO Re-enable -agentlib: once I have a good way to transform the values.
       // .Define("-agentlib:_")
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 1cdeb7c..a172392 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -355,7 +355,7 @@
   }
 
   // Make sure our internal threads are dead before we start tearing down things they're using.
-  Dbg::StopJdwp();
+  GetRuntimeCallbacks()->StopDebugger();
   delete signal_catcher_;
 
   // Make sure all other non-daemon threads have terminated, and all daemon threads are suspended.
@@ -871,8 +871,10 @@
   StartSignalCatcher();
 
   // Start the JDWP thread. If the command-line debugger flags specified "suspend=y",
-  // this will pause the runtime, so we probably want this to come last.
-  Dbg::StartJdwp();
+  // this will pause the runtime (in the internal debugger implementation), so we probably want
+  // this to come last.
+  ScopedObjectAccess soa(Thread::Current());
+  GetRuntimeCallbacks()->StartDebugger();
 }
 
 void Runtime::StartSignalCatcher() {
@@ -1221,8 +1223,29 @@
 
   dump_gc_performance_on_shutdown_ = runtime_options.Exists(Opt::DumpGCPerformanceOnShutdown);
 
-  if (runtime_options.Exists(Opt::JdwpOptions)) {
-    Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions));
+  jdwp_options_ = runtime_options.GetOrDefault(Opt::JdwpOptions);
+  jdwp_provider_ = runtime_options.GetOrDefault(Opt::JdwpProvider);
+  switch (jdwp_provider_) {
+    case JdwpProvider::kNone: {
+      LOG(WARNING) << "Disabling all JDWP support.";
+      break;
+    }
+    case JdwpProvider::kInternal: {
+      if (runtime_options.Exists(Opt::JdwpOptions)) {
+        JDWP::JdwpOptions ops;
+        if (!JDWP::ParseJdwpOptions(runtime_options.GetOrDefault(Opt::JdwpOptions), &ops)) {
+          LOG(ERROR) << "failed to parse jdwp options!";
+          return false;
+        }
+        Dbg::ConfigureJdwp(ops);
+      }
+      break;
+    }
+    case JdwpProvider::kAdbConnection: {
+      constexpr const char* plugin_name = kIsDebugBuild ? "libadbconnectiond.so"
+                                                        : "libadbconnection.so";
+      plugins_.push_back(Plugin::Create(plugin_name));
+    }
   }
   callbacks_->AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback());
   callbacks_->AddClassLoadCallback(Dbg::GetClassLoadCallback());
@@ -1509,6 +1532,7 @@
   }
 
   // Is the process debuggable? Otherwise, do not attempt to load the plugin.
+  // TODO Support a crimped jvmti for non-debuggable runtimes.
   if (!runtime->IsJavaDebuggable()) {
     *error_msg = "Process is not debuggable.";
     return false;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 476b71f..89caac4 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -35,6 +35,7 @@
 #include "experimental_flags.h"
 #include "gc_root.h"
 #include "instrumentation.h"
+#include "jdwp_provider.h"
 #include "obj_ptr.h"
 #include "offsets.h"
 #include "process_state.h"
@@ -696,6 +697,14 @@
     return madvise_random_access_;
   }
 
+  const std::string& GetJdwpOptions() {
+    return jdwp_options_;
+  }
+
+  JdwpProvider GetJdwpProvider() const {
+    return jdwp_provider_;
+  }
+
  private:
   static void InitPlatformSignalHandlers();
 
@@ -953,6 +962,12 @@
   // Whether zygote code is in a section that should not start threads.
   bool zygote_no_threads_;
 
+  // The string containing requested jdwp options
+  std::string jdwp_options_;
+
+  // The jdwp provider we were configured with.
+  JdwpProvider jdwp_provider_;
+
   // Saved environment.
   class EnvSnapshot {
    public:
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 40d7889..cd3c0b7 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -49,6 +49,35 @@
   }
 }
 
+void RuntimeCallbacks::AddDebuggerControlCallback(DebuggerControlCallback* cb) {
+  debugger_control_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveDebuggerControlCallback(DebuggerControlCallback* cb) {
+  Remove(cb, &debugger_control_callbacks_);
+}
+
+bool RuntimeCallbacks::IsDebuggerConfigured() {
+  for (DebuggerControlCallback* cb : debugger_control_callbacks_) {
+    if (cb->IsDebuggerConfigured()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void RuntimeCallbacks::StartDebugger() {
+  for (DebuggerControlCallback* cb : debugger_control_callbacks_) {
+    cb->StartDebugger();
+  }
+}
+
+void RuntimeCallbacks::StopDebugger() {
+  for (DebuggerControlCallback* cb : debugger_control_callbacks_) {
+    cb->StopDebugger();
+  }
+}
+
 void RuntimeCallbacks::AddMethodInspectionCallback(MethodInspectionCallback* cb) {
   method_inspection_callbacks_.push_back(cb);
 }
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index baf941a..f405c9f 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -62,6 +62,19 @@
       REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
 
+class DebuggerControlCallback {
+ public:
+  virtual ~DebuggerControlCallback() {}
+
+  // Begin running the debugger.
+  virtual void StartDebugger() = 0;
+  // The debugger should begin shutting down since the runtime is ending. This is just advisory
+  virtual void StopDebugger() = 0;
+
+  // This allows the debugger to tell the runtime if it is configured.
+  virtual bool IsDebuggerConfigured() = 0;
+};
+
 class RuntimeSigQuitCallback {
  public:
   virtual ~RuntimeSigQuitCallback() {}
@@ -197,6 +210,17 @@
   void AddDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
   void RemoveDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void StartDebugger() REQUIRES_SHARED(Locks::mutator_lock_);
+  // NO_THREAD_SAFETY_ANALYSIS since this is only called when we are in the middle of shutting down
+  // and the mutator_lock_ is no longer acquirable.
+  void StopDebugger() NO_THREAD_SAFETY_ANALYSIS;
+  bool IsDebuggerConfigured() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AddDebuggerControlCallback(DebuggerControlCallback* cb)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  void RemoveDebuggerControlCallback(DebuggerControlCallback* cb)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   std::vector<ThreadLifecycleCallback*> thread_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
@@ -214,6 +238,8 @@
       GUARDED_BY(Locks::mutator_lock_);
   std::vector<DdmCallback*> ddm_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
+  std::vector<DebuggerControlCallback*> debugger_control_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
 };
 
 }  // namespace art
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 2e03562..4bc8245 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -43,7 +43,8 @@
 RUNTIME_OPTIONS_KEY (std::string,         Image)
 RUNTIME_OPTIONS_KEY (Unit,                CheckJni)
 RUNTIME_OPTIONS_KEY (Unit,                JniOptsForceCopy)
-RUNTIME_OPTIONS_KEY (JDWP::JdwpOptions,   JdwpOptions)
+RUNTIME_OPTIONS_KEY (std::string,         JdwpOptions, "")
+RUNTIME_OPTIONS_KEY (JdwpProvider,        JdwpProvider, JdwpProvider::kInternal)
 RUNTIME_OPTIONS_KEY (MemoryKiB,           MemoryMaximumSize,              gc::Heap::kDefaultMaximumSize)  // -Xmx
 RUNTIME_OPTIONS_KEY (MemoryKiB,           MemoryInitialSize,              gc::Heap::kDefaultInitialSize)  // -Xms
 RUNTIME_OPTIONS_KEY (MemoryKiB,           HeapGrowthLimit)                // Default is 0 for unlimited
diff --git a/tools/dt_fds_forward.py b/tools/dt_fds_forward.py
new file mode 100755
index 0000000..516b7fe
--- /dev/null
+++ b/tools/dt_fds_forward.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+#
+# Copyright 2017, 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.
+
+"""
+A python program that simulates the plugin side of the dt_fd_forward transport for testing.
+
+This program will invoke a given java language runtime program and send down debugging arguments
+that cause it to use the dt_fd_forward transport. This will then create a normal server-port that
+debuggers can attach to.
+"""
+
+import argparse
+import array
+from multiprocessing import Process
+import contextlib
+import ctypes
+import os
+import select
+import socket
+import subprocess
+import sys
+import time
+
+LISTEN_START_MESSAGE = b"dt_fd_forward:START-LISTEN\x00"
+LISTEN_END_MESSAGE   = b"dt_fd_forward:END-LISTEN\x00"
+ACCEPTED_MESSAGE     = b"dt_fd_forward:ACCEPTED\x00"
+CLOSE_MESSAGE        = b"dt_fd_forward:CLOSING\x00"
+
+libc = ctypes.cdll.LoadLibrary("libc.so.6")
+def eventfd(init_val, flags):
+  """
+  Creates an eventfd. See 'man 2 eventfd' for more information.
+  """
+  return libc.eventfd(init_val, flags)
+
+@contextlib.contextmanager
+def make_eventfd(init):
+  """
+  Creates an eventfd with given initial value that is closed after the manager finishes.
+  """
+  fd = eventfd(init, 0)
+  yield fd
+  os.close(fd)
+
+@contextlib.contextmanager
+def make_sockets():
+  """
+  Make a (remote,local) socket pair. The remote socket is inheritable by forked processes. They are
+  both linked together.
+  """
+  (rfd, lfd) = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET)
+  yield (rfd, lfd)
+  rfd.close()
+  lfd.close()
+
+def send_fds(sock, remote_read, remote_write, remote_event):
+  """
+  Send the three fds over the given socket.
+  """
+  sock.sendmsg([b"!"],  # We don't actually care what we send here.
+               [(socket.SOL_SOCKET,  # Send over socket.
+                 socket.SCM_RIGHTS,  # Payload is file-descriptor array
+                 array.array('i', [remote_read, remote_write, remote_event]))])
+
+
+def HandleSockets(host, port, local_sock, finish_event):
+  """
+  Handle the IO between the network and the runtime.
+
+  This is similar to what we will do with the plugin that controls the jdwp connection.
+
+  The main difference is it will keep around the connection and event-fd in order to let it send
+  ddms packets directly.
+  """
+  listening = False
+  with socket.socket() as sock:
+    sock.bind((host, port))
+    sock.listen()
+    while True:
+      sources = [local_sock, finish_event, sock]
+      print("Starting select on " + str(sources))
+      (rf, _, _) = select.select(sources, [], [])
+      if local_sock in rf:
+        buf = local_sock.recv(1024)
+        print("Local_sock has data: " + str(buf))
+        if buf == LISTEN_START_MESSAGE:
+          print("listening on " + str(sock))
+          listening = True
+        elif buf == LISTEN_END_MESSAGE:
+          print("End listening")
+          listening = False
+        elif buf == ACCEPTED_MESSAGE:
+          print("Fds were accepted.")
+        elif buf == CLOSE_MESSAGE:
+          # TODO Dup the fds and send a fake DDMS message like the actual plugin would.
+          print("Fds were closed")
+        else:
+          print("Unknown data received from socket " + str(buf))
+          return
+      elif sock in rf:
+        (conn, addr) = sock.accept()
+        with conn:
+          print("connection accepted from " + str(addr))
+          if listening:
+            with make_eventfd(1) as efd:
+              print("sending fds ({}, {}, {}) to target.".format(conn.fileno(), conn.fileno(), efd))
+              send_fds(local_sock, conn.fileno(), conn.fileno(), efd)
+          else:
+            print("Closing fds since we cannot accept them.")
+      if finish_event in rf:
+        print("woke up from finish_event")
+        return
+
+def StartChildProcess(cmd_pre, cmd_post, jdwp_lib, jdwp_ops, remote_sock, can_be_runtest):
+  """
+  Open the child java-language runtime process.
+  """
+  full_cmd = list(cmd_pre)
+  os.set_inheritable(remote_sock.fileno(), True)
+  jdwp_arg = jdwp_lib + "=" + \
+             jdwp_ops + "transport=dt_fd_forward,address=" + str(remote_sock.fileno())
+  if can_be_runtest and cmd_pre[0].endswith("run-test"):
+    print("Assuming run-test. Pass --no-run-test if this isn't true")
+    full_cmd += ["--with-agent", jdwp_arg]
+  else:
+    full_cmd.append("-agentpath:" + jdwp_arg)
+  full_cmd += cmd_post
+  print("Running " + str(full_cmd))
+  # Start the actual process with the fd being passed down.
+  proc = subprocess.Popen(full_cmd, close_fds=False)
+  # Get rid of the extra socket.
+  remote_sock.close()
+  proc.wait()
+
+def main():
+  parser = argparse.ArgumentParser(description="""
+                                   Runs a socket that forwards to dt_fds.
+
+                                   Pass '--' to start passing in the program we will pass the debug
+                                   options down to.
+                                   """)
+  parser.add_argument("--host", type=str, default="localhost",
+                      help="Host we will listen for traffic on. Defaults to 'localhost'.")
+  parser.add_argument("--debug-lib", type=str, default="libjdwp.so",
+                      help="jdwp library we pass to -agentpath:. Default is 'libjdwp.so'")
+  parser.add_argument("--debug-options", type=str, default="server=y,suspend=y,",
+                      help="non-address options we pass to jdwp agent, default is " +
+                           "'server=y,suspend=y,'")
+  parser.add_argument("--port", type=int, default=12345,
+                      help="port we will expose the traffic on. Defaults to 12345.")
+  parser.add_argument("--no-run-test", default=False, action="store_true",
+                      help="don't pass in arguments for run-test even if it looks like that is " +
+                           "the program")
+  parser.add_argument("--pre-end", type=int, default=1,
+                      help="number of 'rest' arguments to put before passing in the debug options")
+  end_idx = 0 if '--' not in sys.argv else sys.argv.index('--')
+  if end_idx == 0 and ('--help' in sys.argv or '-h' in sys.argv):
+    parser.print_help()
+    return
+  args = parser.parse_args(sys.argv[:end_idx][1:])
+  rest = sys.argv[1 + end_idx:]
+
+  with make_eventfd(0) as wakeup_event:
+    with make_sockets() as (remote_sock, local_sock):
+      invoker = Process(target=StartChildProcess,
+                        args=(rest[:args.pre_end],
+                              rest[args.pre_end:],
+                              args.debug_lib,
+                              args.debug_options,
+                              remote_sock,
+                              not args.no_run_test))
+      socket_handler = Process(target=HandleSockets,
+                               args=(args.host, args.port, local_sock, wakeup_event))
+      socket_handler.start()
+      invoker.start()
+    invoker.join()
+    # Write any 64 bit value to the wakeup_event to make sure that the socket handler will wake
+    # up and exit.
+    os.write(wakeup_event, b'\x00\x00\x00\x00\x00\x00\x01\x00')
+    socket_handler.join()
+
+if __name__ == '__main__':
+  main()