Add fd-forwarding transport lib
dt_fd_forward is a jdwpTransport that takes as an address a local
socket file-descriptor and uses it to allow some other piece of code
or program to control the actual attach and communication process.
tools/dt_fds_forward.py is a python program that can be used as a
controller for this comms system. This is useful for testing. It
implements the same behavior (excepting DDMS) that the adbconnection
plugin will but is capable of being easily used on normal host
machines. Unlike the plugin, dt_fds_forward.py works by allowing a
socket file-descriptor to be inherited by the forked Java Language
Runtime.
The overall goal of this transport is to use it to allow us to safely
multiplex the out-bound data with DDMS data. This is needed to let us
match current DDMS behavior which transmits packets in the blind on
the same channel JDWP traffic is sent on.
Test: ./tools/dt_fds_forward.py \
./test/run-test --host --dev 001-HelloWorld
Test: jdb -attach localhost:12345
Test: nc localhost 12345, handshake, disconnect
Test: jdb -attach localhost:12345, detach, attach
Test: ./tools/dt_fds_forward.py \
--debug-lib $JAVA_HOME/jre/lib/amd64/libjdwp.so \
-- \
./test/run-test --host --jvm --dev 001-HelloWorld
Test: jdb -attach localhost:12345
Bug: 62821960
Bug: 69169846
Change-Id: I654db6c6991c006933e1e1f0a4e41c13f795f9a8
diff --git a/Android.bp b/Android.bp
index 295ae4c..2400115 100644
--- a/Android.bp
+++ b/Android.bp
@@ -31,6 +31,8 @@
"dexlist",
"dexoptanalyzer",
"disassembler",
+ "dt_fd_forward",
+ "dt_fd_forward/export",
"imgdiag",
"oatdump",
"openjdkjvm",
diff --git a/dt_fd_forward/Android.bp b/dt_fd_forward/Android.bp
new file mode 100644
index 0000000..84faa08
--- /dev/null
+++ b/dt_fd_forward/Android.bp
@@ -0,0 +1,63 @@
+//
+// 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: {
+ },
+ },
+ 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/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()