https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/201449
>From 354ed7c66abd0849d6b76f4ae233a6068b10d033 Mon Sep 17 00:00:00 2001 From: satya janga <[email protected]> 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 | 14 ++++-- .../Utility/AcceleratorGDBRemotePackets.h | 25 ++++++++++ .../Utility/AcceleratorGDBRemotePackets.cpp | 25 +++++++++- .../AcceleratorGDBRemotePacketsTest.cpp | 50 +++++++++++++++++++ 4 files changed, 109 insertions(+), 5 deletions(-) diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md index 577a5ad31674f..3643986f0bfaa 100644 --- a/lldb/docs/resources/lldbgdbremote.md +++ b/lldb/docs/resources/lldbgdbremote.md @@ -2814,9 +2814,17 @@ 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. -In future patches, each `accelerator_action` will include additional fields -such as connection info for secondary debug sessions and synchronization -options. +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, connect synchronously: the client blocks until the accelerator process is connected and stopped before continuing. If false, the connection is made asynchronously. | **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..89977ba73c231 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 connect to an accelerator GDB server. 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, connect synchronously: the client blocks until the accelerator + /// process is connected and stopped before continuing. If false, the + /// connection is made asynchronously. + 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 70575bf8631b7556a711c930f696a2606427040b Mon Sep 17 00:00:00 2001 From: satya janga <[email protected]> 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. --- .../LLDBServerAcceleratorPlugin.cpp | 9 +- .../gdb-remote/LLDBServerAcceleratorPlugin.h | 27 +++- .../Process/gdb-remote/ProcessGDBRemote.cpp | 57 ++++++++ .../Process/gdb-remote/ProcessGDBRemote.h | 4 + ...oints.py => TestMockAcceleratorActions.py} | 67 +++++++-- ...lugin.py => TestMockAcceleratorPackets.py} | 52 ++++++- lldb/test/API/accelerator/mock/main.c | 12 +- .../Plugins/Accelerator/Mock/CMakeLists.txt | 3 + .../Mock/LLDBServerMockAcceleratorPlugin.cpp | 113 ++++++++++++++- .../Mock/LLDBServerMockAcceleratorPlugin.h | 25 +++- .../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 +++++++ 16 files changed, 807 insertions(+), 29 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/LLDBServerAcceleratorPlugin.cpp b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.cpp index ce3b767832095..302a70976bfa7 100644 --- a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.cpp @@ -8,10 +8,13 @@ #include "LLDBServerAcceleratorPlugin.h" +#include "GDBRemoteCommunicationServerLLGS.h" + using namespace lldb_private::lldb_server; -LLDBServerAcceleratorPlugin::LLDBServerAcceleratorPlugin(GDBServer &gdb_server, - MainLoop &main_loop) - : m_gdb_server(gdb_server), m_main_loop(main_loop) {} +LLDBServerAcceleratorPlugin::LLDBServerAcceleratorPlugin( + GDBServer &native_gdb_server, MainLoop &native_main_loop) + : m_native_gdb_server(native_gdb_server), + m_native_main_loop(native_main_loop) {} LLDBServerAcceleratorPlugin::~LLDBServerAcceleratorPlugin() = default; diff --git a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h index 0230b38e5b0fa..538cc7d365864 100644 --- a/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h +++ b/lldb/source/Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h @@ -10,8 +10,10 @@ #define LLDB_SOURCE_PLUGINS_PROCESS_GDB_REMOTE_LLDBSERVERACCELERATORPLUGIN_H #include "lldb/Host/MainLoop.h" +#include "lldb/Host/common/NativeProcessProtocol.h" #include "lldb/Utility/AcceleratorGDBRemotePackets.h" #include "llvm/ADT/StringRef.h" +#include <memory> #include <optional> namespace lldb_private { @@ -25,8 +27,10 @@ namespace lldb_server { class LLDBServerAcceleratorPlugin { public: using GDBServer = process_gdb_remote::GDBRemoteCommunicationServerLLGS; + using Manager = NativeProcessProtocol::Manager; - LLDBServerAcceleratorPlugin(GDBServer &gdb_server, MainLoop &main_loop); + LLDBServerAcceleratorPlugin(GDBServer &native_gdb_server, + MainLoop &native_main_loop); virtual ~LLDBServerAcceleratorPlugin(); virtual llvm::StringRef GetPluginName() = 0; @@ -36,9 +40,26 @@ class LLDBServerAcceleratorPlugin { virtual llvm::Expected<AcceleratorBreakpointHitResponse> BreakpointWasHit(AcceleratorBreakpointHitArgs &args) = 0; + /// Create an AcceleratorActions whose identifier is unique within this + /// plugin. All actions a plugin creates should be made through this so their + /// identifiers do not collide. + AcceleratorActions GetNewAcceleratorAction() { + return AcceleratorActions(GetPluginName(), + ++m_accelerator_action_identifier); + } + protected: - GDBServer &m_gdb_server; - MainLoop &m_main_loop; + // The CPU process's GDB server that this plugin is attached to. + GDBServer &m_native_gdb_server; + // The native process's main loop. Plugins should prefer to run their + // accelerator server on their own main loop so native and accelerator packet + // handling don't slow each other down. + MainLoop &m_native_main_loop; + // The accelerator process and the in-process GDB server we own to serve it, + // exposing the accelerator as a separate target the client connects to. + std::unique_ptr<Manager> m_process_manager_up; + std::unique_ptr<GDBServer> m_accelerator_gdb_server; + int64_t m_accelerator_action_identifier = 0; }; } // namespace lldb_server diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index a986d0350bf57..1b22aef1b745f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -4238,6 +4238,63 @@ 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. + std::string exe_path = connect_info.exe_path.value_or(""); + std::string triple = connect_info.triple.value_or(""); + 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"); + + // The target's platform is only used to drive the connection here; with no + // exe or triple it defaults to the host platform, and the accelerator's real + // platform is established once it connects and reports its executable, so + // there is nothing meaningful to verify about which platform this is. Still, + // guard against a null platform so we don't crash connecting below. + PlatformSP platform_sp = accelerator_target_sp->GetPlatform(); + if (!platform_sp) + return llvm::createStringError("no platform for the accelerator target"); + 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 new target creation event so clients of the API can detect + // when new targets are created. + 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..3c3071f294a99 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 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..941e0c97a8f2e 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 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 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..18aed944b8d8f 100644 --- a/lldb/test/API/accelerator/mock/main.c +++ b/lldb/test/API/accelerator/mock/main.c @@ -1,15 +1,19 @@ -// The mock accelerator plugin sets its initialize breakpoint on this function. -// Using a dedicated, uniquely named function (rather than "main") ensures the -// mock plugin only affects this test program and not other inferiors launched -// by lldb-server. +// The mock accelerator plugin sets its breakpoints on these dedicated, uniquely +// named functions. void mock_gpu_accelerator_initialize(void) {} 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..7615b7864b203 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) {} + GDBServer &native_gdb_server, MainLoop &native_main_loop) + : LLDBServerAcceleratorPlugin(native_gdb_server, native_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 creates the accelerator target and + // connects it to this server. + m_process_manager_up = + std::make_unique<ProcessMockAccelerator::Manager>(m_native_main_loop); + m_accelerator_gdb_server = std::make_unique<GDBRemoteCommunicationServerLLGS>( + m_native_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_accelerator_gdb_server->SetLaunchInfo(info); + if (Status error = m_accelerator_gdb_server->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"; @@ -21,7 +62,7 @@ llvm::StringRef LLDBServerMockAcceleratorPlugin::GetPluginName() { std::optional<AcceleratorActions> LLDBServerMockAcceleratorPlugin::GetInitializeActions() { - AcceleratorActions actions(GetPluginName(), 1); + AcceleratorActions actions = GetNewAcceleratorAction(); // Set a breakpoint by function name (no shared library scope) on the // dedicated "mock_gpu_accelerator_initialize" hook and ask for the load @@ -47,12 +88,12 @@ 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; - AcceleratorActions actions(GetPluginName(), 2); + AcceleratorActions actions = GetNewAcceleratorAction(); // Breakpoint by function name scoped to a shared library. Tests build to // "a.out", so use that as the shared library name. @@ -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,56 @@ 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 connect to our in-process mock accelerator GDB + // server. + response.disable_bp = true; + response.auto_resume_native = false; + AcceleratorActions actions = GetNewAcceleratorAction(); + 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 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_native_main_loop, [this](std::unique_ptr<Socket> socket) { + std::unique_ptr<Connection> connection_up = + std::make_unique<ConnectionFileDescriptor>(std::move(socket)); + m_accelerator_gdb_server->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..2a25f9ce68a9b 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h @@ -10,13 +10,22 @@ #define LLDB_TOOLS_LLDB_SERVER_PLUGINS_ACCELERATOR_MOCK_LLDBSERVERMOCKACCELERATORPLUGIN_H #include "Plugins/Process/gdb-remote/LLDBServerAcceleratorPlugin.h" +#include "lldb/Host/MainLoopBase.h" + +#include <memory> +#include <vector> namespace lldb_private { + +class TCPSocket; + namespace lldb_server { class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin { public: - LLDBServerMockAcceleratorPlugin(GDBServer &gdb_server, MainLoop &main_loop); + LLDBServerMockAcceleratorPlugin(GDBServer &native_gdb_server, + MainLoop &native_main_loop); + ~LLDBServerMockAcceleratorPlugin() override; llvm::StringRef GetPluginName() override; std::optional<AcceleratorActions> GetInitializeActions() override; @@ -24,6 +33,10 @@ class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin { BreakpointWasHit(AcceleratorBreakpointHitArgs &args) override; private: + // Start listening for the client's 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 +45,16 @@ 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; + + // Listen socket and accept handles for the client's connection to the + // accelerator GDB server (m_accelerator_gdb_server, owned by the base class). + 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..174ab1a0143d4 --- /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 = 1234; +static constexpr lldb::tid_t kMockTid = 3456; + +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 ®_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 ®_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 ®_value) override; + Status WriteRegister(const RegisterInfo *reg_info, + const RegisterValue ®_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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
