| // 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 |