[Lldb-commits] [lldb] 82333f7 - [lldb] Handle accelerator plugin breakpoint actions on the client (#201489)

via lldb-commits lldb-commits at lists.llvm.org
Mon Jun 8 04:21:42 PDT 2026


Author: satyanarayana reddy janga
Date: 2026-06-08T06:21:37-05:00
New Revision: 82333f7cfa553133999f4678e79215210d4b19fc

URL: https://github.com/llvm/llvm-project/commit/82333f7cfa553133999f4678e79215210d4b19fc
DIFF: https://github.com/llvm/llvm-project/commit/82333f7cfa553133999f4678e79215210d4b19fc.diff

LOG: [lldb] Handle accelerator plugin breakpoint actions on the client (#201489)

Wire up the client (ProcessGDBRemote) side of the accelerator plugin
breakpoint protocol so the breakpoints requested by lldb-server
accelerator plugins are actually set, hit, and acted upon.

The below is a related packet sequence from the test could help to
better see things in action.
```
python3.12       < 104> send packet: $qSupported:xmlRegisters=i386,arm,mips,arc;multiprocess+;fork-events+;vfork-events+;swbreak+;hwbreak+#cd
python3.12       < 299> read packet: $PacketSize=131072;QStartNoAckMode+;qEcho+;native-signals+;QThreadSuffixSupported+;QListThreadsInStopReply+;qXfer:features:read+;QNonStop+;jMultiBreakpoint+;QPassSignals+;qXfer:auxv:read+;qXfer:libraries-svr4:read+;qXfer:siginfo:read+;accelerator-plugins+;multiprocess+;fork-events+;vfork-events+#86
python3.12       <  32> send packet: $jAcceleratorPluginInitialize#50
python3.12       < 238> read packet: $[{"breakpoints":[{"by_address":null,"by_name":{"function_name":"mock_gpu_accelerator_initialize","shlib":null}],"identifier":1,"symbol_names":["mock_gpu_accelerator_compute"]}]],"identifier":1,"plugin_name":"mock","session_name":""}]]#2f
python3.12       < 104> send packet: $qSupported:xmlRegisters=i386,arm,mips,arc;multiprocess+;fork-events+;vfork-events+;swbreak+;hwbreak+#cd
python3.12       < 299> read packet: $PacketSize=131072;QStartNoAckMode+;qEcho+;native-signals+;QThreadSuffixSupported+;QListThreadsInStopReply+;qXfer:features:read+;QNonStop+;jMultiBreakpoint+;QPassSignals+;qXfer:auxv:read+;qXfer:libraries-svr4:read+;qXfer:siginfo:read+;accelerator-plugins+;multiprocess+;fork-events+;vfork-events+#86
intern-state     < 314> send packet: $jAcceleratorPluginBreakpointHit:{"breakpoint":{"by_address":null,"by_name":{"function_name":"mock_gpu_accelerator_initialize","shlib":null}],"identifier":1,"symbol_names":["mock_gpu_accelerator_compute"]}],"plugin_name":"mock","symbol_values":[{"name":"mock_gpu_accelerator_compute","value":94768064848192}]]}]#ef
intern-state     < 360> read packet: ${"actions":{"breakpoints":[{"by_address":null,"by_name":{"function_name":"mock_gpu_accelerator_finish","shlib":"a.out"}],"identifier":3,"symbol_names":[]}],{"by_address":{"load_address":94768064848192}],"by_name":null,"identifier":2,"symbol_names":[]}]],"identifier":2,"plugin_name":"mock","session_name":""}],"auto_resume_native":false,"disable_bp":true}]#44
intern-state     < 188> send packet: $jAcceleratorPluginBreakpointHit:{"breakpoint":{"by_address":{"load_address":94768064848192}],"by_name":null,"identifier":2,"symbol_names":[]}],"plugin_name":"mock","symbol_values":[]}]#fd
intern-state     < 220> send packet: $jAcceleratorPluginBreakpointHit:{"breakpoint":{"by_address":null,"by_name":{"function_name":"mock_gpu_accelerator_finish","shlib":"a.out"}],"identifier":3,"symbol_names":[]}],"plugin_name":"mock","symbol_values":[]}]#d7
satyajanga at devgpu011:llvm-project  (accelerator-plugin-handle-actions)$

```

Code changes: 

- GDBRemoteCommunicationClient learns the "accelerator-plugins+" feature
from qSupported and gains GetAcceleratorInitializeActions() and
AcceleratorBreakpointHit() to drive the jAcceleratorPluginInitialize and
jAcceleratorPluginBreakpointHit packets.
- ProcessGDBRemote::HandleAcceleratorActions() sets the requested
breakpoints as internal breakpoints with a synchronous callback. When a
callback fires it resolves any requested symbol values, notifies the
plugin, disables the breakpoint and/or auto-resumes the native process
per the response, and handles any further actions (e.g. new breakpoints)
the plugin returns. Initial actions are fetched in DidLaunchOrAttach.
- The mock accelerator plugin now sets its initialize breakpoint on a
dedicated "accelerator_initialize" hook (rather than "main") so it only
affects this test's inferior, and exercises all three breakpoint types:
by name, by name scoped to a shared library, and by address.
- Add an end-to-end API test that launches a real process and verifies
the breakpoints are set, hit, and chained, plus updated packet-level
coverage.

Added: 
    lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py

Modified: 
    lldb/docs/resources/lldbgdbremote.md
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
    lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
    lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
    lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
    lldb/test/API/accelerator/mock/main.c
    lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
    lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h

Removed: 
    


################################################################################
diff  --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 1fc04e5ce9e11..ef42ebd6ae41c 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -2706,6 +2706,20 @@ The packets below support debugging hardware accelerators (e.g. GPUs,
 FPGAs) alongside the native host process via accelerator plugins installed
 in lldb-server.
 
+An accelerator plugin drives the client by returning a list of
+`AcceleratorActions` (currently just breakpoints to set in the native
+process). The client can receive `AcceleratorActions` at two points:
+
+1. Once, in response to the `jAcceleratorPluginInitialize` packet sent when
+   the native process is launched or attached.
+2. In response to a `jAcceleratorPluginBreakpointHit` packet, when a
+   breakpoint the plugin previously requested is hit. The hit response may
+   carry a further set of `AcceleratorActions`.
+
+Each set of `AcceleratorActions` is tagged with a `plugin_name` and an
+`identifier` that is unique within that plugin, so the client can ignore a
+set it has already processed if the same actions are delivered again.
+
 ### jAcceleratorPluginInitialize
 
 This packet requests initialization data from all accelerator plugins
@@ -2746,9 +2760,19 @@ If no accelerator plugins are installed, the server does not advertise the
 `accelerator-plugins+` feature and this packet should not be sent.
 
 Each `accelerator_action` may include a `breakpoints` array requesting
-breakpoints to be set in the native process. See
-`jAcceleratorPluginBreakpointHit` for the callback when those breakpoints
-are hit.
+breakpoints to be set in the native process. The client sets each of these
+as an internal breakpoint and sends a `jAcceleratorPluginBreakpointHit`
+packet when one is hit. Each breakpoint object has the following fields:
+
+| Key            | Type    | Description |
+|----------------|---------|-------------|
+| `identifier`   | integer | Identifier for this breakpoint, unique within the plugin. It is echoed back in the `jAcceleratorPluginBreakpointHit` packet so the plugin knows which breakpoint was hit. |
+| `by_name`      | object  | Set the breakpoint by function name. Contains `function_name` (string) and an optional `shlib` (string) to scope the breakpoint to a single shared library. |
+| `by_address`   | object  | Set the breakpoint by load address. Contains `load_address` (integer). |
+| `symbol_names` | array   | Symbol names whose load addresses the client should resolve and deliver in the `jAcceleratorPluginBreakpointHit` packet when this breakpoint is hit. May be empty. |
+
+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
@@ -2774,7 +2798,7 @@ The request JSON has the following fields:
 |-----------------|--------|-------------|
 | `plugin_name`   | string | Name of the plugin that requested the breakpoint. |
 | `breakpoint`    | object | The `AcceleratorBreakpointInfo` that was hit, including its `identifier`. |
-| `symbol_values` | array  | Array of `{"name": "<name>", "value": <addr>}` for each symbol requested in the breakpoint's `symbol_names`. |
+| `symbol_values` | array  | Array of `{"name": "<name>", "value": <addr>}`, one entry for each name in the breakpoint's `symbol_names`. `value` is the resolved load address, or `null` if the client could not find the symbol or convert it to a load address. |
 
 The response JSON has the following fields:
 

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 8df7936786b04..391b2e6a78d66 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -23,6 +23,7 @@
 #include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Target/UnixSignals.h"
+#include "lldb/Utility/AcceleratorGDBRemotePackets.h"
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/DataBufferHeap.h"
 #include "lldb/Utility/LLDBAssert.h"
@@ -39,6 +40,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/Config/llvm-config.h" // for LLVM_ENABLE_ZLIB
+#include "llvm/Support/ErrorExtras.h"
 #include "llvm/Support/JSON.h"
 
 #if HAVE_LIBCOMPRESSION
@@ -223,6 +225,79 @@ bool GDBRemoteCommunicationClient::GetMultiBreakpointSupported() {
   return m_supports_multi_breakpoint == eLazyBoolYes;
 }
 
+bool GDBRemoteCommunicationClient::GetAcceleratorPluginsSupported() {
+  if (m_supports_accelerator_plugins == eLazyBoolCalculate)
+    GetRemoteQSupported();
+  return m_supports_accelerator_plugins == eLazyBoolYes;
+}
+
+llvm::Expected<std::vector<AcceleratorActions>>
+GDBRemoteCommunicationClient::GetAcceleratorInitializeActions() {
+  // Get the initial actions (e.g. breakpoints to set) requested by any
+  // accelerator plugins using the "jAcceleratorPluginInitialize" packet. This
+  // is sent once when a native process is launched or attached. The empty
+  // state (no plugins / no actions) is modelled as an empty vector; errors are
+  // returned to the caller to report.
+  if (!GetAcceleratorPluginsSupported())
+    return std::vector<AcceleratorActions>();
+
+  StringExtractorGDBRemote response;
+  response.SetResponseValidatorToJSON();
+  if (SendPacketAndWaitForResponse("jAcceleratorPluginInitialize", response) !=
+      PacketResult::Success)
+    return llvm::createStringError(
+        "failed to send jAcceleratorPluginInitialize packet");
+
+  if (response.IsUnsupportedResponse())
+    return std::vector<AcceleratorActions>();
+
+  if (response.IsErrorResponse())
+    return response.GetStatus().takeError();
+
+  llvm::Expected<std::vector<AcceleratorActions>> actions =
+      llvm::json::parse<std::vector<AcceleratorActions>>(response.Peek(),
+                                                         "AcceleratorActions");
+  if (actions)
+    return actions;
+
+  // A bare JSON parse error (e.g. "missing comma at line 4") is meaningless on
+  // its own, so include both the full response and the parse error; the caller
+  // logs this and the user can spot the problem in the response.
+  return llvm::createStringErrorV(
+      "malformed jAcceleratorPluginInitialize response '{0}': {1}",
+      response.GetStringRef(), llvm::toString(actions.takeError()));
+}
+
+llvm::Expected<AcceleratorBreakpointHitResponse>
+GDBRemoteCommunicationClient::AcceleratorBreakpointHit(
+    const AcceleratorBreakpointHitArgs &args) {
+  StreamGDBRemote packet;
+  packet.PutCString("jAcceleratorPluginBreakpointHit:");
+  packet.PutAsJSON(args, /*hex_ascii=*/false);
+
+  StringExtractorGDBRemote response;
+  if (SendPacketAndWaitForResponse(packet.GetString(), response) !=
+      PacketResult::Success)
+    return llvm::createStringError(
+        "failed to send jAcceleratorPluginBreakpointHit packet");
+
+  if (response.IsErrorResponse())
+    return response.GetStatus().takeError();
+
+  llvm::Expected<AcceleratorBreakpointHitResponse> hit_response =
+      llvm::json::parse<AcceleratorBreakpointHitResponse>(
+          response.Peek(), "AcceleratorBreakpointHitResponse");
+  if (hit_response)
+    return hit_response;
+
+  // A bare JSON parse error (e.g. "missing comma at line 4") is meaningless on
+  // its own, so include both the full response and the parse error; the caller
+  // logs this and the user can spot the problem in the response.
+  return llvm::createStringErrorV(
+      "malformed jAcceleratorPluginBreakpointHit response '{0}': {1}",
+      response.GetStringRef(), llvm::toString(hit_response.takeError()));
+}
+
 bool GDBRemoteCommunicationClient::QueryNoAckModeSupported() {
   if (m_supports_not_sending_acks == eLazyBoolCalculate) {
     m_send_acks = true;
@@ -321,6 +396,7 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
     m_x_packet_state.reset();
     m_supports_reverse_continue = eLazyBoolCalculate;
     m_supports_reverse_step = eLazyBoolCalculate;
+    m_supports_accelerator_plugins = eLazyBoolCalculate;
     m_supports_qProcessInfoPID = true;
     m_supports_qfProcessInfo = true;
     m_supports_qUserName = true;
@@ -381,6 +457,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
   m_supports_reverse_step = eLazyBoolNo;
   m_supports_multi_mem_read = eLazyBoolNo;
   m_supports_multi_breakpoint = eLazyBoolNo;
+  m_supports_accelerator_plugins = eLazyBoolNo;
 
   m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
                                   // not, we assume no limit
@@ -444,6 +521,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
         m_supports_multi_mem_read = eLazyBoolYes;
       else if (x == "jMultiBreakpoint+")
         m_supports_multi_breakpoint = eLazyBoolYes;
+      else if (x == "accelerator-plugins+")
+        m_supports_accelerator_plugins = eLazyBoolYes;
       // Look for a list of compressions in the features list e.g.
       // qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-
       // deflate,lzma

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 79ca0bcd3ed22..235121d88c9a8 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "lldb/Host/File.h"
+#include "lldb/Utility/AcceleratorGDBRemotePackets.h"
 #include "lldb/Utility/AddressableBits.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/GDBRemote.h"
@@ -346,6 +347,26 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
 
   bool GetMultiBreakpointSupported();
 
+  bool GetAcceleratorPluginsSupported();
+
+  /// Send the "jAcceleratorPluginInitialize" packet and return the actions
+  /// requested by each accelerator plugin installed in lldb-server. The packet
+  /// is only sent if the lldb-server advertised accelerator plugin support via
+  /// "accelerator-plugins+" in its qSupported response; otherwise (and when no
+  /// plugin returns actions) this returns an empty vector. Errors are returned
+  /// for the caller to report.
+  llvm::Expected<std::vector<AcceleratorActions>>
+  GetAcceleratorInitializeActions();
+
+  /// Send the "jAcceleratorPluginBreakpointHit" packet to notify the
+  /// accelerator plugin that one of its requested breakpoints was hit, and
+  /// return the plugin's response. This is only used when the lldb-server
+  /// advertised accelerator plugin support via "accelerator-plugins+" in its
+  /// qSupported response, since the breakpoints that trigger it are only set in
+  /// that case. Errors are returned for the caller to report.
+  llvm::Expected<AcceleratorBreakpointHitResponse>
+  AcceleratorBreakpointHit(const AcceleratorBreakpointHitArgs &args);
+
   LazyBool SupportsAllocDeallocMemory() // const
   {
     // Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -582,6 +603,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
   LazyBool m_supports_reverse_step = eLazyBoolCalculate;
   LazyBool m_supports_multi_mem_read = eLazyBoolCalculate;
   LazyBool m_supports_multi_breakpoint = eLazyBoolCalculate;
+  LazyBool m_supports_accelerator_plugins = eLazyBoolCalculate;
 
   bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
       m_supports_qUserName : 1, m_supports_qGroupName : 1,

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index f6eaf5851338b..c21257113622a 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -23,6 +23,8 @@
 #include <ctime>
 #include <sys/types.h>
 
+#include "lldb/Breakpoint/Breakpoint.h"
+#include "lldb/Breakpoint/StoppointCallbackContext.h"
 #include "lldb/Breakpoint/Watchpoint.h"
 #include "lldb/Breakpoint/WatchpointAlgorithms.h"
 #include "lldb/Breakpoint/WatchpointResource.h"
@@ -52,6 +54,8 @@
 #include "lldb/Interpreter/Options.h"
 #include "lldb/Interpreter/Property.h"
 #include "lldb/Symbol/ObjectFile.h"
+#include "lldb/Symbol/Symbol.h"
+#include "lldb/Symbol/SymbolContext.h"
 #include "lldb/Target/ABI.h"
 #include "lldb/Target/DynamicLoader.h"
 #include "lldb/Target/MemoryRegionInfo.h"
@@ -61,7 +65,9 @@
 #include "lldb/Target/TargetList.h"
 #include "lldb/Target/ThreadPlanCallFunction.h"
 #include "lldb/Utility/Args.h"
+#include "lldb/Utility/Baton.h"
 #include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/FileSpecList.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/StreamString.h"
@@ -1060,6 +1066,21 @@ void ProcessGDBRemote::DidLaunchOrAttach(ArchSpec &process_arch) {
     else
       SetUnixSignals(UnixSignals::Create(GetTarget().GetArchitecture()));
   }
+
+  // Ask any accelerator plugins installed in lldb-server for their initial
+  // actions (e.g. breakpoints to set in the native process).
+  llvm::Expected<std::vector<AcceleratorActions>> init_actions =
+      m_gdb_comm.GetAcceleratorInitializeActions();
+  if (!init_actions) {
+    LLDB_LOG_ERROR(log, init_actions.takeError(),
+                   "failed to get accelerator initialize actions: {0}");
+  } else {
+    for (const AcceleratorActions &actions : *init_actions) {
+      if (llvm::Error error = HandleAcceleratorActions(actions))
+        LLDB_LOG_ERROR(log, std::move(error),
+                       "failed to handle accelerator actions: {0}");
+    }
+  }
 }
 
 void ProcessGDBRemote::LoadStubBinaries() {
@@ -4124,6 +4145,190 @@ bool ProcessGDBRemote::NewThreadNotifyBreakpointHit(
   return false;
 }
 
+namespace {
+/// Baton that carries the breakpoint hit arguments to the accelerator plugin
+/// breakpoint callback.
+class AcceleratorBreakpointCallbackBaton
+    : public TypedBaton<AcceleratorBreakpointHitArgs> {
+public:
+  explicit AcceleratorBreakpointCallbackBaton(
+      std::unique_ptr<AcceleratorBreakpointHitArgs> data)
+      : TypedBaton(std::move(data)) {}
+};
+} // namespace
+
+llvm::Error
+ProcessGDBRemote::HandleAcceleratorActions(const AcceleratorActions &actions) {
+  Log *log = GetLog(GDBRLog::Process);
+
+  // The same set of actions can be delivered to the client more than once: a
+  // plugin may keep reporting the same actions (with the same identifier) on
+  // subsequent native stops until its state advances. The identifier uniquely
+  // names a set of actions for a plugin, so skip any set we have already
+  // processed to avoid re-running its side effects (e.g. setting the same
+  // breakpoints again).
+  auto it = m_processed_accelerator_actions.find(actions.plugin_name);
+  if (it != m_processed_accelerator_actions.end() &&
+      it->second == actions.identifier) {
+    LLDB_LOG(log,
+             "ProcessGDBRemote::HandleAcceleratorActions skipping already "
+             "processed actions for plugin '{0}' with identifier {1}",
+             actions.plugin_name, actions.identifier);
+    return llvm::Error::success();
+  }
+  m_processed_accelerator_actions[actions.plugin_name] = actions.identifier;
+
+  // Handle each kind of action. More action kinds will be handled here in the
+  // future, so only return early on error; otherwise fall through so the next
+  // kind of action still gets a chance to run.
+  if (!actions.breakpoints.empty()) {
+    if (llvm::Error error = HandleAcceleratorBreakpoints(actions))
+      return error;
+  }
+
+  return llvm::Error::success();
+}
+
+llvm::Error ProcessGDBRemote::HandleAcceleratorBreakpoints(
+    const AcceleratorActions &actions) {
+  Target &target = GetTarget();
+  llvm::Error error = llvm::Error::success();
+  for (const AcceleratorBreakpointInfo &bp : actions.breakpoints) {
+    // Carry data with the breakpoint so the callback can notify the plugin
+    // when the breakpoint is hit.
+    auto args_up = std::make_unique<AcceleratorBreakpointHitArgs>();
+    args_up->plugin_name = actions.plugin_name;
+    args_up->breakpoint = bp;
+
+    // Each breakpoint must specify exactly one of by_name or by_address. Bad
+    // breakpoints are collected as errors but don't stop the remaining ones
+    // from being set.
+    BreakpointSP bp_sp;
+    if (bp.by_name && bp.by_address) {
+      error = llvm::joinErrors(
+          std::move(error),
+          llvm::createStringErrorV(
+              "accelerator breakpoint {0} specifies both a by_name and a "
+              "by_address specification",
+              bp.identifier));
+      continue;
+    } else if (bp.by_name) {
+      FileSpecList bp_modules;
+      if (bp.by_name->shlib && !bp.by_name->shlib->empty())
+        bp_modules.Append(FileSpec(*bp.by_name->shlib));
+      bp_sp = target.CreateBreakpoint(
+          bp_modules.GetSize() ? &bp_modules : nullptr, // Containing modules.
+          nullptr,                                      // Containing source.
+          bp.by_name->function_name.c_str(),            // Function name.
+          eFunctionNameTypeFull,                        // Function name type.
+          eLanguageTypeUnknown,                         // Language type.
+          0,                                            // Byte offset.
+          false,                                        // Offset is insn count.
+          eLazyBoolNo,                                  // Skip prologue.
+          true,                                         // Internal breakpoint.
+          false);                                       // Request hardware.
+    } else if (bp.by_address) {
+      bp_sp = target.CreateBreakpoint(bp.by_address->load_address,
+                                      /*internal=*/true,
+                                      /*request_hardware=*/false);
+    } else {
+      error = llvm::joinErrors(
+          std::move(error),
+          llvm::createStringErrorV(
+              "accelerator breakpoint {0} has neither a by_name nor a "
+              "by_address specification",
+              bp.identifier));
+      continue;
+    }
+
+    if (!bp_sp) {
+      error = llvm::joinErrors(
+          std::move(error),
+          llvm::createStringErrorV("failed to set accelerator breakpoint {0}",
+                                   bp.identifier));
+      continue;
+    }
+
+    // Give the internal breakpoint a meaningful description for stop reasons,
+    // including the plugin that requested it.
+    std::string kind =
+        llvm::formatv("accelerator-plugin ({0})", actions.plugin_name);
+    bp_sp->SetBreakpointKind(kind.c_str());
+    auto baton_sp = std::make_shared<AcceleratorBreakpointCallbackBaton>(
+        std::move(args_up));
+    bp_sp->SetCallback(AcceleratorBreakpointHitCallback, baton_sp,
+                       /*is_synchronous=*/true);
+  }
+  return error;
+}
+
+bool ProcessGDBRemote::AcceleratorBreakpointHitCallback(
+    void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id,
+    lldb::user_id_t break_loc_id) {
+  ProcessSP process_sp = context->exe_ctx_ref.GetProcessSP();
+  ProcessGDBRemote *process = static_cast<ProcessGDBRemote *>(process_sp.get());
+  return process->AcceleratorBreakpointHit(baton, context, break_id,
+                                           break_loc_id);
+}
+
+bool ProcessGDBRemote::AcceleratorBreakpointHit(
+    void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id,
+    lldb::user_id_t break_loc_id) {
+  AcceleratorBreakpointHitArgs *callback_data =
+      static_cast<AcceleratorBreakpointHitArgs *>(baton);
+  // Copy the args so we can fill in requested symbol values before notifying
+  // lldb-server.
+  AcceleratorBreakpointHitArgs args = *callback_data;
+  Target &target = GetTarget();
+
+  const std::vector<std::string> &symbol_names = args.breakpoint.symbol_names;
+  args.symbol_values.resize(symbol_names.size());
+  for (size_t i = 0; i < symbol_names.size(); ++i) {
+    args.symbol_values[i].name = symbol_names[i];
+    SymbolContextList sc_list;
+    target.GetImages().FindSymbolsWithNameAndType(ConstString(symbol_names[i]),
+                                                  eSymbolTypeAny, sc_list);
+    for (const SymbolContext &sc : sc_list) {
+      if (!sc.symbol)
+        continue;
+      addr_t load_addr = sc.symbol->GetAddress().GetLoadAddress(&target);
+      if (load_addr != LLDB_INVALID_ADDRESS) {
+        args.symbol_values[i].value = load_addr;
+        break;
+      }
+    }
+  }
+
+  Log *log = GetLog(GDBRLog::Process);
+  llvm::Expected<AcceleratorBreakpointHitResponse> response =
+      m_gdb_comm.AcceleratorBreakpointHit(args);
+  if (!response) {
+    LLDB_LOG_ERROR(log, response.takeError(),
+                   "accelerator breakpoint hit notification failed: {0}");
+    // We could not reach the plugin, so auto-resume rather than stopping the
+    // native process at an internal breakpoint the user can't see.
+    return false;
+  }
+
+  // Disable the breakpoint if requested, but keep it around so its hit count
+  // and other stats remain visible.
+  if (response->disable_bp) {
+    if (BreakpointSP bp_sp = target.GetBreakpointByID(break_id))
+      bp_sp->SetEnabled(false);
+  }
+
+  // The plugin may request new actions (e.g. additional breakpoints) in
+  // response to this breakpoint being hit.
+  if (response->actions) {
+    if (llvm::Error error = HandleAcceleratorActions(*response->actions))
+      LLDB_LOG_ERROR(log, std::move(error),
+                     "failed to handle accelerator actions: {0}");
+  }
+
+  // Returning true stops the native process; false auto-resumes it.
+  return !response->auto_resume_native;
+}
+
 Status ProcessGDBRemote::UpdateAutomaticSignalFiltering() {
   Log *log = GetLog(GDBRLog::Process);
   LLDB_LOG(log, "Check if need to update ignored signals");

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 2c3dfa9afe286..0a3386082c388 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -478,6 +478,35 @@ class ProcessGDBRemote : public Process,
   /// Remove the breakpoints associated with thread creation from the Target.
   void RemoveNewThreadBreakpoints();
 
+  /// Handle a set of actions requested by an accelerator plugin. Currently this
+  /// only sets the breakpoints requested in \a actions.
+  llvm::Error HandleAcceleratorActions(const AcceleratorActions &actions);
+
+  /// Set the breakpoints requested by an accelerator plugin as internal
+  /// breakpoints with a callback that notifies the plugin when they are hit.
+  /// Returns an error if any breakpoint could not be set; the remaining
+  /// breakpoints are still set.
+  llvm::Error HandleAcceleratorBreakpoints(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
+  /// response.
+  static bool AcceleratorBreakpointHitCallback(
+      void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id,
+      lldb::user_id_t break_loc_id);
+
+  bool AcceleratorBreakpointHit(void *baton, StoppointCallbackContext *context,
+                                lldb::user_id_t break_id,
+                                lldb::user_id_t break_loc_id);
+
+  /// Tracks the last action identifier handled per accelerator plugin so the
+  /// same actions are not processed more than once. Accelerator actions can
+  /// arrive in a stop reply packet, and if the debugger re-requests the stop
+  /// reply with a '?' packet we can be handed the same actions again; this
+  /// guards against handling them (e.g. setting the same breakpoints) twice.
+  std::map<std::string, int64_t> m_processed_accelerator_actions;
+
   // ContinueDelegate interface
   void HandleAsyncStdout(llvm::StringRef out) override;
   void HandleAsyncMisc(llvm::StringRef data) override;

diff  --git a/lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py
new file mode 100644
index 0000000000000..885e73e3cec15
--- /dev/null
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py
@@ -0,0 +1,104 @@
+"""
+End-to-end test for accelerator plugin breakpoints.
+
+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.
+"""
+
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import configuration
+
+
+def uint64_to_int64(value):
+    """Reinterpret an unsigned 64-bit value as a signed 64-bit integer."""
+    if value >= (1 << 63):
+        return value - (1 << 64)
+    return value
+
+
+class MockAcceleratorBreakpointsTestCase(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def setUp(self):
+        super().setUp()
+        if "mock-accelerator" not in configuration.enabled_plugins:
+            self.skipTest("mock-accelerator plugin is not enabled")
+
+    def check_accelerator_breakpoint_stop(self, process, function_name, hit_count=None):
+        """Verify the process stopped at an internal accelerator breakpoint in
+        the given function. If hit_count is not None, also verify the
+        breakpoint's hit count. Returns the breakpoint."""
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        thread = process.GetSelectedThread()
+
+        # The stop must be due to a breakpoint, and the frame must be in the
+        # expected function.
+        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+        frame = thread.GetFrameAtIndex(0)
+        self.assertEqual(frame.GetFunctionName(), function_name)
+
+        # The breakpoint id is carried in the stop reason data. Accelerator
+        # breakpoints are internal, so they are not in the public breakpoint
+        # list, but can still be looked up by id. The datum is an unsigned
+        # 64-bit value holding the (signed) breakpoint id; internal ids are
+        # negative.
+        self.assertGreater(thread.GetStopReasonDataCount(), 0)
+        bp_id = uint64_to_int64(thread.GetStopReasonDataAtIndex(0))
+        bp = process.GetTarget().FindBreakpointByID(bp_id)
+        self.assertTrue(bp.IsValid())
+        self.assertTrue(bp.IsInternal(), "accelerator breakpoints are internal")
+
+        if hit_count is not None:
+            self.assertEqual(bp.GetHitCount(), hit_count)
+        return bp
+
+    @skipIfRemote
+    @add_test_categories(["llgs"])
+    def test_accelerator_breakpoints(self):
+        """The mock accelerator plugin drives breakpoints in the inferior."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        # Launching the process should stop at the
+        # "mock_gpu_accelerator_initialize" breakpoint that the mock plugin
+        # requested via jAcceleratorPluginInitialize (it requests the native
+        # process not auto-resume). This is a breakpoint by name with no shared
+        # library.
+        process = target.LaunchSimple(None, None, self.get_process_working_directory())
+        self.assertTrue(process, PROCESS_IS_VALID)
+        self.check_accelerator_breakpoint_stop(
+            process, "mock_gpu_accelerator_initialize", hit_count=1
+        )
+
+        # The accelerator breakpoint was set and hit, yet it is internal, so it
+        # never appears in the public breakpoint list.
+        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.
+        process.Continue()
+        self.check_accelerator_breakpoint_stop(
+            process, "mock_gpu_accelerator_compute", hit_count=1
+        )
+
+        process.Continue()
+        self.check_accelerator_breakpoint_stop(
+            process, "mock_gpu_accelerator_finish", hit_count=1
+        )
+
+        # No more accelerator breakpoints; the process runs to exit.
+        process.Continue()
+        self.assertState(process.GetState(), lldb.eStateExited)
+        self.assertEqual(process.GetExitStatus(), 0)

diff  --git a/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py b/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
index 8e2bd0d604366..1eeeb7f731eb7 100644
--- a/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
@@ -81,7 +81,12 @@ def test_jAcceleratorPluginInitialize_returns_breakpoints(self):
         bp = breakpoints[0]
         self.assertIn("identifier", bp)
         self.assertIn("by_name", bp)
-        self.assertEqual(bp["by_name"]["function_name"], "main")
+        self.assertEqual(
+            bp["by_name"]["function_name"], "mock_gpu_accelerator_initialize"
+        )
+        # The initialize breakpoint requests the "mock_gpu_accelerator_compute"
+        # symbol value.
+        self.assertEqual(bp["symbol_names"], ["mock_gpu_accelerator_compute"])
 
     @add_test_categories(["llgs"])
     def test_jAcceleratorPluginBreakpointHit_response(self):
@@ -92,13 +97,18 @@ def test_jAcceleratorPluginBreakpointHit_response(self):
         self.add_qSupported_packets()
         self.expect_gdbremote_sequence()
 
+        # Simulate the initialize breakpoint being hit, supplying the requested
+        # "mock_gpu_accelerator_compute" symbol value so the plugin sets a
+        # breakpoint by address.
         hit_args = {
             "plugin_name": "mock",
             "breakpoint": {
                 "identifier": 1,
-                "symbol_names": [],
+                "symbol_names": ["mock_gpu_accelerator_compute"],
             },
-            "symbol_values": [],
+            "symbol_values": [
+                {"name": "mock_gpu_accelerator_compute", "value": 0x4000}
+            ],
         }
         hit_json = json.dumps(hit_args, separators=(",", ":"))
         escaped_json = escape_binary(hit_json)
@@ -111,12 +121,22 @@ def test_jAcceleratorPluginBreakpointHit_response(self):
         self.assertIn("auto_resume_native", response)
         self.assertFalse(response["auto_resume_native"])
 
-        # Verify that the response includes new actions with a breakpoint.
+        # Verify the response includes new actions with both a by-name (scoped
+        # to a shared library) and a by-address breakpoint.
         self.assertIn("actions", response)
         actions = response["actions"]
         self.assertEqual(actions["plugin_name"], "mock")
         self.assertIn("breakpoints", actions)
-        new_bps = actions["breakpoints"]
-        self.assertGreater(len(new_bps), 0)
-        self.assertEqual(new_bps[0]["identifier"], 2)
-        self.assertEqual(new_bps[0]["by_name"]["function_name"], "exit")
+        new_bps = {bp["identifier"]: bp for bp in actions["breakpoints"]}
+
+        # Breakpoint by name scoped to a shared library.
+        self.assertIn(3, new_bps)
+        self.assertEqual(
+            new_bps[3]["by_name"]["function_name"], "mock_gpu_accelerator_finish"
+        )
+        self.assertEqual(new_bps[3]["by_name"]["shlib"], "a.out")
+
+        # Breakpoint by address, using the supplied
+        # "mock_gpu_accelerator_compute" symbol value.
+        self.assertIn(2, new_bps)
+        self.assertEqual(new_bps[2]["by_address"]["load_address"], 0x4000)

diff  --git a/lldb/test/API/accelerator/mock/main.c b/lldb/test/API/accelerator/mock/main.c
index 78f2de106c92b..5f4c7b1fd3147 100644
--- a/lldb/test/API/accelerator/mock/main.c
+++ b/lldb/test/API/accelerator/mock/main.c
@@ -1 +1,16 @@
-int main(void) { return 0; }
+// 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.
+void mock_gpu_accelerator_initialize(void) {}
+
+int mock_gpu_accelerator_compute(int x) { return x * 2; }
+
+int mock_gpu_accelerator_finish(void) { return 0; }
+
+int main(void) {
+  mock_gpu_accelerator_initialize();
+  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/LLDBServerMockAcceleratorPlugin.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
index cb2951919537a..f89fb90e26aa6 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
@@ -23,9 +23,17 @@ std::optional<AcceleratorActions>
 LLDBServerMockAcceleratorPlugin::GetInitializeActions() {
   AcceleratorActions actions(GetPluginName(), 1);
 
+  // Set a breakpoint by function name (no shared library scope) on the
+  // dedicated "mock_gpu_accelerator_initialize" hook and ask for the load
+  // address of "mock_gpu_accelerator_compute" to be delivered when it is hit.
+  // Using a dedicated, uniquely named function (rather than "main") keeps this
+  // mock from affecting other inferiors that lldb-server launches when the
+  // plugin is compiled in.
   AcceleratorBreakpointInfo bp;
   bp.identifier = kBreakpointIDInitialize;
-  bp.by_name = AcceleratorBreakpointByName{std::nullopt, "main"};
+  bp.by_name = AcceleratorBreakpointByName{std::nullopt,
+                                           "mock_gpu_accelerator_initialize"};
+  bp.symbol_names.push_back("mock_gpu_accelerator_compute");
   actions.breakpoints.push_back(std::move(bp));
 
   return actions;
@@ -35,16 +43,45 @@ llvm::Expected<AcceleratorBreakpointHitResponse>
 LLDBServerMockAcceleratorPlugin::BreakpointWasHit(
     AcceleratorBreakpointHitArgs &args) {
   AcceleratorBreakpointHitResponse response;
-  if (args.breakpoint.identifier == kBreakpointIDInitialize) {
+
+  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.
     response.disable_bp = true;
     response.auto_resume_native = false;
 
-    AcceleratorActions actions(GetPluginName(), kBreakpointIDExit);
-    AcceleratorBreakpointInfo bp;
-    bp.identifier = kBreakpointIDExit;
-    bp.by_name = AcceleratorBreakpointByName{std::nullopt, "exit"};
-    actions.breakpoints.push_back(std::move(bp));
+    AcceleratorActions actions(GetPluginName(), 2);
+
+    // Breakpoint by function name scoped to a shared library. Tests build to
+    // "a.out", so use that as the shared library name.
+    AcceleratorBreakpointInfo by_name_shlib;
+    by_name_shlib.identifier = kBreakpointIDByNameShlib;
+    by_name_shlib.by_name =
+        AcceleratorBreakpointByName{"a.out", "mock_gpu_accelerator_finish"};
+    actions.breakpoints.push_back(std::move(by_name_shlib));
+
+    // Breakpoint by address, using the "mock_gpu_accelerator_compute" symbol
+    // value that was delivered with this breakpoint hit.
+    if (std::optional<uint64_t> compute_addr =
+            args.GetSymbolValue("mock_gpu_accelerator_compute")) {
+      AcceleratorBreakpointInfo by_address;
+      by_address.identifier = kBreakpointIDByAddress;
+      by_address.by_address = AcceleratorBreakpointByAddress{*compute_addr};
+      actions.breakpoints.push_back(std::move(by_address));
+    }
+
     response.actions = std::move(actions);
+    break;
   }
+  case kBreakpointIDByAddress:
+  case kBreakpointIDByNameShlib:
+    // Disable and stop the native process so the hit is observable.
+    response.disable_bp = true;
+    response.auto_resume_native = false;
+    break;
+  }
+
   return response;
 }

diff  --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
index 1d3d03121c02e..7a1b57e0bb0b7 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.h
@@ -24,8 +24,14 @@ class LLDBServerMockAcceleratorPlugin : public LLDBServerAcceleratorPlugin {
   BreakpointWasHit(AcceleratorBreakpointHitArgs &args) override;
 
 private:
+  // Breakpoint set during initialization, by function name with no shared
+  // library. Requests the "compute" symbol value when hit.
   static constexpr int64_t kBreakpointIDInitialize = 1;
-  static constexpr int64_t kBreakpointIDExit = 2;
+  // Breakpoint set by address, using the "compute" symbol value delivered when
+  // the initialize breakpoint was hit.
+  static constexpr int64_t kBreakpointIDByAddress = 2;
+  // Breakpoint set by function name scoped to a shared library.
+  static constexpr int64_t kBreakpointIDByNameShlib = 3;
 };
 
 } // namespace lldb_server


        


More information about the lldb-commits mailing list