[Lldb-commits] [lldb] [lldb] Add SBProtocolServer to start protocol servers programmatically (PR #209923)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 15 16:56:00 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Jonas Devlieghere (JDevlieghere)
<details>
<summary>Changes</summary>
Starting a protocol server (such as MCP, and in the future potentially DAP) is only possible from the command line today, via `protocol-server start`. Add an SB API so an embedder can start one in its own process and learn where to connect.
SBProtocolServer wraps ProtocolServer::GetOrCreate/Start/Stop and exposes the listening connection URI. This lets a tool host the engine's protocol server in-process and talk to it as a normal client, reusing the server's tools rather than reimplementing them.
Assisted-by: Claude
---
Full diff: https://github.com/llvm/llvm-project/pull/209923.diff
8 Files Affected:
- (modified) lldb/bindings/interfaces.swig (+1)
- (modified) lldb/include/lldb/API/LLDB.h (+1)
- (modified) lldb/include/lldb/API/SBDefines.h (+2-1)
- (added) lldb/include/lldb/API/SBProtocolServer.h (+54)
- (modified) lldb/source/API/CMakeLists.txt (+1)
- (added) lldb/source/API/SBProtocolServer.cpp (+160)
- (modified) lldb/unittests/API/CMakeLists.txt (+1)
- (added) lldb/unittests/API/SBProtocolServerTest.cpp (+128)
``````````diff
diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig
index 5cdddd5136ac2..6f09c48fba3ab 100644
--- a/lldb/bindings/interfaces.swig
+++ b/lldb/bindings/interfaces.swig
@@ -140,6 +140,7 @@
%include "lldb/API/SBProcessInfo.h"
%include "lldb/API/SBProcessInfoList.h"
%include "lldb/API/SBProgress.h"
+%include "lldb/API/SBProtocolServer.h"
%include "lldb/API/SBQueue.h"
%include "lldb/API/SBQueueItem.h"
%include "lldb/API/SBReproducer.h"
diff --git a/lldb/include/lldb/API/LLDB.h b/lldb/include/lldb/API/LLDB.h
index 6ac35bb4a364b..af47d12dbc404 100644
--- a/lldb/include/lldb/API/LLDB.h
+++ b/lldb/include/lldb/API/LLDB.h
@@ -57,6 +57,7 @@
#include "lldb/API/SBProcessInfo.h"
#include "lldb/API/SBProcessInfoList.h"
#include "lldb/API/SBProgress.h"
+#include "lldb/API/SBProtocolServer.h"
#include "lldb/API/SBQueue.h"
#include "lldb/API/SBQueueItem.h"
#include "lldb/API/SBReproducer.h"
diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h
index 5fcc685050c0b..7ec8e56067aa6 100644
--- a/lldb/include/lldb/API/SBDefines.h
+++ b/lldb/include/lldb/API/SBDefines.h
@@ -98,6 +98,7 @@ class LLDB_API SBProcess;
class LLDB_API SBProcessInfo;
class LLDB_API SBProcessInfoList;
class LLDB_API SBProgress;
+class LLDB_API SBProtocolServer;
class LLDB_API SBQueue;
class LLDB_API SBQueueItem;
class LLDB_API SBReplayOptions;
@@ -152,6 +153,6 @@ typedef lldb::CommandReturnObjectCallbackResult (*SBCommandPrintCallback)(
typedef lldb::SBError (*SBPlatformLocateModuleCallback)(
void *baton, const lldb::SBModuleSpec &module_spec,
lldb::SBFileSpec &module_file_spec, lldb::SBFileSpec &symbol_file_spec);
-}
+} // namespace lldb
#endif // LLDB_API_SBDEFINES_H
diff --git a/lldb/include/lldb/API/SBProtocolServer.h b/lldb/include/lldb/API/SBProtocolServer.h
new file mode 100644
index 0000000000000..625a71856aa9d
--- /dev/null
+++ b/lldb/include/lldb/API/SBProtocolServer.h
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_API_SBPROTOCOLSERVER_H
+#define LLDB_API_SBPROTOCOLSERVER_H
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb_private {
+class SBProtocolServerImpl;
+}
+
+namespace lldb {
+
+/// A server that speaks a debugging protocol.
+///
+/// Protocol servers are shared across debuggers. Creating a server for a
+/// protocol that already has one hands back the existing server.
+class LLDB_API SBProtocolServer {
+public:
+ SBProtocolServer();
+ SBProtocolServer(const SBProtocolServer &rhs);
+ SBProtocolServer &operator=(const SBProtocolServer &rhs);
+ ~SBProtocolServer();
+
+ /// Returns the protocol server for \p protocol, creating it if this process
+ /// does not already have one. On failure \p error is set.
+ static SBProtocolServer Create(const char *protocol, lldb::SBError &error);
+
+ explicit operator bool() const;
+ bool IsValid() const;
+
+ /// Starts listening for clients on \p connection_uri, which must be an
+ /// accepting URI such as "listen://[host]:port" or "accept:///path".
+ lldb::SBError Start(const char *connection_uri);
+
+ lldb::SBError Stop();
+
+ /// Returns the URI clients can connect to, valid after a successful Start,
+ /// or nullptr if the server is not listening.
+ const char *GetConnectionURI();
+
+private:
+ std::unique_ptr<lldb_private::SBProtocolServerImpl> m_opaque_up;
+};
+
+} // namespace lldb
+
+#endif
diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt
index 15c52aab41368..83ecb428d8ea4 100644
--- a/lldb/source/API/CMakeLists.txt
+++ b/lldb/source/API/CMakeLists.txt
@@ -81,6 +81,7 @@ add_lldb_library(liblldb SHARED ${option_framework}
SBProcessInfo.cpp
SBProcessInfoList.cpp
SBProgress.cpp
+ SBProtocolServer.cpp
SBQueue.cpp
SBQueueItem.cpp
SBReproducer.cpp
diff --git a/lldb/source/API/SBProtocolServer.cpp b/lldb/source/API/SBProtocolServer.cpp
new file mode 100644
index 0000000000000..7b3796599c082
--- /dev/null
+++ b/lldb/source/API/SBProtocolServer.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/API/SBProtocolServer.h"
+#include "lldb/API/SBError.h"
+#include "lldb/Core/ProtocolServer.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/Utility/Instrumentation.h"
+#include "lldb/Utility/UriParser.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+
+#include <memory>
+
+using namespace lldb;
+using namespace lldb_private;
+
+/// PIMPL backing SBProtocolServer.
+class lldb_private::SBProtocolServerImpl {
+public:
+ ProtocolServer *server = nullptr;
+};
+
+SBProtocolServer::SBProtocolServer()
+ : m_opaque_up(std::make_unique<SBProtocolServerImpl>()) {
+ LLDB_INSTRUMENT_VA(this);
+}
+
+SBProtocolServer::SBProtocolServer(const SBProtocolServer &rhs)
+ : m_opaque_up(std::make_unique<SBProtocolServerImpl>(*rhs.m_opaque_up)) {
+ LLDB_INSTRUMENT_VA(this, rhs);
+}
+
+SBProtocolServer &SBProtocolServer::operator=(const SBProtocolServer &rhs) {
+ LLDB_INSTRUMENT_VA(this, rhs);
+
+ if (this != &rhs)
+ *m_opaque_up = *rhs.m_opaque_up;
+ return *this;
+}
+
+SBProtocolServer::~SBProtocolServer() = default;
+
+SBProtocolServer SBProtocolServer::Create(const char *protocol,
+ SBError &error) {
+ LLDB_INSTRUMENT_VA(protocol, error);
+
+ SBProtocolServer server;
+ error.Clear();
+
+ if (!protocol || !protocol[0]) {
+ error.SetErrorString("no protocol specified");
+ return server;
+ }
+
+ ProtocolServer *protocol_server = ProtocolServer::GetOrCreate(protocol);
+ if (!protocol_server) {
+ error.SetErrorStringWithFormat(
+ "unsupported protocol: %s. Supported protocols are: %s", protocol,
+ llvm::join(ProtocolServer::GetSupportedProtocols(), ", ").c_str());
+ return server;
+ }
+
+ server.m_opaque_up->server = protocol_server;
+ return server;
+}
+
+SBProtocolServer::operator bool() const {
+ LLDB_INSTRUMENT_VA(this);
+
+ return m_opaque_up->server != nullptr;
+}
+
+bool SBProtocolServer::IsValid() const {
+ LLDB_INSTRUMENT_VA(this);
+
+ return m_opaque_up->server != nullptr;
+}
+
+SBError SBProtocolServer::Start(const char *connection_uri) {
+ LLDB_INSTRUMENT_VA(this, connection_uri);
+
+ SBError error;
+ if (!m_opaque_up->server) {
+ error.SetErrorString("invalid protocol server");
+ return error;
+ }
+
+ const char *connection_error =
+ "unsupported connection specifier, expected 'accept:///path' or "
+ "'listen://[host]:port'";
+ std::optional<URI> uri = URI::Parse(connection_uri ? connection_uri : "");
+ if (!uri) {
+ error.SetErrorString(connection_error);
+ return error;
+ }
+
+ std::optional<Socket::ProtocolModePair> protocol_and_mode =
+ Socket::GetProtocolAndMode(uri->scheme);
+ if (!protocol_and_mode || protocol_and_mode->second != Socket::ModeAccept) {
+ error.SetErrorString(connection_error);
+ return error;
+ }
+
+ ProtocolServer::Connection connection;
+ connection.protocol = protocol_and_mode->first;
+ if (connection.protocol == Socket::SocketProtocol::ProtocolUnixDomain)
+ connection.name = uri->path;
+ else
+ connection.name =
+ llvm::formatv("[{0}]:{1}",
+ uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
+ uri->port.value_or(0))
+ .str();
+
+ if (llvm::Error err = m_opaque_up->server->Start(connection))
+ error.SetErrorString(llvm::toString(std::move(err)).c_str());
+
+ return error;
+}
+
+SBError SBProtocolServer::Stop() {
+ LLDB_INSTRUMENT_VA(this);
+
+ SBError error;
+ if (!m_opaque_up->server) {
+ error.SetErrorString("invalid protocol server");
+ return error;
+ }
+
+ if (llvm::Error err = m_opaque_up->server->Stop())
+ error.SetErrorString(llvm::toString(std::move(err)).c_str());
+
+ return error;
+}
+
+const char *SBProtocolServer::GetConnectionURI() {
+ LLDB_INSTRUMENT_VA(this);
+
+ if (!m_opaque_up->server)
+ return nullptr;
+
+ Socket *socket = m_opaque_up->server->GetSocket();
+ if (!socket)
+ return nullptr;
+
+ std::vector<std::string> uris = socket->GetListeningConnectionURI();
+ if (uris.empty())
+ return nullptr;
+
+ // A listening socket may report several equivalent URIs (e.g. IPv6 and
+ // IPv4 loopback). Return the first, interned so the pointer stays valid.
+ return ConstString(uris.front()).GetCString();
+}
diff --git a/lldb/unittests/API/CMakeLists.txt b/lldb/unittests/API/CMakeLists.txt
index b86054fb353f7..a7db51aea4994 100644
--- a/lldb/unittests/API/CMakeLists.txt
+++ b/lldb/unittests/API/CMakeLists.txt
@@ -3,6 +3,7 @@ add_lldb_unittest(APITests
SBLineEntryTest.cpp
SBMutexTest.cpp
SBBreakpointClearConditionTest.cpp
+ SBProtocolServerTest.cpp
SBAPITEST
diff --git a/lldb/unittests/API/SBProtocolServerTest.cpp b/lldb/unittests/API/SBProtocolServerTest.cpp
new file mode 100644
index 0000000000000..1191c6761e549
--- /dev/null
+++ b/lldb/unittests/API/SBProtocolServerTest.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Use the umbrella header for -Wdocumentation.
+#include "lldb/API/LLDB.h"
+
+#include "TestingSupport/SubsystemRAII.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBError.h"
+#include "lldb/API/SBProtocolServer.h"
+#include "gtest/gtest.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+class SBProtocolServerTest : public testing::Test {
+protected:
+ void SetUp() override {
+ debugger = SBDebugger::Create(/*source_init_files=*/false);
+ }
+
+ void TearDown() override { SBDebugger::Destroy(debugger); }
+
+ SubsystemRAII<lldb::SBDebugger> subsystems;
+ SBDebugger debugger;
+};
+
+TEST_F(SBProtocolServerTest, DefaultConstructedIsInvalid) {
+ SBProtocolServer server;
+ EXPECT_FALSE(server.IsValid());
+ EXPECT_FALSE(static_cast<bool>(server));
+}
+
+TEST_F(SBProtocolServerTest, CreateMCP) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+ ASSERT_TRUE(error.Success()) << error.GetCString();
+ EXPECT_TRUE(server.IsValid());
+ EXPECT_TRUE(static_cast<bool>(server));
+ // No socket is bound until Start, so there is no connection URI yet.
+ EXPECT_EQ(server.GetConnectionURI(), nullptr);
+}
+
+TEST_F(SBProtocolServerTest, CreateUnsupportedProtocol) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("bogus", error);
+ EXPECT_TRUE(error.Fail());
+ EXPECT_FALSE(server.IsValid());
+}
+
+TEST_F(SBProtocolServerTest, CreateNullProtocol) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create(nullptr, error);
+ EXPECT_TRUE(error.Fail());
+ EXPECT_FALSE(server.IsValid());
+}
+
+TEST_F(SBProtocolServerTest, CreateEmptyProtocol) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("", error);
+ EXPECT_TRUE(error.Fail());
+ EXPECT_FALSE(server.IsValid());
+}
+
+TEST_F(SBProtocolServerTest, StartAndStop) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+ ASSERT_TRUE(server.IsValid());
+
+ // Bind an ephemeral port on the loopback interface so the test never touches
+ // an external network.
+ SBError start_error = server.Start("listen://[localhost]:0");
+ ASSERT_TRUE(start_error.Success()) << start_error.GetCString();
+
+ // Once listening, the server reports the concrete URI a client can connect
+ // to (the ephemeral port resolved above).
+ EXPECT_NE(server.GetConnectionURI(), nullptr);
+
+ EXPECT_TRUE(server.Stop().Success());
+}
+
+TEST_F(SBProtocolServerTest, StartInvalidServer) {
+ // A default-constructed server has no backing protocol server, so Start fails
+ // before the connection URI is ever examined.
+ SBProtocolServer server;
+ EXPECT_TRUE(server.Start("").Fail());
+}
+
+TEST_F(SBProtocolServerTest, StopNotRunning) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+ ASSERT_TRUE(server.IsValid());
+ // Stopping a server that was never started is an error.
+ EXPECT_TRUE(server.Stop().Fail());
+}
+
+TEST_F(SBProtocolServerTest, StartRejectsNonAcceptingURI) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+ ASSERT_TRUE(server.IsValid());
+ // A connecting URI (not an accepting one) is rejected before any socket work.
+ EXPECT_TRUE(server.Start("connect://localhost:1234").Fail());
+ EXPECT_TRUE(server.Start("not a uri").Fail());
+}
+
+TEST_F(SBProtocolServerTest, CopyReferencesSameServer) {
+ SBError error;
+ SBProtocolServer server = SBProtocolServer::Create("MCP", error);
+ ASSERT_TRUE(server.IsValid());
+
+ // The protocol server is owned by the engine, not by SBProtocolServer, so a
+ // copy just references the same server and the source stays valid.
+ SBProtocolServer copy(server);
+ EXPECT_TRUE(copy.IsValid());
+ EXPECT_TRUE(server.IsValid());
+
+ // Copy assignment behaves the same way.
+ SBProtocolServer assigned;
+ EXPECT_FALSE(assigned.IsValid());
+ assigned = server;
+ EXPECT_TRUE(assigned.IsValid());
+ EXPECT_TRUE(server.IsValid());
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/209923
More information about the lldb-commits
mailing list