[Lldb-commits] [lldb] [lldb] Add accelerator plugin connection support (PR #201449)

satyanarayana reddy janga via lldb-commits lldb-commits at lists.llvm.org
Tue Jun 9 05:21:36 PDT 2026


https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/201449

>From e8b92985bebddc0d6001a671e922da5be7f96337 Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Sat, 6 Jun 2026 08:25:45 -0700
Subject: [PATCH 1/2] [lldb] Add connection info to the accelerator plugin
 protocol

Add AcceleratorConnectionInfo, describing how the client should create a
new target and reverse-connect to a separate GDB server that serves an
accelerator's state (e.g. a GPU debug stub). It carries the required
connect URL plus an optional exe path, triple, and a synchronous flag.

A connect_info field is added to AcceleratorActions so a plugin can ask
the client to establish such a connection alongside (or instead of)
setting breakpoints. This is the wire-format foundation; the client-side
handling that acts on connect_info comes in a follow-up.

Adds JSON serialization, unit tests, and documents the new fields in the
jAcceleratorPluginInitialize packet.
---
 lldb/docs/resources/lldbgdbremote.md          | 15 +++++-
 .../Utility/AcceleratorGDBRemotePackets.h     | 25 ++++++++++
 .../Utility/AcceleratorGDBRemotePackets.cpp   | 25 +++++++++-
 .../AcceleratorGDBRemotePacketsTest.cpp       | 50 +++++++++++++++++++
 4 files changed, 111 insertions(+), 4 deletions(-)

diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index ef42ebd6ae41c..7dbf5dbd39311 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2774,9 +2774,20 @@ packet when one is hit. Each breakpoint object has the following fields:
 Exactly one of `by_name` or `by_address` must be provided for each
 breakpoint.
 
+An `accelerator_action` may also include a `connect_info` object asking the
+client to create a new target and connect to a separate GDB server that
+serves the accelerator's state (for example a GPU debug stub). It has the
+following fields:
+
+| Key             | Type   | Description |
+|-----------------|--------|-------------|
+| `connect_url`   | string | Connection URL to connect to, as used by `process connect <url>`. |
+| `exe_path`      | string | Optional path to the executable to use when creating the accelerator target. If omitted, an empty target is created. |
+| `triple`        | string | Optional target triple to use as the architecture for the accelerator target. |
+| `synchronous`   | bool   | If true, the client waits for the accelerator process to finish initializing before continuing. |
+
 In future patches, each `accelerator_action` will include additional fields
-such as connection info for secondary debug sessions and synchronization
-options.
+such as synchronization options for the accelerator process.
 
 **Priority To Implement:** Required for hardware accelerator debugging
 support. Not needed for non-hardware-accelerator debugging.
diff --git a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
index 9ba36fc540a52..601bacef1ee12 100644
--- a/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
+++ b/lldb/include/lldb/Utility/AcceleratorGDBRemotePackets.h
@@ -85,6 +85,28 @@ bool fromJSON(const llvm::json::Value &value,
               AcceleratorBreakpointHitArgs &data, llvm::json::Path path);
 llvm::json::Value toJSON(const AcceleratorBreakpointHitArgs &data);
 
+/// Information the client needs to create a reverse connection to an
+/// accelerator GDB server (e.g. a separate process serving the accelerator's
+/// register and memory state). When an AcceleratorActions carries this, the
+/// client creates a new target and connects to \a connect_url.
+struct AcceleratorConnectionInfo {
+  /// Connection URL the client should connect to (as in "process connect
+  /// <url>"). Required; the remaining fields are optional.
+  std::string connect_url;
+  /// Path to the executable to use when creating the accelerator target. If
+  /// not set, an empty target is created.
+  std::optional<std::string> exe_path;
+  /// Target triple to use as the architecture for the accelerator target.
+  std::optional<std::string> triple;
+  /// If true, the client waits for the accelerator process to finish
+  /// initializing before continuing.
+  bool synchronous = false;
+};
+
+bool fromJSON(const llvm::json::Value &value, AcceleratorConnectionInfo &data,
+              llvm::json::Path path);
+llvm::json::Value toJSON(const AcceleratorConnectionInfo &data);
+
 /// Actions to be performed in the native process on behalf of an accelerator
 /// plugin. AcceleratorActions are returned in the following contexts:
 ///
@@ -114,6 +136,9 @@ struct AcceleratorActions {
   int64_t identifier = 0;
   /// New breakpoints to set. Nothing to set if this is empty.
   std::vector<AcceleratorBreakpointInfo> breakpoints;
+  /// If set, the client should create a new target and connect to the
+  /// accelerator GDB server described here.
+  std::optional<AcceleratorConnectionInfo> connect_info;
 };
 
 bool fromJSON(const llvm::json::Value &value, AcceleratorActions &data,
diff --git a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
index 34067b1fa64c7..509d622ad2129 100644
--- a/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
+++ b/lldb/source/Utility/AcceleratorGDBRemotePackets.cpp
@@ -86,21 +86,42 @@ AcceleratorBreakpointHitArgs::GetSymbolValue(StringRef symbol_name) const {
   return std::nullopt;
 }
 
+bool fromJSON(const Value &value, AcceleratorConnectionInfo &data, Path path) {
+  ObjectMapper o(value, path);
+  return o && o.map("connect_url", data.connect_url) &&
+         o.mapOptional("exe_path", data.exe_path) &&
+         o.mapOptional("triple", data.triple) &&
+         o.map("synchronous", data.synchronous);
+}
+
+json::Value toJSON(const AcceleratorConnectionInfo &data) {
+  return Object{
+      {"connect_url", data.connect_url},
+      {"exe_path", data.exe_path},
+      {"triple", data.triple},
+      {"synchronous", data.synchronous},
+  };
+}
+
 bool fromJSON(const Value &value, AcceleratorActions &data, Path path) {
   ObjectMapper o(value, path);
   return o && o.map("plugin_name", data.plugin_name) &&
          o.map("session_name", data.session_name) &&
          o.map("identifier", data.identifier) &&
-         o.map("breakpoints", data.breakpoints);
+         o.map("breakpoints", data.breakpoints) &&
+         o.mapOptional("connect_info", data.connect_info);
 }
 
 json::Value toJSON(const AcceleratorActions &data) {
-  return Object{
+  Object obj{
       {"plugin_name", data.plugin_name},
       {"session_name", data.session_name},
       {"identifier", data.identifier},
       {"breakpoints", data.breakpoints},
   };
+  if (data.connect_info)
+    obj["connect_info"] = *data.connect_info;
+  return obj;
 }
 
 bool fromJSON(const Value &value, AcceleratorBreakpointHitResponse &data,
diff --git a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
index fdcb0585fa3c7..9b029dda91b96 100644
--- a/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
+++ b/lldb/unittests/Utility/AcceleratorGDBRemotePacketsTest.cpp
@@ -187,3 +187,53 @@ TEST(AcceleratorGDBRemotePacketsTest,
   EXPECT_EQ("exit",
             deserialized->actions->breakpoints[0].by_name->function_name);
 }
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorConnectionInfo) {
+  AcceleratorConnectionInfo conn;
+  conn.connect_url = "connect://localhost:1234";
+  conn.exe_path = "/path/to/accel.elf";
+  conn.triple = "amdgcn-amd-amdhsa";
+  conn.synchronous = true;
+
+  Expected<AcceleratorConnectionInfo> deserialized = roundtripJSON(conn);
+  ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+  EXPECT_EQ(conn.exe_path, deserialized->exe_path);
+  EXPECT_EQ(conn.triple, deserialized->triple);
+  EXPECT_EQ(conn.connect_url, deserialized->connect_url);
+  EXPECT_EQ(conn.synchronous, deserialized->synchronous);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorConnectionInfoMinimal) {
+  AcceleratorConnectionInfo conn;
+  conn.connect_url = "connect://localhost:5678";
+
+  Expected<AcceleratorConnectionInfo> deserialized = roundtripJSON(conn);
+  ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+  EXPECT_EQ(std::nullopt, deserialized->exe_path);
+  EXPECT_EQ(std::nullopt, deserialized->triple);
+  EXPECT_EQ(conn.connect_url, deserialized->connect_url);
+  EXPECT_FALSE(deserialized->synchronous);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorActionsWithConnectInfo) {
+  AcceleratorActions actions("mock", 3);
+  AcceleratorConnectionInfo conn;
+  conn.connect_url = "connect://localhost:9999";
+  conn.synchronous = true;
+  actions.connect_info = std::move(conn);
+
+  Expected<AcceleratorActions> deserialized = roundtripJSON(actions);
+  ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+  ASSERT_TRUE(deserialized->connect_info.has_value());
+  EXPECT_EQ("connect://localhost:9999",
+            deserialized->connect_info->connect_url);
+  EXPECT_TRUE(deserialized->connect_info->synchronous);
+}
+
+TEST(AcceleratorGDBRemotePacketsTest, AcceleratorActionsWithoutConnectInfo) {
+  AcceleratorActions actions("mock", 4);
+
+  Expected<AcceleratorActions> deserialized = roundtripJSON(actions);
+  ASSERT_THAT_EXPECTED(deserialized, Succeeded());
+  EXPECT_FALSE(deserialized->connect_info.has_value());
+}

>From 3373576e60eac454eafabdc9478cfacf1ba99c5b Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Mon, 8 Jun 2026 08:01:22 -0700
Subject: [PATCH 2/2] [lldb] Create and connect an accelerator target from
 connect_info

Act on the AcceleratorConnectionInfo returned by an accelerator plugin:
when an AcceleratorActions carries connect_info, ProcessGDBRemote creates
a new target and reverse-connects it to the GDB server the plugin points
at, broadcasting the new target so listeners (e.g. IDEs) can pick it up.

To exercise this end to end, the mock accelerator plugin runs a second
gdb-remote server inside the lldb-server process, backed by a minimal
synthetic accelerator process (ProcessMockAccelerator + one stopped
ThreadMockAccelerator + a tiny RegisterContextMockAccelerator with fixed
register values); no real process is launched. When the initialize
breakpoint is hit, the plugin requests a breakpoint on a dedicated
"mock_gpu_accelerator_connect" hook; hitting that hook returns connect_info
so the client connects. The hook is added to the existing mock inferior
used by the breakpoint test.

The existing end-to-end breakpoint test is extended to continue to the
connection hook and verify that a second (accelerator) target is created
alongside the original one, that its process is connected and stopped, and
that each of its registers reads back the expected value.
---
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |  55 ++++++++
 .../Process/gdb-remote/ProcessGDBRemote.h     |   4 +
 ...oints.py => TestMockAcceleratorActions.py} |  67 +++++++--
 ...lugin.py => TestMockAcceleratorPackets.py} |  52 ++++++-
 lldb/test/API/accelerator/mock/main.c         |  10 +-
 .../Plugins/Accelerator/Mock/CMakeLists.txt   |   3 +
 .../Mock/LLDBServerMockAcceleratorPlugin.cpp  | 106 +++++++++++++-
 .../Mock/LLDBServerMockAcceleratorPlugin.h    |  30 ++++
 .../Mock/ProcessMockAccelerator.cpp           | 112 +++++++++++++++
 .../Accelerator/Mock/ProcessMockAccelerator.h |  71 ++++++++++
 .../Mock/RegisterContextMockAccelerator.cpp   | 133 ++++++++++++++++++
 .../Mock/RegisterContextMockAccelerator.h     |  49 +++++++
 .../Mock/ThreadMockAccelerator.cpp            |  54 +++++++
 .../Accelerator/Mock/ThreadMockAccelerator.h  |  48 +++++++
 14 files changed, 777 insertions(+), 17 deletions(-)
 rename lldb/test/API/accelerator/mock/{TestMockAcceleratorBreakpoints.py => TestMockAcceleratorActions.py} (59%)
 rename lldb/test/API/accelerator/mock/{TestMockAcceleratorPlugin.py => TestMockAcceleratorPackets.py} (70%)
 create mode 100644 lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
 create mode 100644 lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
 create mode 100644 lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.cpp
 create mode 100644 lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.h
 create mode 100644 lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.cpp
 create mode 100644 lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.h

diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 16284f0052f5e..698892cfc4e19 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -4197,6 +4197,61 @@ ProcessGDBRemote::HandleAcceleratorActions(const AcceleratorActions &actions) {
       return error;
   }
 
+  if (actions.connect_info) {
+    if (llvm::Error error = HandleAcceleratorConnection(actions))
+      return error;
+  }
+
+  return llvm::Error::success();
+}
+
+llvm::Error ProcessGDBRemote::HandleAcceleratorConnection(
+    const AcceleratorActions &actions) {
+  const AcceleratorConnectionInfo &connect_info = *actions.connect_info;
+  Debugger &debugger = GetTarget().GetDebugger();
+
+  // Create a new (empty) target for the accelerator and connect to the GDB
+  // server the plugin is serving.
+  llvm::StringRef exe_path =
+      connect_info.exe_path ? *connect_info.exe_path : llvm::StringRef();
+  llvm::StringRef triple =
+      connect_info.triple ? *connect_info.triple : llvm::StringRef();
+  TargetSP accelerator_target_sp;
+  Status error = debugger.GetTargetList().CreateTarget(
+      debugger, exe_path, triple, eLoadDependentsNo,
+      /*platform_options=*/nullptr, accelerator_target_sp);
+  if (error.Fail())
+    return error.takeError();
+  if (!accelerator_target_sp)
+    return llvm::createStringError("failed to create accelerator target");
+
+  PlatformSP platform_sp = accelerator_target_sp->GetPlatform();
+  if (!platform_sp)
+    return llvm::createStringError(
+        "no platform for the accelerator target connection");
+
+  ProcessSP process_sp =
+      connect_info.synchronous
+          ? platform_sp->ConnectProcessSynchronous(
+                connect_info.connect_url, GetPluginNameStatic(), debugger,
+                *debugger.GetAsyncOutputStream(), accelerator_target_sp.get(),
+                error)
+          : platform_sp->ConnectProcess(connect_info.connect_url,
+                                        GetPluginNameStatic(), debugger,
+                                        accelerator_target_sp.get(), error);
+  if (error.Fail())
+    return error.takeError();
+  if (!process_sp)
+    return llvm::createStringError("failed to connect to the accelerator");
+
+  accelerator_target_sp->SetTargetSessionName(actions.session_name);
+
+  // Broadcast the target creation event so DAP can create a child session
+  auto event_sp = std::make_shared<Event>(
+      Target::eBroadcastBitNewTargetCreated,
+      new Target::TargetEventData(GetTarget().shared_from_this(),
+                                  accelerator_target_sp));
+  GetTarget().BroadcastEvent(event_sp);
   return llvm::Error::success();
 }
 
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 0a3386082c388..525a45f7cce21 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -488,6 +488,10 @@ class ProcessGDBRemote : public Process,
   /// breakpoints are still set.
   llvm::Error HandleAcceleratorBreakpoints(const AcceleratorActions &actions);
 
+  /// Create a new target for an accelerator and connect it to the GDB server
+  /// described by the action's connection info.
+  llvm::Error HandleAcceleratorConnection(const AcceleratorActions &actions);
+
   /// Breakpoint callback invoked when an accelerator-plugin-requested
   /// breakpoint is hit. Resolves any requested symbol values, notifies the
   /// plugin via the "jAcceleratorPluginBreakpointHit" packet, and handles the
diff --git a/lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorActions.py
similarity index 59%
rename from lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py
rename to lldb/test/API/accelerator/mock/TestMockAcceleratorActions.py
index 885e73e3cec15..e60be335c15af 100644
--- a/lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorActions.py
@@ -1,11 +1,15 @@
 """
-End-to-end test for accelerator plugin breakpoints.
+End-to-end test for accelerator plugin actions (breakpoints and connections).
 
 Launches a real process against an lldb-server that has the mock accelerator
 plugin enabled and verifies that the breakpoints requested by the plugin are
 set in the native process, hit, and that hitting one breakpoint can request
 further breakpoints. This exercises all three breakpoint types: by name, by
 name scoped to a shared library, and by address.
+
+It also verifies that hitting the plugin's connection-trigger breakpoint causes
+the client to create a second (accelerator) target and reverse-connect it to
+the mock accelerator GDB server.
 """
 
 import lldb
@@ -22,7 +26,7 @@ def uint64_to_int64(value):
     return value
 
 
-class MockAcceleratorBreakpointsTestCase(TestBase):
+class MockAcceleratorActionsTestCase(TestBase):
     NO_DEBUG_INFO_TESTCASE = True
 
     def setUp(self):
@@ -60,8 +64,9 @@ def check_accelerator_breakpoint_stop(self, process, function_name, hit_count=No
 
     @skipIfRemote
     @add_test_categories(["llgs"])
-    def test_accelerator_breakpoints(self):
-        """The mock accelerator plugin drives breakpoints in the inferior."""
+    def test_accelerator_actions(self):
+        """The mock accelerator plugin drives breakpoints in the inferior and,
+        once initialized, a connection that creates a second target."""
         self.build()
         exe = self.getBuildArtifact("a.out")
         target = self.dbg.CreateTarget(exe)
@@ -83,11 +88,55 @@ def test_accelerator_breakpoints(self):
         self.assertEqual(target.GetNumBreakpoints(), 0)
 
         # Hitting the mock_gpu_accelerator_initialize breakpoint caused the
-        # plugin to request two more breakpoints: one by address (on
-        # "mock_gpu_accelerator_compute", from the symbol value delivered with
-        # the hit) and one by name scoped to the "a.out" shared library (on
-        # "mock_gpu_accelerator_finish"). main() calls
-        # mock_gpu_accelerator_compute() first.
+        # plugin to request three more breakpoints: the connection hook (hit
+        # next, since main() connects right after initializing), one by address
+        # (on "mock_gpu_accelerator_compute", from the symbol value delivered
+        # with the hit), and one by name scoped to the "a.out" shared library (on
+        # "mock_gpu_accelerator_finish"). Only the CPU target exists until the
+        # connection hook is hit.
+        self.assertEqual(self.dbg.GetNumTargets(), 1)
+        process.Continue()
+        self.check_accelerator_breakpoint_stop(
+            process, "mock_gpu_accelerator_connect", hit_count=1
+        )
+
+        # The accelerator target now exists alongside the CPU target.
+        self.assertEqual(self.dbg.GetNumTargets(), 2)
+        accelerator_target = None
+        for i in range(self.dbg.GetNumTargets()):
+            candidate = self.dbg.GetTargetAtIndex(i)
+            if candidate != target:
+                accelerator_target = candidate
+                break
+        self.assertTrue(accelerator_target.IsValid())
+
+        # The accelerator process must be successfully connected and stopped.
+        accelerator_process = accelerator_target.GetProcess()
+        self.assertTrue(accelerator_process.IsValid())
+        self.assertState(accelerator_process.GetState(), lldb.eStateStopped)
+
+        # Validate the registers (each value == 0x1000 + register index).
+        accelerator_frame = accelerator_process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        expected_registers = {
+            "r0": 0x1000,
+            "r1": 0x1001,
+            "sp": 0x1002,
+            "fp": 0x1003,
+            "pc": 0x1004,
+            "flags": 0x1005,
+        }
+        for name, value in expected_registers.items():
+            reg = accelerator_frame.FindRegister(name)
+            self.assertTrue(reg.IsValid(), "register %s should exist" % name)
+            self.assertEqual(
+                reg.GetValueAsUnsigned(),
+                value,
+                "register %s should read back its expected value" % name,
+            )
+
+        # With the accelerator connected, continue through the remaining
+        # breakpoint types: by address (on mock_gpu_accelerator_compute), then by
+        # name scoped to a shared library (on mock_gpu_accelerator_finish).
         process.Continue()
         self.check_accelerator_breakpoint_stop(
             process, "mock_gpu_accelerator_compute", hit_count=1
diff --git a/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
similarity index 70%
rename from lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
rename to lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
index 1eeeb7f731eb7..ff205deae91c9 100644
--- a/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorPackets.py
@@ -1,9 +1,9 @@
 """
-Tests for the lldb-server mock accelerator plugin.
+Packet-level tests for the lldb-server mock accelerator plugin.
 
 Verifies the accelerator-plugins+ feature in qSupported,
 the jAcceleratorPluginInitialize packet response, and
-the jAcceleratorPluginBreakpointHit round-trip.
+the jAcceleratorPluginBreakpointHit round-trip (including connect_info).
 """
 
 import json
@@ -22,7 +22,7 @@ def get_accelerator_action(actions, plugin_name):
     return None
 
 
-class MockAcceleratorPluginTestCase(gdbremote_testcase.GdbRemoteTestCaseBase):
+class MockAcceleratorPacketsTestCase(gdbremote_testcase.GdbRemoteTestCaseBase):
     def setUp(self):
         super().setUp()
         if "mock-accelerator" not in configuration.enabled_plugins:
@@ -140,3 +140,49 @@ def test_jAcceleratorPluginBreakpointHit_response(self):
         # "mock_gpu_accelerator_compute" symbol value.
         self.assertIn(2, new_bps)
         self.assertEqual(new_bps[2]["by_address"]["load_address"], 0x4000)
+
+        # The initialize hit also arms the connection-trigger breakpoint by name.
+        self.assertIn(4, new_bps)
+        self.assertEqual(
+            new_bps[4]["by_name"]["function_name"], "mock_gpu_accelerator_connect"
+        )
+
+    @add_test_categories(["llgs"])
+    def test_jAcceleratorPluginBreakpointHit_returns_connect_info(self):
+        self.build()
+        self.set_inferior_startup_launch()
+        self.prep_debug_monitor_and_inferior()
+
+        self.add_qSupported_packets()
+        self.expect_gdbremote_sequence()
+
+        # Simulate the connection-trigger breakpoint (identifier 4) being hit.
+        # The plugin responds with connect_info describing the reverse
+        # connection the client should make to the mock accelerator GDB server.
+        hit_args = {
+            "plugin_name": "mock",
+            "breakpoint": {"identifier": 4, "symbol_names": []},
+            "symbol_values": [],
+        }
+        hit_json = json.dumps(hit_args, separators=(",", ":"))
+        escaped_json = escape_binary(hit_json)
+        response = self.send_and_decode_json(
+            "jAcceleratorPluginBreakpointHit:" + escaped_json
+        )
+
+        self.assertTrue(response["disable_bp"])
+        self.assertFalse(response["auto_resume_native"])
+
+        actions = response["actions"]
+        self.assertEqual(actions["plugin_name"], "mock")
+        self.assertEqual(actions["session_name"], "Mock Accelerator Session")
+
+        # The connect_info must carry a reverse-connect URL to a local port and
+        # request a synchronous connection.
+        self.assertIn("connect_info", actions)
+        connect_info = actions["connect_info"]
+        self.assertTrue(
+            connect_info["connect_url"].startswith("connect://localhost:"),
+            connect_info["connect_url"],
+        )
+        self.assertTrue(connect_info["synchronous"])
diff --git a/lldb/test/API/accelerator/mock/main.c b/lldb/test/API/accelerator/mock/main.c
index 5f4c7b1fd3147..1e90585124472 100644
--- a/lldb/test/API/accelerator/mock/main.c
+++ b/lldb/test/API/accelerator/mock/main.c
@@ -1,5 +1,5 @@
-// The mock accelerator plugin sets its initialize breakpoint on this function.
-// Using a dedicated, uniquely named function (rather than "main") ensures the
+// The mock accelerator plugin sets its breakpoints on these dedicated, uniquely
+// named functions. Using dedicated functions (rather than "main") ensures the
 // mock plugin only affects this test program and not other inferiors launched
 // by lldb-server.
 void mock_gpu_accelerator_initialize(void) {}
@@ -8,8 +8,14 @@ int mock_gpu_accelerator_compute(int x) { return x * 2; }
 
 int mock_gpu_accelerator_finish(void) { return 0; }
 
+// When the plugin's connection-trigger breakpoint on this function is hit, it
+// asks the client to create a second target and connect to the mock accelerator
+// GDB server.
+void mock_gpu_accelerator_connect(void) {}
+
 int main(void) {
   mock_gpu_accelerator_initialize();
+  mock_gpu_accelerator_connect();
   int result = mock_gpu_accelerator_compute(21);
   mock_gpu_accelerator_finish();
   return result == 42 ? 0 : 1;
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/CMakeLists.txt b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/CMakeLists.txt
index 9b5a82a087e3d..d6cdc603213e3 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/CMakeLists.txt
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/CMakeLists.txt
@@ -1,5 +1,8 @@
 add_lldb_library(lldbServerPluginMockAccelerator
   LLDBServerMockAcceleratorPlugin.cpp
+  ProcessMockAccelerator.cpp
+  ThreadMockAccelerator.cpp
+  RegisterContextMockAccelerator.cpp
 
   LINK_LIBS
     lldbHost
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
index f89fb90e26aa6..1364363b14315 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
@@ -7,13 +7,54 @@
 //===----------------------------------------------------------------------===//
 
 #include "LLDBServerMockAcceleratorPlugin.h"
+#include "ProcessMockAccelerator.h"
 
+#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h"
+#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"
+#include "lldb/Host/ProcessLaunchInfo.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Host/common/TCPSocket.h"
+#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h"
+#include "lldb/Utility/Args.h"
+#include "lldb/Utility/Connection.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace lldb;
 using namespace lldb_private;
 using namespace lldb_private::lldb_server;
+using namespace lldb_private::process_gdb_remote;
 
 LLDBServerMockAcceleratorPlugin::LLDBServerMockAcceleratorPlugin(
     GDBServer &gdb_server, MainLoop &main_loop)
-    : LLDBServerAcceleratorPlugin(gdb_server, main_loop) {}
+    : LLDBServerAcceleratorPlugin(gdb_server, main_loop) {
+  // Run a second gdb-remote server inside this lldb-server process (alongside
+  // the one debugging the CPU process), backed by ProcessMockAccelerator. No
+  // real process is launched or exec'd: ProcessMockAccelerator::Manager just
+  // returns a synthetic, already-stopped process with a single thread and a
+  // fixed set of registers. The client reverse-connects to this server to
+  // create the accelerator target.
+  m_process_manager_up =
+      std::make_unique<ProcessMockAccelerator::Manager>(m_main_loop);
+  m_gpu_server_up = std::make_unique<GDBRemoteCommunicationServerLLGS>(
+      m_main_loop, *m_process_manager_up);
+
+  // LaunchProcess() is how LLGS obtains its current process; it routes to
+  // ProcessMockAccelerator::Manager::Launch() (which ignores this info) and
+  // only requires a non-empty argument list, so a single placeholder is enough.
+  ProcessLaunchInfo info;
+  Args args;
+  args.AppendArgument("/pretend/path/to/mockgpu");
+  info.SetArguments(args, /*first_arg_is_executable=*/true);
+  m_gpu_server_up->SetLaunchInfo(info);
+  if (Status error = m_gpu_server_up->LaunchProcess(); error.Fail())
+    LLDB_LOG(GetLog(GDBRLog::Plugin),
+             "failed to create mock accelerator process: {0}",
+             error.AsCString());
+}
+
+LLDBServerMockAcceleratorPlugin::~LLDBServerMockAcceleratorPlugin() = default;
 
 llvm::StringRef LLDBServerMockAcceleratorPlugin::GetPluginName() {
   return "mock";
@@ -47,8 +88,8 @@ LLDBServerMockAcceleratorPlugin::BreakpointWasHit(
   switch (args.breakpoint.identifier) {
   case kBreakpointIDInitialize: {
     // The initialize breakpoint was hit. Disable it, stop the native process,
-    // and request two more breakpoints to exercise the remaining breakpoint
-    // types.
+    // and request more breakpoints: two to exercise the remaining breakpoint
+    // types, plus the connection hook now that the accelerator has initialized.
     response.disable_bp = true;
     response.auto_resume_native = false;
 
@@ -72,6 +113,17 @@ LLDBServerMockAcceleratorPlugin::BreakpointWasHit(
       actions.breakpoints.push_back(std::move(by_address));
     }
 
+    // Now that the accelerator has initialized, set the breakpoint on the
+    // dedicated connection hook. Arming it only after the initialize hit
+    // (rather than up front) mirrors how a real GPU plugin connects once the
+    // runtime is ready. It only resolves in programs that define
+    // "mock_gpu_accelerator_connect".
+    AcceleratorBreakpointInfo connect_bp;
+    connect_bp.identifier = kBreakpointIDConnect;
+    connect_bp.by_name = AcceleratorBreakpointByName{
+        std::nullopt, "mock_gpu_accelerator_connect"};
+    actions.breakpoints.push_back(std::move(connect_bp));
+
     response.actions = std::move(actions);
     break;
   }
@@ -81,7 +133,55 @@ LLDBServerMockAcceleratorPlugin::BreakpointWasHit(
     response.disable_bp = true;
     response.auto_resume_native = false;
     break;
+  case kBreakpointIDConnect: {
+    // The program reached its connection hook. Ask the client to create a
+    // second target and reverse-connect to our in-process mock accelerator
+    // GDB server.
+    response.disable_bp = true;
+    response.auto_resume_native = false;
+    AcceleratorActions actions(GetPluginName(), kBreakpointIDConnect);
+    actions.session_name = "Mock Accelerator Session";
+    actions.connect_info = CreateConnection();
+    response.actions = std::move(actions);
+    break;
+  }
   }
 
   return response;
 }
+
+std::optional<AcceleratorConnectionInfo>
+LLDBServerMockAcceleratorPlugin::CreateConnection() {
+  Log *log = GetLog(GDBRLog::Plugin);
+
+  // Listen on an ephemeral local port; the client will reverse-connect to it.
+  llvm::Expected<std::unique_ptr<TCPSocket>> sock =
+      Socket::TcpListen("localhost:0");
+  if (!sock) {
+    LLDB_LOG_ERROR(log, sock.takeError(),
+                   "mock accelerator failed to listen: {0}");
+    return std::nullopt;
+  }
+
+  AcceleratorConnectionInfo info;
+  info.connect_url =
+      llvm::formatv("connect://localhost:{0}", (*sock)->GetLocalPortNumber());
+  info.synchronous = true;
+
+  m_listen_socket = std::move(*sock);
+  llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>> handles =
+      m_listen_socket->Accept(
+          m_main_loop, [this](std::unique_ptr<Socket> socket) {
+            std::unique_ptr<Connection> connection_up =
+                std::make_unique<ConnectionFileDescriptor>(std::move(socket));
+            m_gpu_server_up->InitializeConnection(std::move(connection_up));
+          });
+  if (!handles) {
+    LLDB_LOG_ERROR(log, handles.takeError(),
+                   "mock accelerator failed to accept: {0}");
+    return std::nullopt;
+  }
+  m_read_handles = std::move(*handles);
+
+  return info;
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
index 7a1b57e0bb0b7..c97f3619eaaae 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
@@ -10,13 +10,26 @@
 #define LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_LLDBSERVERMOCKACCELERATORPLUGIN_H
 
 #include "Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h"
+#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Host/common/NativeProcessProtocol.h"
+
+#include <memory>
+#include <vector>
 
 namespace lldb_private {
+
+class TCPSocket;
+
+namespace process_gdb_remote {
+class GDBRemoteCommunicationServerLLGS;
+} // namespace process_gdb_remote
+
 namespace lldb_server {
 
 class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin {
 public:
   LLDBServerMockAcceleratorPlugin(GDBServer &gdb_server, MainLoop &main_loop);
+  ~LLDBServerMockAcceleratorPlugin() override;
 
   llvm::StringRef GetPluginName() override;
   std::optional<AcceleratorActions> GetInitializeActions() override;
@@ -24,6 +37,10 @@ class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin {
   BreakpointWasHit(AcceleratorBreakpointHitArgs &args) override;
 
 private:
+  // Start listening for a reverse connection to the mock accelerator GDB
+  // server and return the connection info the client should connect to.
+  std::optional<AcceleratorConnectionInfo> CreateConnection();
+
   // Breakpoint set during initialization, by function name with no shared
   // library. Requests the "compute" symbol value when hit.
   static constexpr int64_t kBreakpointIDInitialize = 1;
@@ -32,6 +49,19 @@ class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin {
   static constexpr int64_t kBreakpointIDByAddress = 2;
   // Breakpoint set by function name scoped to a shared library.
   static constexpr int64_t kBreakpointIDByNameShlib = 3;
+  // Breakpoint on the dedicated "mock_gpu_accelerator_connect" hook. When hit,
+  // the plugin asks the client to create a second target and connect to the
+  // mock accelerator GDB server. Only programs that define that function (the
+  // connection test) trigger it.
+  static constexpr int64_t kBreakpointIDConnect = 4;
+
+  // The in-process GDB server (and its fake process) that serves the mock
+  // accelerator connection.
+  std::unique_ptr<NativeProcessProtocol::Manager> m_process_manager_up;
+  std::unique_ptr<process_gdb_remote::GDBRemoteCommunicationServerLLGS>
+      m_gpu_server_up;
+  std::unique_ptr<TCPSocket> m_listen_socket;
+  std::vector<MainLoopBase::ReadHandleUP> m_read_handles;
 };
 
 } // namespace lldb_server
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
new file mode 100644
index 0000000000000..ba5e162d41c83
--- /dev/null
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp
@@ -0,0 +1,112 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "ProcessMockAccelerator.h"
+#include "ThreadMockAccelerator.h"
+
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Host/ProcessLaunchInfo.h"
+#include "llvm/Support/Error.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::lldb_server;
+
+// A fixed, fake pid and tid for the single mock accelerator process/thread.
+static constexpr lldb::pid_t kMockPid = 7777;
+static constexpr lldb::tid_t kMockTid = 1;
+
+llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+ProcessMockAccelerator::Manager::Launch(ProcessLaunchInfo &launch_info,
+                                        NativeDelegate &native_delegate) {
+  return std::make_unique<ProcessMockAccelerator>(kMockPid, native_delegate);
+}
+
+llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+ProcessMockAccelerator::Manager::Attach(lldb::pid_t pid,
+                                        NativeDelegate &native_delegate) {
+  return llvm::createStringError("attach is not supported by the mock "
+                                 "accelerator process");
+}
+
+ProcessMockAccelerator::ProcessMockAccelerator(lldb::pid_t pid,
+                                               NativeDelegate &delegate)
+    : NativeProcessProtocol(pid, /*terminal_fd=*/-1, delegate) {
+  m_state = eStateStopped;
+  UpdateThreads();
+}
+
+Status ProcessMockAccelerator::Resume(const ResumeActionList &resume_actions) {
+  // Nothing actually runs; stay stopped.
+  return Status();
+}
+
+Status ProcessMockAccelerator::Halt() { return Status(); }
+
+Status ProcessMockAccelerator::Detach() {
+  SetState(eStateDetached, true);
+  return Status();
+}
+
+Status ProcessMockAccelerator::Signal(int signo) {
+  return Status::FromErrorString("unimplemented");
+}
+
+Status ProcessMockAccelerator::Kill() { return Status(); }
+
+Status ProcessMockAccelerator::ReadMemory(lldb::addr_t addr, void *buf,
+                                          size_t size, size_t &bytes_read) {
+  bytes_read = 0;
+  return Status::FromErrorString("unimplemented");
+}
+
+Status ProcessMockAccelerator::WriteMemory(lldb::addr_t addr, const void *buf,
+                                           size_t size, size_t &bytes_written) {
+  bytes_written = 0;
+  return Status::FromErrorString("unimplemented");
+}
+
+lldb::addr_t ProcessMockAccelerator::GetSharedLibraryInfoAddress() {
+  return LLDB_INVALID_ADDRESS;
+}
+
+size_t ProcessMockAccelerator::UpdateThreads() {
+  if (m_threads.empty()) {
+    m_threads.push_back(
+        std::make_unique<ThreadMockAccelerator>(*this, kMockTid));
+    SetCurrentThreadID(kMockTid);
+  }
+  return m_threads.size();
+}
+
+const ArchSpec &ProcessMockAccelerator::GetArchitecture() const {
+  if (!m_arch.IsValid())
+    m_arch = HostInfo::GetArchitecture();
+  return m_arch;
+}
+
+Status ProcessMockAccelerator::SetBreakpoint(lldb::addr_t addr, uint32_t size,
+                                             bool hardware) {
+  return Status::FromErrorString("unimplemented");
+}
+
+llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+ProcessMockAccelerator::GetAuxvData() const {
+  return std::error_code(ENOENT, std::generic_category());
+}
+
+Status ProcessMockAccelerator::GetLoadedModuleFileSpec(const char *module_path,
+                                                       FileSpec &file_spec) {
+  return Status::FromErrorString("unimplemented");
+}
+
+Status
+ProcessMockAccelerator::GetFileLoadAddress(const llvm::StringRef &file_name,
+                                           lldb::addr_t &load_addr) {
+  return Status::FromErrorString("unimplemented");
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
new file mode 100644
index 0000000000000..07c0c4c6d5ef5
--- /dev/null
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_PROCESSMOCKACCELERATOR_H
+#define LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_PROCESSMOCKACCELERATOR_H
+
+#include "lldb/Host/common/NativeProcessProtocol.h"
+#include "lldb/Utility/ArchSpec.h"
+
+namespace lldb_private {
+namespace lldb_server {
+
+/// A minimal, always-stopped fake process used to serve a GDB remote
+/// connection for the mock accelerator plugin. It models a single stopped
+/// thread and nothing else; it lets the client create and connect to a second
+/// (accelerator) target without requiring any real hardware.
+class ProcessMockAccelerator : public NativeProcessProtocol {
+public:
+  class Manager : public NativeProcessProtocol::Manager {
+  public:
+    using NativeProcessProtocol::Manager::Manager;
+
+    llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+    Launch(ProcessLaunchInfo &launch_info,
+           NativeDelegate &native_delegate) override;
+
+    llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
+    Attach(lldb::pid_t pid, NativeDelegate &native_delegate) override;
+  };
+
+  ProcessMockAccelerator(lldb::pid_t pid, NativeDelegate &delegate);
+
+  Status Resume(const ResumeActionList &resume_actions) override;
+  Status Halt() override;
+  Status Detach() override;
+  Status Signal(int signo) override;
+  Status Kill() override;
+
+  Status ReadMemory(lldb::addr_t addr, void *buf, size_t size,
+                    size_t &bytes_read) override;
+  Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size,
+                     size_t &bytes_written) override;
+
+  lldb::addr_t GetSharedLibraryInfoAddress() override;
+  size_t UpdateThreads() override;
+  const ArchSpec &GetArchitecture() const override;
+
+  Status SetBreakpoint(lldb::addr_t addr, uint32_t size,
+                       bool hardware) override;
+
+  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
+  GetAuxvData() const override;
+
+  Status GetLoadedModuleFileSpec(const char *module_path,
+                                 FileSpec &file_spec) override;
+  Status GetFileLoadAddress(const llvm::StringRef &file_name,
+                            lldb::addr_t &load_addr) override;
+
+private:
+  mutable ArchSpec m_arch;
+};
+
+} // namespace lldb_server
+} // namespace lldb_private
+
+#endif // LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_PROCESSMOCKACCELERATOR_H
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.cpp
new file mode 100644
index 0000000000000..b7eac116f8f7b
--- /dev/null
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.cpp
@@ -0,0 +1,133 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "RegisterContextMockAccelerator.h"
+
+#include "lldb/Utility/DataBufferHeap.h"
+#include "lldb/Utility/RegisterValue.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::lldb_server;
+
+// LLDB register numbers must start at 0 and be contiguous. This minimal set is
+// just enough for the mock accelerator process to be debugged over GDB remote.
+enum LLDBRegNum : uint32_t {
+  LLDB_R0 = 0,
+  LLDB_R1,
+  LLDB_SP,
+  LLDB_FP,
+  LLDB_PC,
+  LLDB_Flags,
+  kNumRegs
+};
+
+#define DEFINE_REG(name, idx, generic)                                         \
+  {name,                                                                       \
+   nullptr,                                                                    \
+   sizeof(uint64_t),                                                           \
+   idx * sizeof(uint64_t),                                                     \
+   eEncodingUint,                                                              \
+   eFormatHex,                                                                 \
+   {LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, generic, LLDB_INVALID_REGNUM,    \
+    idx},                                                                      \
+   nullptr,                                                                    \
+   nullptr,                                                                    \
+   nullptr}
+
+static const RegisterInfo g_register_infos[] = {
+    DEFINE_REG("r0", LLDB_R0, LLDB_INVALID_REGNUM),
+    DEFINE_REG("r1", LLDB_R1, LLDB_INVALID_REGNUM),
+    DEFINE_REG("sp", LLDB_SP, LLDB_REGNUM_GENERIC_SP),
+    DEFINE_REG("fp", LLDB_FP, LLDB_REGNUM_GENERIC_FP),
+    DEFINE_REG("pc", LLDB_PC, LLDB_REGNUM_GENERIC_PC),
+    DEFINE_REG("flags", LLDB_Flags, LLDB_INVALID_REGNUM),
+};
+
+// The set's member register numbers. This must be a valid array (not null):
+// the stop-reply path reads it to expedite the set's registers.
+static const uint32_t g_register_nums[] = {LLDB_R0, LLDB_R1, LLDB_SP,
+                                           LLDB_FP, LLDB_PC, LLDB_Flags};
+
+static const RegisterSet g_register_set = {"General Purpose Registers", "gpr",
+                                           kNumRegs, g_register_nums};
+
+RegisterContextMockAccelerator::RegisterContextMockAccelerator(
+    NativeThreadProtocol &native_thread)
+    : NativeRegisterContext(native_thread) {
+  // Give each register a distinct, constant value so reads are deterministic.
+  for (uint32_t i = 0; i < kNumRegs; ++i)
+    m_regs[i] = 0x1000 + i;
+}
+
+uint32_t RegisterContextMockAccelerator::GetRegisterCount() const {
+  return kNumRegs;
+}
+
+uint32_t RegisterContextMockAccelerator::GetUserRegisterCount() const {
+  return kNumRegs;
+}
+
+const RegisterInfo *
+RegisterContextMockAccelerator::GetRegisterInfoAtIndex(uint32_t reg) const {
+  if (reg < kNumRegs)
+    return &g_register_infos[reg];
+  return nullptr;
+}
+
+uint32_t RegisterContextMockAccelerator::GetRegisterSetCount() const {
+  return 1;
+}
+
+const RegisterSet *
+RegisterContextMockAccelerator::GetRegisterSet(uint32_t set_index) const {
+  if (set_index == 0)
+    return &g_register_set;
+  return nullptr;
+}
+
+Status
+RegisterContextMockAccelerator::ReadRegister(const RegisterInfo *reg_info,
+                                             RegisterValue &reg_value) {
+  if (!reg_info)
+    return Status::FromErrorString("invalid register info");
+  const uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
+  if (reg >= kNumRegs)
+    return Status::FromErrorString("invalid register number");
+  reg_value.SetUInt64(m_regs[reg]);
+  return Status();
+}
+
+Status
+RegisterContextMockAccelerator::WriteRegister(const RegisterInfo *reg_info,
+                                              const RegisterValue &reg_value) {
+  if (!reg_info)
+    return Status::FromErrorString("invalid register info");
+  const uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
+  if (reg >= kNumRegs)
+    return Status::FromErrorString("invalid register number");
+  m_regs[reg] = reg_value.GetAsUInt64();
+  return Status();
+}
+
+Status RegisterContextMockAccelerator::ReadAllRegisterValues(
+    lldb::WritableDataBufferSP &data_sp) {
+  data_sp = std::make_shared<DataBufferHeap>(
+      reinterpret_cast<const uint8_t *>(m_regs.data()),
+      m_regs.size() * sizeof(uint64_t));
+  return Status();
+}
+
+Status RegisterContextMockAccelerator::WriteAllRegisterValues(
+    const lldb::DataBufferSP &data_sp) {
+  if (!data_sp || data_sp->GetByteSize() != m_regs.size() * sizeof(uint64_t))
+    return Status::FromErrorString("invalid register data");
+  ::memcpy(m_regs.data(), data_sp->GetBytes(),
+           m_regs.size() * sizeof(uint64_t));
+  return Status();
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.h
new file mode 100644
index 0000000000000..b26a5d454826c
--- /dev/null
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/RegisterContextMockAccelerator.h
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_REGISTERCONTEXTMOCKACCELERATOR_H
+#define LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_REGISTERCONTEXTMOCKACCELERATOR_H
+
+#include "lldb/Host/common/NativeRegisterContext.h"
+
+#include <array>
+
+namespace lldb_private {
+namespace lldb_server {
+
+/// A minimal register context for the mock accelerator process. It exposes a
+/// small, fixed register set with constant values; it exists only so the mock
+/// accelerator process can be debugged over the GDB remote protocol, not to
+/// model any real hardware.
+class RegisterContextMockAccelerator : public NativeRegisterContext {
+public:
+  RegisterContextMockAccelerator(NativeThreadProtocol &native_thread);
+
+  uint32_t GetRegisterCount() const override;
+  uint32_t GetUserRegisterCount() const override;
+  const RegisterInfo *GetRegisterInfoAtIndex(uint32_t reg) const override;
+  uint32_t GetRegisterSetCount() const override;
+  const RegisterSet *GetRegisterSet(uint32_t set_index) const override;
+
+  Status ReadRegister(const RegisterInfo *reg_info,
+                      RegisterValue &reg_value) override;
+  Status WriteRegister(const RegisterInfo *reg_info,
+                       const RegisterValue &reg_value) override;
+
+  Status ReadAllRegisterValues(lldb::WritableDataBufferSP &data_sp) override;
+  Status WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override;
+
+private:
+  /// The registers, indexed by LLDB register number. Each one is 64 bits.
+  std::array<uint64_t, 6> m_regs;
+};
+
+} // namespace lldb_server
+} // namespace lldb_private
+
+#endif // LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_REGISTERCONTEXTMOCKACCELERATOR_H
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.cpp
new file mode 100644
index 0000000000000..69b7408ef3b56
--- /dev/null
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.cpp
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#include "ThreadMockAccelerator.h"
+#include "ProcessMockAccelerator.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::lldb_server;
+
+ThreadMockAccelerator::ThreadMockAccelerator(ProcessMockAccelerator &process,
+                                             lldb::tid_t tid)
+    : NativeThreadProtocol(process, tid), m_reg_context(*this) {
+  m_stop_info.reason = lldb::eStopReasonTrace;
+}
+
+std::string ThreadMockAccelerator::GetName() {
+  return "Mock Accelerator Thread";
+}
+
+lldb::StateType ThreadMockAccelerator::GetState() {
+  return lldb::eStateStopped;
+}
+
+bool ThreadMockAccelerator::GetStopReason(ThreadStopInfo &stop_info,
+                                          std::string &description) {
+  stop_info = m_stop_info;
+  description = "mock accelerator thread stopped";
+  return true;
+}
+
+Status ThreadMockAccelerator::SetWatchpoint(lldb::addr_t addr, size_t size,
+                                            uint32_t watch_flags,
+                                            bool hardware) {
+  return Status::FromErrorString("unimplemented");
+}
+
+Status ThreadMockAccelerator::RemoveWatchpoint(lldb::addr_t addr) {
+  return Status::FromErrorString("unimplemented");
+}
+
+Status ThreadMockAccelerator::SetHardwareBreakpoint(lldb::addr_t addr,
+                                                    size_t size) {
+  return Status::FromErrorString("unimplemented");
+}
+
+Status ThreadMockAccelerator::RemoveHardwareBreakpoint(lldb::addr_t addr) {
+  return Status::FromErrorString("unimplemented");
+}
diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.h
new file mode 100644
index 0000000000000..5f9dec9483511
--- /dev/null
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ThreadMockAccelerator.h
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_THREADMOCKACCELERATOR_H
+#define LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_THREADMOCKACCELERATOR_H
+
+#include "RegisterContextMockAccelerator.h"
+#include "lldb/Host/common/NativeThreadProtocol.h"
+#include <string>
+
+namespace lldb_private {
+namespace lldb_server {
+
+class ProcessMockAccelerator;
+
+/// A single, always-stopped thread for the mock accelerator process.
+class ThreadMockAccelerator : public NativeThreadProtocol {
+public:
+  ThreadMockAccelerator(ProcessMockAccelerator &process, lldb::tid_t tid);
+
+  std::string GetName() override;
+  lldb::StateType GetState() override;
+  bool GetStopReason(ThreadStopInfo &stop_info,
+                     std::string &description) override;
+  RegisterContextMockAccelerator &GetRegisterContext() override {
+    return m_reg_context;
+  }
+
+  Status SetWatchpoint(lldb::addr_t addr, size_t size, uint32_t watch_flags,
+                       bool hardware) override;
+  Status RemoveWatchpoint(lldb::addr_t addr) override;
+  Status SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override;
+  Status RemoveHardwareBreakpoint(lldb::addr_t addr) override;
+
+private:
+  RegisterContextMockAccelerator m_reg_context;
+  ThreadStopInfo m_stop_info;
+};
+
+} // namespace lldb_server
+} // namespace lldb_private
+
+#endif // LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_THREADMOCKACCELERATOR_H



More information about the lldb-commits mailing list