blob: f1aed96e5b23a51591f0621ca1d6a3e25c2c2896 [file] [log] [blame]
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/debug/wasm/gdb-server/transport.h"
#include <fcntl.h>
#ifndef SD_BOTH
#define SD_BOTH 2
#endif
namespace v8 {
namespace internal {
namespace wasm {
namespace gdb_server {
SocketBinding::SocketBinding(SocketHandle socket_handle)
: socket_handle_(socket_handle) {}
// static
SocketBinding SocketBinding::Bind(uint16_t tcp_port) {
SocketHandle socket_handle = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket_handle == InvalidSocket) {
TRACE_GDB_REMOTE("Failed to create socket.\n");
return SocketBinding(InvalidSocket);
}
struct sockaddr_in sockaddr;
// Clearing sockaddr_in first appears to be necessary on Mac OS X.
memset(&sockaddr, 0, sizeof(sockaddr));
socklen_t addrlen = static_cast<socklen_t>(sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sockaddr.sin_port = htons(tcp_port);
#if _WIN32
// On Windows, SO_REUSEADDR has a different meaning than on POSIX systems.
// SO_REUSEADDR allows hijacking of an open socket by another process.
// The SO_EXCLUSIVEADDRUSE flag prevents this behavior.
// See:
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621(v=vs.85).aspx
//
// Additionally, unlike POSIX, TCP server sockets can be bound to
// ports in the TIME_WAIT state, without setting SO_REUSEADDR.
int exclusive_address = 1;
if (setsockopt(socket_handle, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<char*>(&exclusive_address),
sizeof(exclusive_address))) {
TRACE_GDB_REMOTE("Failed to set SO_EXCLUSIVEADDRUSE option.\n");
}
#else
// On POSIX, this is necessary to ensure that the TCP port is released
// promptly when sel_ldr exits. Without this, the TCP port might
// only be released after a timeout, and later processes can fail
// to bind it.
int reuse_address = 1;
if (setsockopt(socket_handle, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<char*>(&reuse_address),
sizeof(reuse_address))) {
TRACE_GDB_REMOTE("Failed to set SO_REUSEADDR option.\n");
}
#endif
if (bind(socket_handle, reinterpret_cast<struct sockaddr*>(&sockaddr),
addrlen)) {
TRACE_GDB_REMOTE("Failed to bind server.\n");
return SocketBinding(InvalidSocket);
}
if (listen(socket_handle, 1)) {
TRACE_GDB_REMOTE("Failed to listen.\n");
return SocketBinding(InvalidSocket);
}
return SocketBinding(socket_handle);
}
std::unique_ptr<SocketTransport> SocketBinding::CreateTransport() {
return std::make_unique<SocketTransport>(socket_handle_);
}
uint16_t SocketBinding::GetBoundPort() {
struct sockaddr_in saddr;
struct sockaddr* psaddr = reinterpret_cast<struct sockaddr*>(&saddr);
// Clearing sockaddr_in first appears to be necessary on Mac OS X.
memset(&saddr, 0, sizeof(saddr));
socklen_t addrlen = static_cast<socklen_t>(sizeof(saddr));
if (::getsockname(socket_handle_, psaddr, &addrlen)) {
TRACE_GDB_REMOTE("Failed to retrieve bound address.\n");
return 0;
}
return ntohs(saddr.sin_port);
}
// Do not delay sending small packets. This significantly speeds up
// remote debugging. Debug stub uses buffering to send outgoing packets
// so they are not split into more TCP packets than necessary.
void DisableNagleAlgorithm(SocketHandle socket) {
int nodelay = 1;
if (::setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<char*>(&nodelay), sizeof(nodelay))) {
TRACE_GDB_REMOTE("Failed to set TCP_NODELAY option.\n");
}
}
Transport::Transport(SocketHandle s)
: buf_(new char[kBufSize]),
pos_(0),
size_(0),
handle_bind_(s),
handle_accept_(InvalidSocket) {}
Transport::~Transport() {
if (handle_accept_ != InvalidSocket) {
CloseSocket(handle_accept_);
}
}
void Transport::CopyFromBuffer(char** dst, int32_t* len) {
int32_t copy_bytes = std::min(*len, size_ - pos_);
memcpy(*dst, buf_.get() + pos_, copy_bytes);
pos_ += copy_bytes;
*len -= copy_bytes;
*dst += copy_bytes;
}
bool Transport::Read(char* dst, int32_t len) {
if (pos_ < size_) {
CopyFromBuffer(&dst, &len);
}
while (len > 0) {
pos_ = 0;
size_ = 0;
if (!ReadSomeData()) {
return false;
}
CopyFromBuffer(&dst, &len);
}
return true;
}
bool Transport::Write(const char* src, int32_t len) {
while (len > 0) {
ssize_t result = ::send(handle_accept_, src, len, 0);
if (result > 0) {
src += result;
len -= result;
continue;
}
if (result == 0) {
return false;
}
if (SocketGetLastError() != kErrInterrupt) {
return false;
}
}
return true;
}
// Return true if there is data to read.
bool Transport::IsDataAvailable() const {
if (pos_ < size_) {
return true;
}
fd_set fds;
FD_ZERO(&fds);
FD_SET(handle_accept_, &fds);
// We want a "non-blocking" check
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
// Check if this file handle can select on read
int cnt = select(static_cast<int>(handle_accept_) + 1, &fds, 0, 0, &timeout);
// If we are ready, or if there is an error. We return true
// on error, to let the next IO request fail.
if (cnt != 0) return true;
return false;
}
void Transport::Close() {
::shutdown(handle_bind_, SD_BOTH);
CloseSocket(handle_bind_);
Disconnect();
}
void Transport::Disconnect() {
if (handle_accept_ != InvalidSocket) {
// Shutdown the connection in both directions. This should
// always succeed, and nothing we can do if this fails.
::shutdown(handle_accept_, SD_BOTH);
CloseSocket(handle_accept_);
handle_accept_ = InvalidSocket;
}
}
#if _WIN32
SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
socket_event_ = WSA_INVALID_EVENT;
faulted_thread_event_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (faulted_thread_event_ == NULL) {
TRACE_GDB_REMOTE(
"SocketTransport::SocketTransport: Failed to create event object for "
"faulted thread\n");
}
}
SocketTransport::~SocketTransport() {
if (!CloseHandle(faulted_thread_event_)) {
TRACE_GDB_REMOTE(
"SocketTransport::~SocketTransport: Failed to close "
"event\n");
}
if (socket_event_) {
if (!::WSACloseEvent(socket_event_)) {
TRACE_GDB_REMOTE(
"SocketTransport::~SocketTransport: Failed to close "
"socket event\n");
}
}
}
bool SocketTransport::AcceptConnection() {
CHECK(handle_accept_ == InvalidSocket);
handle_accept_ = ::accept(handle_bind_, NULL, 0);
if (handle_accept_ != InvalidSocket) {
DisableNagleAlgorithm(handle_accept_);
// Create socket event
socket_event_ = ::WSACreateEvent();
if (socket_event_ == WSA_INVALID_EVENT) {
TRACE_GDB_REMOTE(
"SocketTransport::AcceptConnection: Failed to create socket event\n");
}
// Listen for close events in order to handle them correctly.
// Additionally listen for read readiness as WSAEventSelect sets the socket
// to non-blocking mode.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547(v=vs.85).aspx
if (::WSAEventSelect(handle_accept_, socket_event_, FD_CLOSE | FD_READ) ==
SOCKET_ERROR) {
TRACE_GDB_REMOTE(
"SocketTransport::AcceptConnection: Failed to bind event to "
"socket\n");
}
return true;
}
return false;
}
bool SocketTransport::ReadSomeData() {
while (true) {
ssize_t result =
::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
if (result > 0) {
size_ += result;
return true;
}
if (result == 0) {
return false; // The connection was gracefully closed.
}
// WSAEventSelect sets socket to non-blocking mode. This is essential
// for socket event notification to work, there is no workaround.
// See remarks section at the page
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
if (SocketGetLastError() == WSAEWOULDBLOCK) {
if (::WaitForSingleObject(socket_event_, INFINITE) == WAIT_FAILED) {
TRACE_GDB_REMOTE(
"SocketTransport::ReadSomeData: Failed to wait on socket event\n");
}
if (!::ResetEvent(socket_event_)) {
TRACE_GDB_REMOTE(
"SocketTransport::ReadSomeData: Failed to reset socket event\n");
}
continue;
}
if (SocketGetLastError() != kErrInterrupt) {
return false;
}
}
}
void SocketTransport::WaitForDebugStubEvent() {
// Don't wait if we already have data to read.
bool wait = !(pos_ < size_);
HANDLE handles[2];
handles[0] = faulted_thread_event_;
handles[1] = socket_event_;
int count = size_ < kBufSize ? 2 : 1;
int result =
WaitForMultipleObjects(count, handles, FALSE, wait ? INFINITE : 0);
if (result == WAIT_OBJECT_0 + 1) {
if (!ResetEvent(socket_event_)) {
TRACE_GDB_REMOTE(
"SocketTransport::WaitForDebugStubEvent: Failed to reset socket "
"event\n");
}
return;
} else if (result == WAIT_OBJECT_0) {
if (!ResetEvent(faulted_thread_event_)) {
TRACE_GDB_REMOTE(
"SocketTransport::WaitForDebugStubEvent: Failed to reset event\n");
}
return;
} else if (result == WAIT_TIMEOUT) {
return;
}
TRACE_GDB_REMOTE(
"SocketTransport::WaitForDebugStubEvent: Wait for events failed\n");
}
bool SocketTransport::SignalThreadEvent() {
if (!SetEvent(faulted_thread_event_)) {
return false;
}
return true;
}
void SocketTransport::Disconnect() {
Transport::Disconnect();
if (socket_event_ != WSA_INVALID_EVENT && !::WSACloseEvent(socket_event_)) {
TRACE_GDB_REMOTE(
"SocketTransport::~SocketTransport: Failed to close "
"socket event\n");
}
socket_event_ = WSA_INVALID_EVENT;
SignalThreadEvent();
}
#else // _WIN32
SocketTransport::SocketTransport(SocketHandle s) : Transport(s) {
int fds[2];
#if defined(__linux__)
int ret = pipe2(fds, O_CLOEXEC);
#else
int ret = pipe(fds);
#endif
if (ret < 0) {
TRACE_GDB_REMOTE(
"SocketTransport::SocketTransport: Failed to allocate pipe for faulted "
"thread\n");
}
faulted_thread_fd_read_ = fds[0];
faulted_thread_fd_write_ = fds[1];
}
SocketTransport::~SocketTransport() {
if (close(faulted_thread_fd_read_) != 0) {
TRACE_GDB_REMOTE(
"SocketTransport::~SocketTransport: Failed to close "
"event\n");
}
if (close(faulted_thread_fd_write_) != 0) {
TRACE_GDB_REMOTE(
"SocketTransport::~SocketTransport: Failed to close "
"event\n");
}
}
bool SocketTransport::AcceptConnection() {
CHECK(handle_accept_ == InvalidSocket);
handle_accept_ = ::accept(handle_bind_, NULL, 0);
if (handle_accept_ != InvalidSocket) {
DisableNagleAlgorithm(handle_accept_);
return true;
}
return false;
}
bool SocketTransport::ReadSomeData() {
while (true) {
ssize_t result =
::recv(handle_accept_, buf_.get() + size_, kBufSize - size_, 0);
if (result > 0) {
size_ += result;
return true;
}
if (result == 0) {
return false; // The connection was gracefully closed.
}
if (SocketGetLastError() != kErrInterrupt) {
return false;
}
}
}
void SocketTransport::WaitForDebugStubEvent() {
// Don't wait if we already have data to read.
bool wait = !(pos_ < size_);
fd_set fds;
FD_ZERO(&fds);
FD_SET(faulted_thread_fd_read_, &fds);
int max_fd = faulted_thread_fd_read_;
if (size_ < kBufSize) {
FD_SET(handle_accept_, &fds);
max_fd = std::max(max_fd, handle_accept_);
}
int ret;
// We don't need sleep-polling on Linux now, so we set either zero or infinite
// timeout.
if (wait) {
ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
} else {
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
ret = select(max_fd + 1, &fds, NULL, NULL, &timeout);
}
if (ret < 0) {
TRACE_GDB_REMOTE(
"SocketTransport::WaitForDebugStubEvent: Failed to wait for "
"debug stub event\n");
}
if (ret > 0) {
if (FD_ISSET(faulted_thread_fd_read_, &fds)) {
char buf[16];
if (read(faulted_thread_fd_read_, &buf, sizeof(buf)) < 0) {
TRACE_GDB_REMOTE(
"SocketTransport::WaitForDebugStubEvent: Failed to read from "
"debug stub event pipe fd\n");
}
}
if (FD_ISSET(handle_accept_, &fds)) ReadSomeData();
}
}
bool SocketTransport::SignalThreadEvent() {
// Notify the debug stub by marking the thread as faulted.
char buf = 0;
if (write(faulted_thread_fd_write_, &buf, sizeof(buf)) != sizeof(buf)) {
TRACE_GDB_REMOTE(
"SocketTransport:SignalThreadEvent: Can't send debug stub "
"event\n");
return false;
}
return true;
}
#endif // _WIN32
} // namespace gdb_server
} // namespace wasm
} // namespace internal
} // namespace v8
#undef SD_BOTH