diff options
35 files changed, 2730 insertions, 207 deletions
diff --git a/Android.bp b/Android.bp index 295ae4c556..197860694b 100644 --- a/Android.bp +++ b/Android.bp @@ -20,6 +20,7 @@ art_static_dependencies = [ ] subdirs = [ + "adbconnection", "benchmark", "build", "cmdline", @@ -31,6 +32,8 @@ subdirs = [ "dexlist", "dexoptanalyzer", "disassembler", + "dt_fd_forward", + "dt_fd_forward/export", "imgdiag", "oatdump", "openjdkjvm", diff --git a/Android.mk b/Android.mk index 174cde36ef..cb0b709107 100644 --- a/Android.mk +++ b/Android.mk @@ -370,6 +370,7 @@ LOCAL_REQUIRED_MODULES := \ 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 @@ LOCAL_REQUIRED_MODULES += \ libopenjdkjvmtid \ patchoatd \ profmand \ + libadbconnectiond \ endif endif diff --git a/adbconnection/Android.bp b/adbconnection/Android.bp new file mode 100644 index 0000000000..441b706556 --- /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 0000000000..2a9982a6e4 --- /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 0000000000..28a5a05af3 --- /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 c438c54cea..5d672061df 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(CmdlineParserTest, DISABLED_TestXGcOption) { } // 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; + EXPECT_SINGLE_PARSE_DEFAULT_VALUE(JdwpProvider::kInternal, "", M::JdwpProvider); + } +} // TEST_F - const char *opt_args = "-Xrunjdwp:transport=dt_socket,address=8000,server=y"; +TEST_F(CmdlineParserTest, TestJdwpProviderDefault) { + const char* opt_args = "-XjdwpProvider:default"; + EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, M::JdwpProvider); +} // TEST_F - EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); - } +TEST_F(CmdlineParserTest, TestJdwpProviderInternal) { + const char* opt_args = "-XjdwpProvider:internal"; + EXPECT_SINGLE_PARSE_VALUE(JdwpProvider::kInternal, opt_args, 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; - - const char *opt_args = "-agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n"; - - 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 f12ef971af..529fe2bcdb 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 @@ struct CmdlineType<Unit> : CmdlineTypeParser<Unit> { }; 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/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index bef9d77b9a..b3d956137a 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -545,11 +545,8 @@ class WatchDog { // it's rather easy to hang in unwinding. // LogLine also avoids ART logging lock issues, as it's really only a wrapper around // logcat logging or stderr output. - android::base::LogMessage::LogLine(__FILE__, - __LINE__, - android::base::LogId::DEFAULT, - LogSeverity::FATAL, - message.c_str()); + LogHelper::LogLineLowStack(__FILE__, __LINE__, LogSeverity::FATAL, message.c_str()); + // If we're on the host, try to dump all threads to get a sense of what's going on. This is // restricted to the host as the dump may itself go bad. // TODO: Use a double watchdog timeout, so we can enable this on-device. diff --git a/dt_fd_forward/Android.bp b/dt_fd_forward/Android.bp new file mode 100644 index 0000000000..1ba2323a15 --- /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 0000000000..e69de29bb2 --- /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 0000000000..4fd88fa841 --- /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 0000000000..c7ac840928 --- /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 0000000000..cf3088dc60 --- /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 0000000000..9303c59acd --- /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 0000000000..c3a63212b9 --- /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 0000000000..e69de29bb2 --- /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 0000000000..245f0c2275 --- /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 dc4c78b32f..86e851c72b 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 @@ using android::base::StringPrintf; 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 @@ TEST_F(PatchoatTest, PatchoatRelocationSameAsDex2oatRelocation) { 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; - - // 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; + // 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 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 6477347a6e..1e5fe16e19 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -470,6 +470,7 @@ gensrcs { "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 @@ art_cc_test { "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/base/bit_string.h b/runtime/base/bit_string.h index a2164f335d..bfbe8eaf71 100644 --- a/runtime/base/bit_string.h +++ b/runtime/base/bit_string.h @@ -114,7 +114,7 @@ inline std::ostream& operator<<(std::ostream& os, const BitStringChar& bc) { /** * BitString * - * MSB LSB + * lsb (least significant bit) msb * +------------+------------+------------+-----+------------+ * | | | | | | * | Char0 | Char1 | Char2 | ... | CharN | @@ -131,8 +131,9 @@ inline std::ostream& operator<<(std::ostream& os, const BitStringChar& bc) { * "ABCDE...K" := [A,B,C,D,E, ... K] + [0]*(N-idx(K)) s.t. N >= K. * // Padded with trailing 0s to fit (N+1) bitstring chars. * MaxBitstringLen := N+1 - * StrLen(Bitstring) := MaxBitStringLen - | forall char in CharI..CharN : char == 0 AND Char(I-1) != 0 | - * // Maximum length - the # of consecutive trailing zeroes. + * StrLen(Bitstring) := I s.t. (I == 0 OR Char(I-1) != 0) + * AND forall char in CharI..CharN : char == 0 + * // = Maximum length - the # of consecutive trailing zeroes. * Bitstring[N] := CharN * Bitstring[I..N) := [CharI, CharI+1, ... CharN-1] * @@ -278,8 +279,8 @@ struct BitString { private: friend std::ostream& operator<<(std::ostream& os, const BitString& bit_string); - // Data is stored with the "highest" position in the least-significant-bit. - // As positions approach 0, the bits are stored with increasing significance. + // Data is stored with the first character in the least-significant-bit. + // Unused bits are zero. StorageType storage_; }; diff --git a/runtime/debugger.cc b/runtime/debugger.cc index b5ae09f701..c85c2336b0 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -328,6 +328,7 @@ bool Dbg::gDisposed = false; 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 @@ bool DebuggerActiveMethodInspectionCallback::IsMethodSafeToJit(ArtMethod* m) { 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 @@ void Dbg::ConfigureJdwp(const JDWP::JdwpOptions& jdwp_options) { 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 d5bad8dc67..74018137a0 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -53,18 +53,22 @@ class ScopedObjectAccessUnchecked; 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 @@ class Dbg { } // 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 @@ class Dbg { 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 d712b10bc2..b491c3ee5c 100644 --- a/runtime/jdwp/jdwp.h +++ b/runtime/jdwp/jdwp.h @@ -98,14 +98,15 @@ bool operator!=(const JdwpLocation& lhs, const JdwpLocation& rhs); * 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 @@ struct JdwpOptions { 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 e275554721..63f5dc8b69 100644 --- a/runtime/jdwp/jdwp_main.cc +++ b/runtime/jdwp/jdwp_main.cc @@ -37,6 +37,119 @@ using android::base::StringPrintf; 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 0000000000..10c52e8cf8 --- /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 0000000000..b62e10b4f8 --- /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 88a78ab4be..787646dd21 100644 --- a/runtime/native/dalvik_system_VMDebug.cc +++ b/runtime/native/dalvik_system_VMDebug.cc @@ -157,8 +157,9 @@ static jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) { 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 a3c00364a1..47309edfd7 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -92,8 +92,11 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .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 1cdeb7c77c..a172392c51 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -355,7 +355,7 @@ Runtime::~Runtime() { } // 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 @@ void Runtime::InitNonZygoteOrPostFork( 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 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { 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 @@ static bool EnsureJvmtiPlugin(Runtime* runtime, } // 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 476b71f169..89caac4f3c 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 @@ class Runtime { 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 @@ class Runtime { // 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 40d7889565..cd3c0b7c88 100644 --- a/runtime/runtime_callbacks.cc +++ b/runtime/runtime_callbacks.cc @@ -49,6 +49,35 @@ void RuntimeCallbacks::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8 } } +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 baf941a8e1..f405c9fe34 100644 --- a/runtime/runtime_callbacks.h +++ b/runtime/runtime_callbacks.h @@ -62,6 +62,19 @@ class DdmCallback { 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 @@ class RuntimeCallbacks { 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 @@ class RuntimeCallbacks { 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 2e03562505..4bc824570a 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -43,7 +43,8 @@ RUNTIME_OPTIONS_KEY (std::string, ClassPath) 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/runtime/subtype_check_info.h b/runtime/subtype_check_info.h index cd579c3a5c..8320fb35a9 100644 --- a/runtime/subtype_check_info.h +++ b/runtime/subtype_check_info.h @@ -90,10 +90,13 @@ namespace art { * * Uninitialized <=> StrLen(PathToRoot) == 0 * Next == 0 + * OF == False * Initialized <=> StrLen(PathToRoot) < Depth - * Next == 0 + * Next == 1 + * OF == False * Assigned <=> StrLen(PathToRoot) == Depth - * Next > 1 + * Next >= 1 + * OF == False * Overflowed <=> OF == True * * Tree Invariants: @@ -219,7 +222,7 @@ struct SubtypeCheckInfo { // Next must be non-0 to disambiguate it from Uninitialized. child.MaybeInitNext(); - // Always clear the inherited Parent's next Value on the child. + // Always clear the inherited Parent's next Value, i.e. the child's last path entry. OverwriteNextValueFromParent(/*inout*/&child, BitStringChar{}); // The state is now Initialized | Overflowed. @@ -235,7 +238,6 @@ struct SubtypeCheckInfo { // Assign attempt. if (HasNext() && !bitstring_and_of_.overflow_) { - // Do not bother assigning if parent had overflowed. BitStringChar next = GetNext(); if (next != next.MaximumValue()) { // The parent's "next" value is now the child's latest path element. @@ -260,17 +262,16 @@ struct SubtypeCheckInfo { // Get the current state (Uninitialized, Initialized, Assigned, or Overflowed). // See the "SubtypeCheckInfo" documentation above which explains how a state is determined. State GetState() const { - if (GetBitString().IsEmpty()) { - // Empty bitstring (all 0s) -> uninitialized. - DCHECK(!bitstring_and_of_.overflow_); - return kUninitialized; - } - if (bitstring_and_of_.overflow_) { // Overflowed if and only if the OF bit was set. return kOverflowed; } + if (GetBitString().IsEmpty()) { + // Empty bitstring (all 0s) -> uninitialized. + return kUninitialized; + } + // Either Assigned or Initialized. BitString path_to_root = GetPathToRoot(); @@ -387,6 +388,7 @@ struct SubtypeCheckInfo { SetBitStringUnchecked(bs); } + // If there is a next field, set it to 1. void MaybeInitNext() { if (HasNext()) { // Clearing out the "Next" value like this diff --git a/tools/dt_fds_forward.py b/tools/dt_fds_forward.py new file mode 100755 index 0000000000..516b7fef96 --- /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() |