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()