summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Maybee <paulmay@microsoft.com>2023-07-29 17:23:43 (GMT)
committerBrad King <brad.king@kitware.com>2023-08-17 14:11:33 (GMT)
commit8b1257e7bf821461e6e0d92bf57e1c4ed885ddf9 (patch)
tree9ce28af2bff1b6c1d6f97f3e105849d3fe7829f4
parentb0054dd65c1d69a437abe85d27e704326884a9c2 (diff)
downloadCMake-8b1257e7bf821461e6e0d92bf57e1c4ed885ddf9.zip
CMake-8b1257e7bf821461e6e0d92bf57e1c4ed885ddf9.tar.gz
CMake-8b1257e7bf821461e6e0d92bf57e1c4ed885ddf9.tar.bz2
Debugger: Replace libuv with platform-specific connection code
Remove libuv usage from CMake debugger. Libuv has an async io model and cppdap uses a sync model, so an extra thread and a buffer copy were necessary to match semantics. In order to eliminate those costs this commit implements the IO using platform specific APIs.
-rw-r--r--Source/CMakeLists.txt18
-rw-r--r--Source/cmDebuggerPipeConnection.cxx293
-rw-r--r--Source/cmDebuggerPipeConnection.h139
-rw-r--r--Source/cmDebuggerPosixPipeConnection.cxx205
-rw-r--r--Source/cmDebuggerPosixPipeConnection.h81
-rw-r--r--Source/cmDebuggerWindowsPipeConnection.cxx272
-rw-r--r--Source/cmDebuggerWindowsPipeConnection.h101
-rw-r--r--Source/cmake.cxx6
-rw-r--r--Tests/CMakeLib/testDebuggerAdapterPipe.cxx6
-rw-r--r--Tests/CMakeLib/testDebuggerNamedPipe.cxx9
10 files changed, 690 insertions, 440 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 708aec7..4c5b4e3 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -775,8 +775,6 @@ if(CMake_ENABLE_DEBUGGER)
cmDebuggerBreakpointManager.h
cmDebuggerExceptionManager.cxx
cmDebuggerExceptionManager.h
- cmDebuggerPipeConnection.cxx
- cmDebuggerPipeConnection.h
cmDebuggerProtocol.cxx
cmDebuggerProtocol.h
cmDebuggerSourceBreakpoint.cxx
@@ -794,6 +792,21 @@ if(CMake_ENABLE_DEBUGGER)
cmDebuggerVariablesManager.cxx
cmDebuggerVariablesManager.h
)
+ if(WIN32)
+ target_sources(
+ CMakeLib
+ PRIVATE
+ cmDebuggerWindowsPipeConnection.cxx
+ cmDebuggerWindowsPipeConnection.h
+ )
+ else()
+ target_sources(
+ CMakeLib
+ PRIVATE
+ cmDebuggerPosixPipeConnection.cxx
+ cmDebuggerPosixPipeConnection.h
+ )
+ endif()
target_link_libraries(CMakeLib PUBLIC cppdap::cppdap)
endif()
@@ -945,7 +958,6 @@ if(CMake_BUILD_PCH)
"$<$<COMPILE_LANGUAGE:CXX>:cmArgumentParser.h>"
"$<$<COMPILE_LANGUAGE:CXX>:cmake.h>"
"$<$<COMPILE_LANGUAGE:CXX>:cmCMakePath.h>"
- "$<$<COMPILE_LANGUAGE:CXX>:cmDebuggerPipeConnection.h>"
"$<$<COMPILE_LANGUAGE:CXX>:cmCurl.h>")
set_source_files_properties(
diff --git a/Source/cmDebuggerPipeConnection.cxx b/Source/cmDebuggerPipeConnection.cxx
deleted file mode 100644
index 1b54346..0000000
--- a/Source/cmDebuggerPipeConnection.cxx
+++ /dev/null
@@ -1,293 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
-#include "cmDebuggerPipeConnection.h"
-
-#include <algorithm>
-#include <cassert>
-#include <cstring>
-#include <stdexcept>
-#include <utility>
-
-namespace cmDebugger {
-
-struct write_req_t
-{
- uv_write_t req;
- uv_buf_t buf;
-};
-
-cmDebuggerPipeBase::cmDebuggerPipeBase(std::string name)
- : PipeName(std::move(name))
-{
- Loop.init();
- LoopExit.init(
- *Loop, [](uv_async_t* handle) { uv_stop((uv_loop_t*)handle->data); },
- Loop);
- WriteEvent.init(
- *Loop,
- [](uv_async_t* handle) {
- auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
- conn->WriteInternal();
- },
- this);
- PipeClose.init(
- *Loop,
- [](uv_async_t* handle) {
- auto* conn = static_cast<cmDebuggerPipeBase*>(handle->data);
- if (conn->Pipe.get()) {
- conn->Pipe->data = nullptr;
- conn->Pipe.reset();
- }
- },
- this);
-}
-
-void cmDebuggerPipeBase::WaitForConnection()
-{
- std::unique_lock<std::mutex> lock(Mutex);
- Connected.wait(lock, [this] { return isOpen() || FailedToOpen; });
- if (FailedToOpen) {
- throw std::runtime_error("Failed to open debugger connection.");
- }
-}
-
-void cmDebuggerPipeBase::close()
-{
- std::unique_lock<std::mutex> lock(Mutex);
-
- CloseConnection();
- PipeClose.send();
- lock.unlock();
- ReadReady.notify_all();
-}
-
-size_t cmDebuggerPipeBase::read(void* buffer, size_t n)
-{
- std::unique_lock<std::mutex> lock(Mutex);
- ReadReady.wait(lock, [this] { return !isOpen() || !ReadBuffer.empty(); });
-
- if (!isOpen() && ReadBuffer.empty()) {
- return 0;
- }
-
- auto size = std::min(n, ReadBuffer.size());
- memcpy(buffer, ReadBuffer.data(), size);
- ReadBuffer.erase(0, size);
- return size;
-}
-
-bool cmDebuggerPipeBase::write(const void* buffer, size_t n)
-{
- std::unique_lock<std::mutex> lock(Mutex);
- WriteBuffer.append(static_cast<const char*>(buffer), n);
- lock.unlock();
- WriteEvent.send();
-
- lock.lock();
- WriteComplete.wait(lock, [this] { return WriteBuffer.empty(); });
- return true;
-}
-
-void cmDebuggerPipeBase::StopLoop()
-{
- LoopExit.send();
-
- if (LoopThread.joinable()) {
- LoopThread.join();
- }
-}
-
-void cmDebuggerPipeBase::BufferData(const std::string& data)
-{
- std::unique_lock<std::mutex> lock(Mutex);
- ReadBuffer += data;
- lock.unlock();
- ReadReady.notify_all();
-}
-
-void cmDebuggerPipeBase::WriteInternal()
-{
- std::unique_lock<std::mutex> lock(Mutex);
- auto n = WriteBuffer.length();
- assert(this->Pipe.get());
- write_req_t* req = new write_req_t;
- req->req.data = &WriteComplete;
- char* rawBuffer = new char[n];
- req->buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(n));
- memcpy(req->buf.base, WriteBuffer.data(), n);
- WriteBuffer.clear();
- lock.unlock();
-
- uv_write(
- reinterpret_cast<uv_write_t*>(req), this->Pipe, &req->buf, 1,
- [](uv_write_t* cb_req, int status) {
- (void)status; // We need to free memory even if the write failed.
- write_req_t* wr = reinterpret_cast<write_req_t*>(cb_req);
- reinterpret_cast<std::condition_variable*>(wr->req.data)->notify_all();
- delete[] (wr->buf.base);
- delete wr;
- });
-
-#ifdef __clang_analyzer__
- // Tell clang-analyzer that 'rawBuffer' does not leak.
- // We pass ownership to the closure.
- delete[] rawBuffer;
-#endif
-}
-
-cmDebuggerPipeConnection::cmDebuggerPipeConnection(std::string name)
- : cmDebuggerPipeBase(std::move(name))
-{
- ServerPipeClose.init(
- *Loop,
- [](uv_async_t* handle) {
- auto* conn = static_cast<cmDebuggerPipeConnection*>(handle->data);
- if (conn->ServerPipe.get()) {
- conn->ServerPipe->data = nullptr;
- conn->ServerPipe.reset();
- }
- },
- this);
-}
-
-cmDebuggerPipeConnection::~cmDebuggerPipeConnection()
-{
- StopLoop();
-}
-
-bool cmDebuggerPipeConnection::StartListening(std::string& errorMessage)
-{
- this->ServerPipe.init(*Loop, 0,
- static_cast<cmDebuggerPipeConnection*>(this));
-
- int r;
- if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) {
- errorMessage =
- "Internal Error with " + this->PipeName + ": " + uv_err_name(r);
- return false;
- }
-
- r = uv_listen(this->ServerPipe, 1, [](uv_stream_t* stream, int status) {
- if (status >= 0) {
- auto* conn = static_cast<cmDebuggerPipeConnection*>(stream->data);
- if (conn) {
- conn->Connect(stream);
- }
- }
- });
-
- if (r != 0) {
- errorMessage =
- "Internal Error listening on " + this->PipeName + ": " + uv_err_name(r);
- return false;
- }
-
- // Start the libuv event loop thread so that a client can connect.
- LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
-
- StartedListening.set_value();
-
- return true;
-}
-
-std::shared_ptr<dap::Reader> cmDebuggerPipeConnection::GetReader()
-{
- return std::static_pointer_cast<dap::Reader>(shared_from_this());
-}
-
-std::shared_ptr<dap::Writer> cmDebuggerPipeConnection::GetWriter()
-{
- return std::static_pointer_cast<dap::Writer>(shared_from_this());
-}
-
-bool cmDebuggerPipeConnection::isOpen()
-{
- return this->Pipe.get() != nullptr;
-}
-
-void cmDebuggerPipeConnection::CloseConnection()
-{
- ServerPipeClose.send();
-}
-
-void cmDebuggerPipeConnection::Connect(uv_stream_t* server)
-{
- if (this->Pipe.get()) {
- // Accept and close all pipes but the first:
- cm::uv_pipe_ptr rejectPipe;
-
- rejectPipe.init(*Loop, 0);
- uv_accept(server, rejectPipe);
-
- return;
- }
-
- cm::uv_pipe_ptr ClientPipe;
- ClientPipe.init(*Loop, 0, static_cast<cmDebuggerPipeConnection*>(this));
-
- if (uv_accept(server, ClientPipe) != 0) {
- return;
- }
-
- StartReading<cmDebuggerPipeConnection>(ClientPipe);
-
- std::unique_lock<std::mutex> lock(Mutex);
- Pipe = std::move(ClientPipe);
- lock.unlock();
- Connected.notify_all();
-}
-
-cmDebuggerPipeClient::~cmDebuggerPipeClient()
-{
- StopLoop();
-}
-
-void cmDebuggerPipeClient::Start()
-{
- this->Pipe.init(*Loop, 0, static_cast<cmDebuggerPipeClient*>(this));
-
- uv_connect_t* connect = new uv_connect_t;
- connect->data = this;
- uv_pipe_connect(
- connect, Pipe, PipeName.c_str(), [](uv_connect_t* cb_connect, int status) {
- auto* conn = static_cast<cmDebuggerPipeClient*>(cb_connect->data);
- if (status >= 0) {
- conn->Connect();
- } else {
- conn->FailConnection();
- }
- delete cb_connect;
- });
-
- // Start the libuv event loop so that the pipe can connect.
- LoopThread = std::thread([this] { uv_run(Loop, UV_RUN_DEFAULT); });
-}
-
-bool cmDebuggerPipeClient::isOpen()
-{
- return IsConnected;
-}
-
-void cmDebuggerPipeClient::CloseConnection()
-{
- IsConnected = false;
-}
-
-void cmDebuggerPipeClient::Connect()
-{
- StartReading<cmDebuggerPipeClient>(Pipe);
- std::unique_lock<std::mutex> lock(Mutex);
- IsConnected = true;
- lock.unlock();
- Connected.notify_all();
-}
-
-void cmDebuggerPipeClient::FailConnection()
-{
- std::unique_lock<std::mutex> lock(Mutex);
- FailedToOpen = true;
- lock.unlock();
- Connected.notify_all();
-}
-
-} // namespace cmDebugger
diff --git a/Source/cmDebuggerPipeConnection.h b/Source/cmDebuggerPipeConnection.h
deleted file mode 100644
index 0991ff7..0000000
--- a/Source/cmDebuggerPipeConnection.h
+++ /dev/null
@@ -1,139 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
-#pragma once
-
-#include "cmConfigure.h" // IWYU pragma: keep
-
-#include <condition_variable>
-#include <cstddef>
-#include <future>
-#include <memory>
-#include <mutex>
-#include <string>
-#include <thread>
-
-#include <cm3p/cppdap/io.h>
-#include <cm3p/uv.h>
-
-#include "cmDebuggerAdapter.h"
-#include "cmUVHandlePtr.h"
-
-namespace cmDebugger {
-
-class cmDebuggerPipeBase : public dap::ReaderWriter
-{
-public:
- cmDebuggerPipeBase(std::string name);
-
- void WaitForConnection();
-
- // dap::ReaderWriter implementation
-
- void close() final;
- size_t read(void* buffer, size_t n) final;
- bool write(const void* buffer, size_t n) final;
-
-protected:
- virtual void CloseConnection(){};
- template <typename T>
- void StartReading(uv_stream_t* stream)
- {
- uv_read_start(
- stream,
- // alloc_cb
- [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
- (void)handle;
- char* rawBuffer = new char[suggested_size];
- *buf =
- uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
- },
- // read_cb
- [](uv_stream_t* readStream, ssize_t nread, const uv_buf_t* buf) {
- auto conn = static_cast<T*>(readStream->data);
- if (conn) {
- if (nread >= 0) {
- conn->BufferData(std::string(buf->base, buf->base + nread));
- } else {
- conn->close();
- }
- }
- delete[] (buf->base);
- });
- }
- void StopLoop();
-
- const std::string PipeName;
- std::thread LoopThread;
- cm::uv_loop_ptr Loop;
- cm::uv_pipe_ptr Pipe;
- std::mutex Mutex;
- std::condition_variable Connected;
- bool FailedToOpen = false;
-
-private:
- void BufferData(const std::string& data);
- void WriteInternal();
-
- cm::uv_async_ptr LoopExit;
- cm::uv_async_ptr WriteEvent;
- cm::uv_async_ptr PipeClose;
- std::string WriteBuffer;
- std::string ReadBuffer;
- std::condition_variable ReadReady;
- std::condition_variable WriteComplete;
-};
-
-class cmDebuggerPipeConnection
- : public cmDebuggerPipeBase
- , public cmDebuggerConnection
- , public std::enable_shared_from_this<cmDebuggerPipeConnection>
-{
-public:
- cmDebuggerPipeConnection(std::string name);
- ~cmDebuggerPipeConnection() override;
-
- void WaitForConnection() override
- {
- cmDebuggerPipeBase::WaitForConnection();
- }
-
- bool StartListening(std::string& errorMessage) override;
- std::shared_ptr<dap::Reader> GetReader() override;
- std::shared_ptr<dap::Writer> GetWriter() override;
-
- // dap::ReaderWriter implementation
-
- bool isOpen() override;
-
- // Used for unit test synchronization
- std::promise<void> StartedListening;
-
-private:
- void CloseConnection() override;
- void Connect(uv_stream_t* server);
-
- cm::uv_pipe_ptr ServerPipe;
- cm::uv_async_ptr ServerPipeClose;
-};
-
-class cmDebuggerPipeClient : public cmDebuggerPipeBase
-{
-public:
- using cmDebuggerPipeBase::cmDebuggerPipeBase;
- ~cmDebuggerPipeClient() override;
-
- void Start();
-
- // dap::ReaderWriter implementation
-
- bool isOpen() override;
-
-private:
- void CloseConnection() override;
- void Connect();
- void FailConnection();
-
- bool IsConnected = false;
-};
-
-} // namespace cmDebugger
diff --git a/Source/cmDebuggerPosixPipeConnection.cxx b/Source/cmDebuggerPosixPipeConnection.cxx
new file mode 100644
index 0000000..0587450
--- /dev/null
+++ b/Source/cmDebuggerPosixPipeConnection.cxx
@@ -0,0 +1,205 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerPosixPipeConnection.h"
+
+#include <cerrno>
+#include <cstring>
+#include <stdexcept>
+#include <utility>
+
+#include <unistd.h>
+
+#include <sys/socket.h>
+
+namespace cmDebugger {
+
+#ifndef _WIN32
+
+cmDebuggerPipeConnection_POSIX::cmDebuggerPipeConnection_POSIX(
+ std::string name)
+ : PipeName(std::move(name))
+{
+ addr.sun_path[0] = '\0';
+}
+
+cmDebuggerPipeConnection_POSIX::~cmDebuggerPipeConnection_POSIX()
+{
+ if (isOpen()) {
+ close();
+ }
+}
+
+bool cmDebuggerPipeConnection_POSIX::StartListening(std::string& errorMessage)
+{
+ listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (listen_fd < 0) {
+ errorMessage = "Failed to create socket: ";
+ errorMessage += strerror(errno);
+ return false;
+ }
+
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, PipeName.c_str(), sizeof(addr.sun_path));
+ addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
+ if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
+ errorMessage = "Failed to bind name '";
+ errorMessage += addr.sun_path;
+ errorMessage += "' to socket: ";
+ errorMessage += strerror(errno);
+ close_listen();
+ return false;
+ }
+
+ if (listen(listen_fd, 1) == -1) {
+ errorMessage = "Failed to listen on socket: ";
+ errorMessage += strerror(errno);
+ close_listen();
+ return false;
+ }
+
+ StartedListening.set_value();
+ return true;
+}
+
+std::shared_ptr<dap::Reader> cmDebuggerPipeConnection_POSIX::GetReader()
+{
+ return std::static_pointer_cast<dap::Reader>(shared_from_this());
+}
+
+std::shared_ptr<dap::Writer> cmDebuggerPipeConnection_POSIX::GetWriter()
+{
+ return std::static_pointer_cast<dap::Writer>(shared_from_this());
+}
+
+bool cmDebuggerPipeConnection_POSIX::isOpen()
+{
+ return rw_pipe >= 0;
+}
+
+void cmDebuggerPipeConnection_POSIX::close()
+{
+ close_listen();
+ ::close(rw_pipe);
+ rw_pipe = -1;
+}
+
+void cmDebuggerPipeConnection_POSIX::close_listen()
+{
+ if (strlen(addr.sun_path) > 0) {
+ unlink(addr.sun_path);
+ addr.sun_path[0] = '\0';
+ }
+ ::close(listen_fd);
+ listen_fd = -1;
+}
+
+void cmDebuggerPipeConnection_POSIX::WaitForConnection()
+{
+ sockaddr_un laddr;
+ socklen_t len = sizeof(laddr);
+ rw_pipe = accept(listen_fd, (sockaddr*)&laddr, &len);
+ if (rw_pipe < 0) {
+ close();
+ return;
+ }
+
+ close_listen(); // no longer need the listen resources
+}
+
+size_t cmDebuggerPipeConnection_POSIX::read(void* buffer, size_t n)
+{
+ size_t result = 0;
+ if (rw_pipe >= 0) {
+ result = ::read(rw_pipe, buffer, n);
+ if (result == 0) {
+ close();
+ }
+ }
+
+ return result;
+}
+
+bool cmDebuggerPipeConnection_POSIX::write(void const* buffer, size_t n)
+{
+ bool result = false;
+ if (rw_pipe >= 0) {
+ result = ::write(rw_pipe, buffer, n) >= 0;
+ if (!result) {
+ close();
+ }
+ }
+
+ return result;
+}
+
+cmDebuggerPipeClient_POSIX::cmDebuggerPipeClient_POSIX(std::string name)
+ : PipeName(std::move(name))
+{
+}
+
+cmDebuggerPipeClient_POSIX::~cmDebuggerPipeClient_POSIX()
+{
+ close();
+}
+
+void cmDebuggerPipeClient_POSIX::WaitForConnection()
+{
+ rw_pipe = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (rw_pipe < 0) {
+ throw std::runtime_error(std::string("Failed to create socket: ") +
+ strerror(errno));
+ }
+
+ sockaddr_un addr;
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, PipeName.c_str(), sizeof(addr.sun_path));
+ addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
+ if (connect(rw_pipe, (sockaddr*)&addr, sizeof(addr)) == -1) {
+ close();
+ throw std::runtime_error(
+ std::string("Failed to connect path to socket: ") + strerror(errno));
+ }
+}
+
+bool cmDebuggerPipeClient_POSIX::isOpen()
+{
+ return rw_pipe >= 0;
+}
+
+void cmDebuggerPipeClient_POSIX::close()
+{
+ if (isOpen()) {
+ ::close(rw_pipe);
+ rw_pipe = -1;
+ }
+}
+
+size_t cmDebuggerPipeClient_POSIX::read(void* buffer, size_t n)
+{
+ int count = 0;
+ if (isOpen()) {
+ count = static_cast<int>(::read(rw_pipe, buffer, n));
+ if (count == 0) {
+ close();
+ }
+ }
+
+ return count;
+}
+
+bool cmDebuggerPipeClient_POSIX::write(void const* buffer, size_t n)
+{
+ int count = 0;
+ if (isOpen()) {
+ count = static_cast<int>(::write(rw_pipe, buffer, n));
+ if (count < 0) {
+ close();
+ }
+ }
+
+ return count > 0;
+}
+
+#endif // !_WIN32
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerPosixPipeConnection.h b/Source/cmDebuggerPosixPipeConnection.h
new file mode 100644
index 0000000..42642fa
--- /dev/null
+++ b/Source/cmDebuggerPosixPipeConnection.h
@@ -0,0 +1,81 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <string>
+
+#include <cm3p/cppdap/io.h>
+
+#include <sys/un.h>
+
+#include "cmDebuggerAdapter.h"
+
+namespace cmDebugger {
+
+#ifndef _WIN32
+
+class cmDebuggerPipeConnection_POSIX
+ : public dap::ReaderWriter
+ , public cmDebuggerConnection
+ , public std::enable_shared_from_this<cmDebuggerPipeConnection_POSIX>
+{
+public:
+ cmDebuggerPipeConnection_POSIX(std::string name);
+ ~cmDebuggerPipeConnection_POSIX() override;
+
+ void WaitForConnection() override;
+
+ bool StartListening(std::string& errorMessage) override;
+ std::shared_ptr<dap::Reader> GetReader() override;
+ std::shared_ptr<dap::Writer> GetWriter() override;
+
+ // dap::ReaderWriter implementation
+
+ bool isOpen() override;
+ void close() override;
+ size_t read(void* buffer, size_t n) override;
+ bool write(void const* buffer, size_t n) override;
+
+ // Used for unit test synchronization
+ std::promise<void> StartedListening;
+
+private:
+ void close_listen(); // release listen resources
+
+ std::string const PipeName;
+ sockaddr_un addr;
+ int listen_fd = -1; // listen fd
+ int rw_pipe = -1; // rw fd
+};
+
+using cmDebuggerPipeConnection = cmDebuggerPipeConnection_POSIX;
+
+class cmDebuggerPipeClient_POSIX
+ : public dap::ReaderWriter
+ , public std::enable_shared_from_this<cmDebuggerPipeClient_POSIX>
+{
+public:
+ cmDebuggerPipeClient_POSIX(std::string name);
+ ~cmDebuggerPipeClient_POSIX() override;
+ void WaitForConnection();
+
+ bool isOpen() override;
+ void close() override;
+ size_t read(void* buffer, size_t n) override;
+ bool write(void const* buffer, size_t n) override;
+
+private:
+ std::string const PipeName;
+ int rw_pipe = -1;
+};
+
+using cmDebuggerPipeClient = cmDebuggerPipeClient_POSIX;
+
+#endif // !_WIN32
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerWindowsPipeConnection.cxx b/Source/cmDebuggerWindowsPipeConnection.cxx
new file mode 100644
index 0000000..1c6a2a7
--- /dev/null
+++ b/Source/cmDebuggerWindowsPipeConnection.cxx
@@ -0,0 +1,272 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmDebuggerWindowsPipeConnection.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+#include <utility>
+
+namespace cmDebugger {
+
+#ifdef _WIN32
+
+DuplexPipe_WIN32::DuplexPipe_WIN32(HANDLE pipe)
+ : hPipe(pipe)
+{
+ readOp.Offset = readOp.OffsetHigh = 0;
+ readOp.hEvent = CreateEvent(NULL, true, false, NULL);
+ writeOp.Offset = readOp.OffsetHigh = 0;
+ writeOp.hEvent = CreateEvent(NULL, true, false, NULL);
+}
+
+DuplexPipe_WIN32::~DuplexPipe_WIN32()
+{
+ close();
+}
+
+size_t DuplexPipe_WIN32::read(void* buffer, size_t n)
+{
+ if (hPipe != INVALID_HANDLE_VALUE) {
+ readOp.Offset = readOp.OffsetHigh = 0;
+ ResetEvent(readOp.hEvent);
+ auto r = ReadFile(hPipe, buffer, n, NULL, &readOp);
+ auto err = GetLastError();
+ if (r || err == ERROR_IO_PENDING) {
+ DWORD nRead = 0;
+ if (GetOverlappedResult(hPipe, &readOp, &nRead, true)) {
+ return nRead;
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool DuplexPipe_WIN32::write(void const* buffer, size_t n)
+{
+ if (hPipe != INVALID_HANDLE_VALUE) {
+ writeOp.Offset = writeOp.OffsetHigh = 0;
+ ResetEvent(writeOp.hEvent);
+ auto w = WriteFile(hPipe, buffer, n, NULL, &writeOp);
+ auto err = GetLastError();
+ if (w || err == ERROR_IO_PENDING) {
+ DWORD nWrite = 0;
+ if (GetOverlappedResult(hPipe, &writeOp, &nWrite, true)) {
+ return n == nWrite;
+ }
+ }
+ }
+
+ return false;
+}
+
+void DuplexPipe_WIN32::close()
+{
+ CloseHandle(hPipe);
+ hPipe = INVALID_HANDLE_VALUE;
+ CloseHandle(readOp.hEvent);
+ CloseHandle(writeOp.hEvent);
+ readOp.hEvent = writeOp.hEvent = INVALID_HANDLE_VALUE;
+}
+
+bool DuplexPipe_WIN32::WaitForConnection()
+{
+ auto connect = ConnectNamedPipe(hPipe, &readOp);
+ auto err = GetLastError();
+ if (!connect && err == ERROR_IO_PENDING) {
+ DWORD ignored;
+ if (GetOverlappedResult(hPipe, &readOp, &ignored, true)) {
+ return true;
+ }
+ }
+
+ return connect || err == ERROR_PIPE_CONNECTED;
+}
+
+cmDebuggerPipeConnection_WIN32::cmDebuggerPipeConnection_WIN32(
+ std::string name)
+ : PipeName(std::move(name))
+ , pipes(nullptr)
+{
+}
+
+cmDebuggerPipeConnection_WIN32::~cmDebuggerPipeConnection_WIN32()
+{
+ if (isOpen()) {
+ pipes = nullptr;
+ }
+}
+
+bool cmDebuggerPipeConnection_WIN32::StartListening(std::string& errorMessage)
+{
+ bool result = true;
+
+ auto hPipe = CreateNamedPipeA(
+ PipeName.c_str(),
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, 1,
+ 1024 * 16, 1024 * 16, NMPWAIT_USE_DEFAULT_WAIT, NULL);
+
+ if (hPipe == INVALID_HANDLE_VALUE) {
+ auto err = GetLastError();
+ errorMessage = GetErrorMessage(err);
+ result = false;
+ }
+
+ if (result) {
+ pipes = std::make_unique<DuplexPipe_WIN32>(hPipe);
+ }
+
+ StartedListening.set_value();
+ return result;
+}
+
+std::string cmDebuggerPipeConnection_WIN32::GetErrorMessage(DWORD errorCode)
+{
+ LPSTR message = nullptr;
+ DWORD size = FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPSTR)&message, 0, nullptr);
+ std::string errorMessage = "Internal Error with " + this->PipeName + ": " +
+ std::string(message, size);
+ LocalFree(message);
+ return errorMessage;
+}
+
+std::shared_ptr<dap::Reader> cmDebuggerPipeConnection_WIN32::GetReader()
+{
+ return std::static_pointer_cast<dap::Reader>(shared_from_this());
+}
+
+std::shared_ptr<dap::Writer> cmDebuggerPipeConnection_WIN32::GetWriter()
+{
+ return std::static_pointer_cast<dap::Writer>(shared_from_this());
+}
+
+bool cmDebuggerPipeConnection_WIN32::isOpen()
+{
+ return pipes != nullptr;
+}
+
+void cmDebuggerPipeConnection_WIN32::close()
+{
+ CloseConnection();
+}
+
+void cmDebuggerPipeConnection_WIN32::CloseConnection()
+{
+ if (isOpen()) {
+ pipes->close();
+ pipes = nullptr;
+ }
+}
+
+void cmDebuggerPipeConnection_WIN32::WaitForConnection()
+{
+ if (!isOpen()) {
+ return;
+ }
+
+ if (pipes->WaitForConnection()) {
+ return;
+ }
+
+ CloseConnection();
+}
+
+size_t cmDebuggerPipeConnection_WIN32::read(void* buffer, size_t n)
+{
+ size_t result = 0;
+ if (isOpen()) {
+ result = pipes->read(buffer, n);
+ if (result == 0) {
+ CloseConnection();
+ }
+ }
+
+ return result;
+}
+
+bool cmDebuggerPipeConnection_WIN32::write(void const* buffer, size_t n)
+{
+ bool result = false;
+ if (isOpen()) {
+ result = pipes->write(buffer, n);
+ if (!result) {
+ CloseConnection();
+ }
+ }
+
+ return result;
+}
+
+cmDebuggerPipeClient_WIN32::cmDebuggerPipeClient_WIN32(std::string name)
+ : PipeName(std::move(name))
+{
+}
+
+cmDebuggerPipeClient_WIN32::~cmDebuggerPipeClient_WIN32()
+{
+ close();
+}
+
+void cmDebuggerPipeClient_WIN32::WaitForConnection()
+{
+ if (!isOpen()) {
+ auto hPipe = CreateFileA(PipeName.c_str(), GENERIC_READ | GENERIC_WRITE, 0,
+ NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+ if (hPipe == INVALID_HANDLE_VALUE) {
+ auto err = GetLastError();
+ throw std::runtime_error("CreateFile failed with " + err);
+ }
+
+ pipes = std::make_unique<DuplexPipe_WIN32>(hPipe);
+ }
+}
+
+bool cmDebuggerPipeClient_WIN32::isOpen()
+{
+ return pipes != nullptr;
+}
+
+void cmDebuggerPipeClient_WIN32::close()
+{
+ if (isOpen()) {
+ pipes->close();
+ pipes = nullptr;
+ }
+}
+
+size_t cmDebuggerPipeClient_WIN32::read(void* buffer, size_t n)
+{
+ size_t result = 0;
+ if (isOpen()) {
+ result = pipes->read(buffer, n);
+ if (result == 0) {
+ close();
+ }
+ }
+
+ return result;
+}
+
+bool cmDebuggerPipeClient_WIN32::write(void const* buffer, size_t n)
+{
+ bool result = false;
+ if (isOpen()) {
+ result = pipes->write(buffer, n);
+ if (!result) {
+ close();
+ }
+ }
+
+ return result;
+}
+
+#endif // _WIN32
+
+} // namespace cmDebugger
diff --git a/Source/cmDebuggerWindowsPipeConnection.h b/Source/cmDebuggerWindowsPipeConnection.h
new file mode 100644
index 0000000..88ed1de
--- /dev/null
+++ b/Source/cmDebuggerWindowsPipeConnection.h
@@ -0,0 +1,101 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <condition_variable>
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <windows.h>
+
+#include <cm3p/cppdap/io.h>
+
+#include "cmDebuggerAdapter.h"
+
+namespace cmDebugger {
+
+#ifdef _WIN32
+
+class DuplexPipe_WIN32
+{
+public:
+ DuplexPipe_WIN32(HANDLE read);
+ ~DuplexPipe_WIN32();
+
+ void close();
+ size_t read(void* buffer, size_t n);
+ bool write(void const* buffer, size_t n);
+
+ bool WaitForConnection();
+
+private:
+ HANDLE hPipe;
+ OVERLAPPED readOp;
+ OVERLAPPED writeOp;
+};
+
+class cmDebuggerPipeConnection_WIN32
+ : public dap::ReaderWriter
+ , public cmDebuggerConnection
+ , public std::enable_shared_from_this<cmDebuggerPipeConnection_WIN32>
+{
+public:
+ cmDebuggerPipeConnection_WIN32(std::string name);
+ ~cmDebuggerPipeConnection_WIN32() override;
+
+ void WaitForConnection() override;
+
+ bool StartListening(std::string& errorMessage) override;
+ std::shared_ptr<dap::Reader> GetReader() override;
+ std::shared_ptr<dap::Writer> GetWriter() override;
+
+ // dap::ReaderWriter implementation
+
+ bool isOpen() override;
+ void close() override;
+ size_t read(void* buffer, size_t n) override;
+ bool write(void const* buffer, size_t n) override;
+
+ // Used for unit test synchronization
+ std::promise<void> StartedListening;
+
+private:
+ void CloseConnection();
+ std::string GetErrorMessage(DWORD errorCode);
+
+ std::string const PipeName;
+ std::unique_ptr<DuplexPipe_WIN32> pipes;
+};
+
+using cmDebuggerPipeConnection = cmDebuggerPipeConnection_WIN32;
+
+class cmDebuggerPipeClient_WIN32
+ : public dap::ReaderWriter
+ , public std::enable_shared_from_this<cmDebuggerPipeClient_WIN32>
+{
+public:
+ cmDebuggerPipeClient_WIN32(std::string name);
+ ~cmDebuggerPipeClient_WIN32();
+ void WaitForConnection();
+
+ bool isOpen() override;
+ void close() override;
+ size_t read(void* buffer, size_t n) override;
+ bool write(void const* buffer, size_t n) override;
+
+private:
+ std::string const PipeName;
+ std::unique_ptr<DuplexPipe_WIN32> pipes;
+};
+
+using cmDebuggerPipeClient = cmDebuggerPipeClient_WIN32;
+
+#endif // _WIN32
+
+} // namespace cmDebugger
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index b8ebca5..ab4a7e6 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -40,7 +40,11 @@
#include "cmCommands.h"
#ifdef CMake_ENABLE_DEBUGGER
# include "cmDebuggerAdapter.h"
-# include "cmDebuggerPipeConnection.h"
+# ifdef _WIN32
+# include "cmDebuggerWindowsPipeConnection.h"
+# else //!_WIN32
+# include "cmDebuggerPosixPipeConnection.h"
+# endif //_WIN32
#endif
#include "cmDocumentation.h"
#include "cmDocumentationEntry.h"
diff --git a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
index 643661d..c0f2e9b 100644
--- a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
+++ b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
@@ -19,13 +19,15 @@
#include <cm3p/cppdap/types.h>
#include "cmDebuggerAdapter.h"
-#include "cmDebuggerPipeConnection.h"
#include "cmDebuggerProtocol.h"
#include "cmVersionConfig.h"
#ifdef _WIN32
# include "cmCryptoHash.h"
+# include "cmDebuggerWindowsPipeConnection.h"
# include "cmSystemTools.h"
+#else
+# include "cmDebuggerPosixPipeConnection.h"
#endif
#include "testCommon.h"
@@ -128,7 +130,7 @@ bool testProtocolWithPipes()
auto client2Debugger =
std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
- client2Debugger->Start();
+
client2Debugger->WaitForConnection();
client->bind(client2Debugger, client2Debugger);
diff --git a/Tests/CMakeLib/testDebuggerNamedPipe.cxx b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
index d2b0728..41761eb 100644
--- a/Tests/CMakeLib/testDebuggerNamedPipe.cxx
+++ b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
@@ -16,7 +16,12 @@
#include "cmsys/RegularExpression.hxx"
-#include "cmDebuggerPipeConnection.h"
+#ifdef _WIN32
+# include "cmDebuggerWindowsPipeConnection.h"
+#else
+# include "cmDebuggerPosixPipeConnection.h"
+#endif
+
#include "cmSystemTools.h"
#ifdef _WIN32
@@ -104,7 +109,7 @@ int runTest(int argc, char* argv[])
attempt++;
try {
client = std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
- client->Start();
+
client->WaitForConnection();
std::cout << "cmDebuggerPipeClient connected.\n";
break;