[Lldb-commits] [lldb] [lldb] Handle accelerator plugin breakpoint actions on the client (PR #201489)
satyanarayana reddy janga via lldb-commits
lldb-commits at lists.llvm.org
Thu Jun 4 05:26:22 PDT 2026
https://github.com/satyajanga updated https://github.com/llvm/llvm-project/pull/201489
>From 4a37ef9aad71ec4d82ea4b671e9cee1cf03ba127 Mon Sep 17 00:00:00 2001
From: satya janga <satyajanga at fb.com>
Date: Wed, 3 Jun 2026 18:33:55 -0700
Subject: [PATCH] [lldb] Handle accelerator plugin breakpoint actions on the
client
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.
- 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.
---
.../GDBRemoteCommunicationClient.cpp | 79 +++++++++
.../gdb-remote/GDBRemoteCommunicationClient.h | 15 ++
.../Process/gdb-remote/ProcessGDBRemote.cpp | 167 ++++++++++++++++++
.../Process/gdb-remote/ProcessGDBRemote.h | 24 +++
.../mock/TestMockAcceleratorBreakpoints.py | 75 ++++++++
.../mock/TestMockAcceleratorPlugin.py | 27 ++-
lldb/test/API/accelerator/mock/main.c | 16 +-
.../Mock/LLDBServerMockAcceleratorPlugin.cpp | 48 ++++-
.../Mock/LLDBServerMockAcceleratorPlugin.h | 8 +-
9 files changed, 442 insertions(+), 17 deletions(-)
create mode 100644 lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 8df7936786b04..316312822b8a2 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -15,6 +15,7 @@
#include <optional>
#include <sstream>
+#include "lldb/Core/Debugger.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/SafeMachO.h"
@@ -23,6 +24,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"
@@ -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;
+}
+
+std::optional<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.
+ if (!GetAcceleratorPluginsSupported())
+ return std::nullopt;
+
+ StringExtractorGDBRemote response;
+ response.SetResponseValidatorToJSON();
+ if (SendPacketAndWaitForResponse("jAcceleratorPluginInitialize", response) !=
+ PacketResult::Success)
+ return std::nullopt;
+
+ if (response.IsUnsupportedResponse())
+ return std::nullopt;
+
+ if (response.IsErrorResponse()) {
+ Debugger::ReportError(response.GetStatus().AsCString());
+ return std::nullopt;
+ }
+
+ llvm::Expected<std::vector<AcceleratorActions>> actions =
+ llvm::json::parse<std::vector<AcceleratorActions>>(response.Peek(),
+ "AcceleratorActions");
+ if (actions)
+ return std::move(*actions);
+
+ // JSON parsing errors won't make sense to the user, so don't show them.
+ llvm::consumeError(actions.takeError());
+ Debugger::ReportError(
+ llvm::formatv("malformed jAcceleratorPluginInitialize response: {0}",
+ response.GetStringRef()));
+ return std::nullopt;
+}
+
+std::optional<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 std::nullopt;
+
+ if (response.IsErrorResponse()) {
+ Debugger::ReportError(response.GetStatus().AsCString());
+ return std::nullopt;
+ }
+
+ llvm::Expected<AcceleratorBreakpointHitResponse> hit_response =
+ llvm::json::parse<AcceleratorBreakpointHitResponse>(
+ response.Peek(), "AcceleratorBreakpointHitResponse");
+ if (hit_response)
+ return std::move(*hit_response);
+
+ // JSON parsing errors won't make sense to the user, so don't show them.
+ llvm::consumeError(hit_response.takeError());
+ Debugger::ReportError(
+ llvm::formatv("malformed jAcceleratorPluginBreakpointHit response: {0}",
+ response.GetStringRef()));
+ return std::nullopt;
+}
+
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..09ebd8ef726d7 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,19 @@ 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.
+ std::optional<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.
+ std::optional<AcceleratorBreakpointHitResponse>
+ AcceleratorBreakpointHit(const AcceleratorBreakpointHitArgs &args);
+
LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -582,6 +596,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..609e5ec97cf77 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,19 @@ 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).
+ if (std::optional<std::vector<AcceleratorActions>> init_actions =
+ m_gdb_comm.GetAcceleratorInitializeActions()) {
+ for (const AcceleratorActions &actions : *init_actions) {
+ if (Status error = HandleAcceleratorActions(actions); error.Fail())
+ Debugger::ReportError(llvm::formatv(
+ "failed to handle accelerator actions for plugin "
+ "'{0}': {1}\nActions:\n{2}\n",
+ actions.plugin_name, error.AsCString(), toJSON(actions)));
+ }
+ }
}
void ProcessGDBRemote::LoadStubBinaries() {
@@ -4124,6 +4143,154 @@ 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
+
+Status
+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 Status();
+ }
+ m_processed_accelerator_actions[actions.plugin_name] = actions.identifier;
+
+ if (!actions.breakpoints.empty())
+ HandleAcceleratorBreakpoints(actions);
+
+ return Status();
+}
+
+void ProcessGDBRemote::HandleAcceleratorBreakpoints(
+ const AcceleratorActions &actions) {
+ Target &target = GetTarget();
+ 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;
+
+ BreakpointSP bp_sp;
+ 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);
+ }
+
+ if (!bp_sp)
+ continue;
+
+ // Give the internal breakpoint a meaningful description for stop reasons.
+ bp_sp->SetBreakpointKind("accelerator-plugin");
+ auto baton_sp = std::make_shared<AcceleratorBreakpointCallbackBaton>(
+ std::move(args_up));
+ bp_sp->SetCallback(AcceleratorBreakpointHitCallback, baton_sp,
+ /*is_synchronous=*/true);
+ }
+}
+
+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;
+ }
+ }
+ }
+
+ std::optional<AcceleratorBreakpointHitResponse> response =
+ m_gdb_comm.AcceleratorBreakpointHit(args);
+ if (!response)
+ 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 (Status error = HandleAcceleratorActions(*response->actions);
+ error.Fail())
+ Debugger::ReportError(
+ llvm::formatv("failed to handle accelerator actions for plugin "
+ "'{0}': {1}\nActions:\n{2}\n",
+ response->actions->plugin_name, error.AsCString(),
+ toJSON(*response->actions)));
+ }
+
+ // 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 c6d67b3aa5350..56c05d5cad984 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -478,6 +478,30 @@ 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.
+ Status 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.
+ void 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.
+ 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..2dd8a5ba9a67c
--- /dev/null
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorBreakpoints.py
@@ -0,0 +1,75 @@
+"""
+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
+
+
+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")
+
+ @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 "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.assertState(process.GetState(), lldb.eStateStopped)
+
+ frame = process.GetSelectedThread().GetFrameAtIndex(0)
+ self.assertEqual(
+ frame.GetFunctionName(),
+ "accelerator_initialize",
+ "stopped at the by-name accelerator breakpoint",
+ )
+
+ # Hitting the initialize breakpoint caused the plugin to request two
+ # more breakpoints: one by address (on "compute", from the symbol value
+ # delivered with the hit) and one by name scoped to the "a.out" shared
+ # library (on "finish"). main() calls compute() first.
+ process.Continue()
+ self.assertState(process.GetState(), lldb.eStateStopped)
+ frame = process.GetSelectedThread().GetFrameAtIndex(0)
+ self.assertEqual(
+ frame.GetFunctionName(),
+ "compute",
+ "stopped at the by-address accelerator breakpoint on compute",
+ )
+
+ process.Continue()
+ self.assertState(process.GetState(), lldb.eStateStopped)
+ frame = process.GetSelectedThread().GetFrameAtIndex(0)
+ self.assertEqual(
+ frame.GetFunctionName(),
+ "finish",
+ "stopped at the by-name+shlib accelerator breakpoint on finish",
+ )
+
+ # 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..bb1f626cfef82 100644
--- a/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
+++ b/lldb/test/API/accelerator/mock/TestMockAcceleratorPlugin.py
@@ -81,7 +81,9 @@ 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"], "accelerator_initialize")
+ # The initialize breakpoint requests the "compute" symbol value.
+ self.assertEqual(bp["symbol_names"], ["compute"])
@add_test_categories(["llgs"])
def test_jAcceleratorPluginBreakpointHit_response(self):
@@ -92,13 +94,15 @@ def test_jAcceleratorPluginBreakpointHit_response(self):
self.add_qSupported_packets()
self.expect_gdbremote_sequence()
+ # Simulate the "main" breakpoint being hit, supplying the requested
+ # "compute" symbol value so the plugin sets a breakpoint by address.
hit_args = {
"plugin_name": "mock",
"breakpoint": {
"identifier": 1,
- "symbol_names": [],
+ "symbol_names": ["compute"],
},
- "symbol_values": [],
+ "symbol_values": [{"name": "compute", "value": 0x4000}],
}
hit_json = json.dumps(hit_args, separators=(",", ":"))
escaped_json = escape_binary(hit_json)
@@ -111,12 +115,19 @@ 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"], "finish")
+ self.assertEqual(new_bps[3]["by_name"]["shlib"], "a.out")
+
+ # Breakpoint by address, using the supplied "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..f0873ee47d30f 100644
--- a/lldb/test/API/accelerator/mock/main.c
+++ b/lldb/test/API/accelerator/mock/main.c
@@ -1 +1,15 @@
-int main(void) { return 0; }
+// The mock accelerator plugin sets its initialize breakpoint on this function.
+// Using a dedicated function (rather than "main") ensures the mock plugin only
+// affects this test program and not other inferiors launched by lldb-server.
+void accelerator_initialize(void) {}
+
+int compute(int x) { return x * 2; }
+
+int finish(void) { return 0; }
+
+int main(void) {
+ accelerator_initialize();
+ int result = compute(21);
+ 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..9aea8174db0bc 100644
--- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
+++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/LLDBServerMockAcceleratorPlugin.cpp
@@ -23,9 +23,16 @@ std::optional<AcceleratorActions>
LLDBServerMockAcceleratorPlugin::GetInitializeActions() {
AcceleratorActions actions(GetPluginName(), 1);
+ // Set a breakpoint by function name (no shared library scope) on the
+ // dedicated "accelerator_initialize" hook and ask for the load address of
+ // "compute" to be delivered when it is hit. Using a dedicated function name
+ // (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, "accelerator_initialize"};
+ bp.symbol_names.push_back("compute");
actions.breakpoints.push_back(std::move(bp));
return actions;
@@ -35,16 +42,43 @@ 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", "finish"};
+ actions.breakpoints.push_back(std::move(by_name_shlib));
+
+ // Breakpoint by address, using the "compute" symbol value that was
+ // delivered with this breakpoint hit.
+ if (std::optional<uint64_t> compute_addr = args.GetSymbolValue("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