[Lldb-commits] [lldb] [LLDB] Support enabling/disabling InstrumentationRuntime plugins during a debug session (PR #190083)
Dan Liew via lldb-commits
lldb-commits at lists.llvm.org
Tue Apr 7 15:42:29 PDT 2026
https://github.com/delcypher updated https://github.com/llvm/llvm-project/pull/190083
>From 26f3da457dd7ce8392581eec76b2215e4fdb7868 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 30 Mar 2026 13:08:34 -0700
Subject: [PATCH 01/15] [LLDB] Support enabling/disabling
InstrumentationRuntime plugins during a debug session
Previously enabling/disabling an instrumentation runtime plugin via the
`plugin enable/disable instrumentation-runtime.<name>` shell command
only toggled the plugin's registration state in the PluginManager but
did not actually activate or deactivate the plugin in the debug session.
In practice what this meant is that
1. If the plugin was activated when the inferior process was launched it
could not be disabled even if requested. What this meant in practice
is that it was impossible to prevent LLDB from stopping at the
breakpoint set by an instrumentation runtime plugin because the
breakpoints used are typically internal breakpoints (cannot be
controlled by the user). While stopping at these breakpoints is
usually desirable there are use cases where the developer may wish to
not automatically stop and instead have finer grained control.
2. If the plugin was disabled before the inferior process was launched
(e.g. `plugin disable instrumentation-runtime.<name>` in
`~/.lldbinit`) it could not be enabled even if requested (the plugin
was not activated). While this is a niche use case it is still very
odd from a user perspective that the `plugin enable
instrumentation-runtime.<name>` command did nothing during a debug
session.
This patch implements a mechanism for enabling/disabling instrumentation
plugins during a debug session. In the future we may want to generalize
this to support other plugin types but for now it makes sense to
restrict this to where support is obviously needed to reduce risk (LLDB
might misbehave if we allowed enabling/disabling any arbitrary plugin
during a debug session).
An issue I ran into is that instrumentation plugins are per-process but
the current implementation of the `plugin enable/disable` implies we
should make the change globally (i.e. for all processes in the debug
session). Previously the `PluginManager` didn't know what processes
existed and so there wasn't a simple way to iterate over the live
processes. To fix this `PluginManager` now keeps track of processes that
have instrumentation runtimes. An alternative we could pursue is
iterating over all the debugger instances, targets, then processes and
sending the notifications that way.
With this patch when the user runs `plugin enable/disable
instrumentation-runtime.<name>`, `PluginManager` notifies all registered
processes via `Process::SetInstrumentationRuntimeEnabled()`, which in turn
calls `InstrumentationRuntime::Enable()` or `Disable()` on the appropriate
plugin instance. If the plugin being requested doesn't exist and we are being
asked to enable then the plugin is created on demand and then
`InstrumentationRuntime::Enable()` is called on the plugin.
Test cases for the UBSan and BoundsSafety instrumentation plugins are
included. BoundsSafety is included because fixing the behavior for this
plugin is my primary goal. However, because Clang support for
`-fbounds-safety` is not fully upstream yet those tests don't run
upstream. However, the UBSan instrumentation plugin tests do so tests
have been added to ensure we have coverage in upstream of the new
behavior in this patch.
Assisted-by: Claude Code
rdar://167725878
---
lldb/include/lldb/Core/PluginManager.h | 5 +
.../lldb/Target/InstrumentationRuntime.h | 15 ++
lldb/include/lldb/Target/Process.h | 15 ++
.../Python/lldbsuite/test/lldbtest.py | 13 ++
lldb/source/Core/PluginManager.cpp | 90 +++++++++++-
.../ASan/InstrumentationRuntimeASan.h | 2 +-
.../InstrumentationRuntimeASanLibsanitizers.h | 2 +-
.../InstrumentationRuntimeBoundsSafety.h | 2 +-
.../InstrumentationRuntimeMainThreadChecker.h | 2 +-
.../TSan/InstrumentationRuntimeTSan.h | 2 +-
.../UBSan/InstrumentationRuntimeUBSan.h | 2 +-
lldb/source/Target/InstrumentationRuntime.cpp | 36 +++++
lldb/source/Target/Process.cpp | 53 +++++++
.../ubsan/plugin_enable_disable/Makefile | 4 +
.../TestUbsanPluginEnableDisable.py | 131 ++++++++++++++++++
.../ubsan/plugin_enable_disable/main.c | 16 +++
.../TestBoundsSafetyInstrumentationPlugin.py | 110 ++++++++++++++-
.../API/lang/BoundsSafety/soft_trap/main.c | 7 +-
.../Inputs/boundsSafetyMultipleSoftTraps.c | 19 +++
...ap_call_minimal_plugin_disable_enable.test | 62 +++++++++
...ap_call_minimal_plugin_enable_disable.test | 75 ++++++++++
21 files changed, 644 insertions(+), 19 deletions(-)
create mode 100644 lldb/test/API/functionalities/ubsan/plugin_enable_disable/Makefile
create mode 100644 lldb/test/API/functionalities/ubsan/plugin_enable_disable/TestUbsanPluginEnableDisable.py
create mode 100644 lldb/test/API/functionalities/ubsan/plugin_enable_disable/main.c
create mode 100644 lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c
create mode 100644 lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test
create mode 100644 lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 9403d3c34abec..1f9b29b54f107 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -592,6 +592,11 @@ class PluginManager {
static llvm::SmallVector<InstrumentationRuntimeCallbacks>
GetInstrumentationRuntimeCallbacks();
+ static void RegisterProcessForInstrumentationRuntimeNotifications(
+ lldb::ProcessSP process);
+ static void UnregisterProcessFromInstrumentationRuntimeNotifications(
+ lldb::ProcessSP process);
+
// TypeSystem
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
TypeSystemCreateInstance create_callback,
diff --git a/lldb/include/lldb/Target/InstrumentationRuntime.h b/lldb/include/lldb/Target/InstrumentationRuntime.h
index d2499528e97ab..663dc8c255496 100644
--- a/lldb/include/lldb/Target/InstrumentationRuntime.h
+++ b/lldb/include/lldb/Target/InstrumentationRuntime.h
@@ -73,6 +73,9 @@ class InstrumentationRuntime
/// is guaranteed to be loaded.
virtual void Activate() = 0;
+ /// Remove any breakpoints and perform any necessary clean up.
+ virtual void Deactivate() = 0;
+
/// \return true if `CheckIfRuntimeIsValid` should be called on all modules.
/// In this case the return value of `GetPatternForRuntimeLibrary` will be
/// ignored. Return false if `CheckIfRuntimeIsValid` should only be called
@@ -92,6 +95,18 @@ class InstrumentationRuntime
bool IsActive() const { return m_is_active; }
+ /// Called when the user runs
+ /// `plugin enable instrumentation-runtime.<name>`
+ ///
+ /// \returns true iff on success
+ virtual bool Enable();
+
+ /// Called when the user runs
+ /// `plugin enable instrumentation-runtime.<name>`
+ ///
+ /// \returns true iff on success
+ virtual bool Disable();
+
virtual lldb::ThreadCollectionSP
GetBacktracesFromExtendedStopInfo(StructuredData::ObjectSP info);
};
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 3de4e1244a09b..7d751bd42fd0c 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2617,6 +2617,20 @@ void PruneThreadPlans();
lldb::InstrumentationRuntimeSP
GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type);
+ /// Enable/Disable an instrumentation runtime plugin for this process.
+ /// If the plugging doesn't currently exist it will be created.
+ /// This is intended to be used by PluginManager to handle requests from
+ /// the user to enable/disable a instrumentation runtime plugin during a
+ /// debug session.
+ ///
+ /// \param irt - The type of instrumentation runtime plugin
+ /// \param enabled - If true try to enable the plugin, otherwise try to
+ /// disable it.
+ ///
+ /// \return true iff the plugin was successfully enabled/disabled.
+ bool SetInstrumentationRuntimeEnabled(lldb::InstrumentationRuntimeType irt,
+ bool enabled);
+
/// Try to fetch the module specification for a module with the given file
/// name and architecture. Process sub-classes have to override this method
/// if they support platforms where the Platform object can't get the module
@@ -3426,6 +3440,7 @@ void PruneThreadPlans();
LanguageRuntimeCollection m_language_runtimes;
std::recursive_mutex m_language_runtimes_mutex;
InstrumentationRuntimeCollection m_instrumentation_runtimes;
+ bool m_registered_for_instrumentation_runtime_enabled_changed = false;
std::unique_ptr<NextEventAction> m_next_event_action_up;
std::vector<PreResumeCallbackAndBaton> m_pre_resume_actions;
bool m_currently_handling_do_on_removals;
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 150dc4d5f16f1..200e3bb20b20b 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -1805,6 +1805,19 @@ def get_stats(self, options=None):
metrics_json = return_obj.GetOutput()
return json.loads(metrics_json)
+ def plugin_is_enabled(self, name):
+ interp = self.dbg.GetCommandInterpreter()
+ result = lldb.SBCommandReturnObject()
+ interp.HandleCommand(f"plugin list {name}", result)
+ if not result.Succeeded():
+ return None
+ output = result.GetOutput()
+ if "[+]" in output:
+ return True
+ if "[-]" in output:
+ return False
+ return None
+
# Metaclass for TestBase to change the list of test metods when a new TestCase is loaded.
# We change the test methods to create a new test method for each test for each debug info we are
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 39344ad7cd084..a98937e56756d 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -590,12 +590,18 @@ template <typename Instance> class PluginInstances {
[&](const Instance &instance) { return count++ == idx; });
}
- std::optional<Instance> GetInstanceForName(llvm::StringRef name) {
+ std::optional<Instance> GetInstanceForName(llvm::StringRef name,
+ bool enabled_only = true) {
if (name.empty())
return std::nullopt;
- return FindEnabledInstance(
- [&](const Instance &instance) { return instance.name == name; });
+ auto predicate = [&](const Instance &instance) {
+ return instance.name == name;
+ };
+ if (enabled_only)
+ return FindEnabledInstance(predicate);
+
+ return FindInstance(predicate);
}
std::optional<Instance>
@@ -607,6 +613,16 @@ template <typename Instance> class PluginInstances {
return std::nullopt;
}
+ std::optional<Instance>
+ FindInstance(std::function<bool(const Instance &)> predicate) const {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ for (const auto &instance : m_instances) {
+ if (predicate(instance))
+ return instance;
+ }
+ return std::nullopt;
+ }
+
// Return a list of all the registered plugin instances. This includes both
// enabled and disabled instances. The instances are listed in the order they
// were registered which is the order they would be queried if they were all
@@ -1859,8 +1875,16 @@ struct InstrumentationRuntimeInstance
InstrumentationRuntimeGetType get_type_callback = nullptr;
};
-typedef PluginInstances<InstrumentationRuntimeInstance>
- InstrumentationRuntimeInstances;
+struct InstrumentationRuntimeInstances
+ : public PluginInstances<InstrumentationRuntimeInstance> {
+
+ InstrumentationRuntimeGetType GetTypeCallbackForName(llvm::StringRef name,
+ bool enabled_only) {
+ if (auto instance = GetInstanceForName(name, enabled_only))
+ return instance->get_type_callback;
+ return nullptr;
+ }
+};
static InstrumentationRuntimeInstances &GetInstrumentationRuntimeInstances() {
static InstrumentationRuntimeInstances g_instances;
@@ -1890,6 +1914,33 @@ PluginManager::GetInstrumentationRuntimeCallbacks() {
return result;
}
+static std::mutex &GetInstrumentationRuntimeProcessesMutex() {
+ static std::mutex g_mutex;
+ return g_mutex;
+}
+
+static llvm::SmallVector<lldb::ProcessWP> &
+GetInstrumentationRuntimeProcesses() {
+ static llvm::SmallVector<lldb::ProcessWP> g_processes;
+ return g_processes;
+}
+
+void PluginManager::RegisterProcessForInstrumentationRuntimeNotifications(
+ lldb::ProcessSP process) {
+ std::lock_guard<std::mutex> guard(GetInstrumentationRuntimeProcessesMutex());
+ GetInstrumentationRuntimeProcesses().push_back(process);
+}
+
+void PluginManager::UnregisterProcessFromInstrumentationRuntimeNotifications(
+ lldb::ProcessSP process) {
+ std::lock_guard<std::mutex> guard(GetInstrumentationRuntimeProcessesMutex());
+ auto &processes = GetInstrumentationRuntimeProcesses();
+ llvm::erase_if(processes, [process](const lldb::ProcessWP &wp) {
+ auto sp = wp.lock();
+ return !sp || sp == process;
+ });
+}
+
#pragma mark TypeSystem
struct TypeSystemInstance : public PluginInstance<TypeSystemCreateInstance> {
@@ -2464,7 +2515,34 @@ PluginManager::GetInstrumentationRuntimePluginInfo() {
}
bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
bool enable) {
- return GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable);
+ if (!GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable))
+ return false;
+
+ // Notify all registered processes to enable/disable the plugin.
+ auto type_cb = GetInstrumentationRuntimeInstances().GetTypeCallbackForName(
+ name, /*enabled_only=*/false);
+ if (!type_cb)
+ return false;
+ auto instrumentation_ty = type_cb();
+ std::lock_guard<std::mutex> guard(GetInstrumentationRuntimeProcessesMutex());
+ auto &processes = GetInstrumentationRuntimeProcesses();
+ bool success = true;
+ for (auto &process_wp : processes) {
+ ProcessSP process_sp = process_wp.lock();
+ if (!process_sp)
+ continue; // FIXME: Should we try to erase this from the list?
+
+ // If the process is dead don't try to enable the plugin because there's
+ // nothing useful it can do and it might crash when enabled on a dead
+ // process.
+ // FIXME: Should we try to erase from the list if the process is dead?
+ if (process_sp->IsAlive()) {
+ success &= process_sp->SetInstrumentationRuntimeEnabled(
+ instrumentation_ty, enable);
+ }
+ }
+
+ return success;
}
llvm::SmallVector<RegisteredPluginInfo>
diff --git a/lldb/source/Plugins/InstrumentationRuntime/ASan/InstrumentationRuntimeASan.h b/lldb/source/Plugins/InstrumentationRuntime/ASan/InstrumentationRuntimeASan.h
index 177959d7126be..49cf7a78c34e0 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/ASan/InstrumentationRuntimeASan.h
+++ b/lldb/source/Plugins/InstrumentationRuntime/ASan/InstrumentationRuntimeASan.h
@@ -42,7 +42,7 @@ class InstrumentationRuntimeASan : public lldb_private::InstrumentationRuntime {
void Activate() override;
- void Deactivate();
+ void Deactivate() override;
static bool NotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
diff --git a/lldb/source/Plugins/InstrumentationRuntime/ASanLibsanitizers/InstrumentationRuntimeASanLibsanitizers.h b/lldb/source/Plugins/InstrumentationRuntime/ASanLibsanitizers/InstrumentationRuntimeASanLibsanitizers.h
index abb445a9dd676..69741ffc8cf7b 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/ASanLibsanitizers/InstrumentationRuntimeASanLibsanitizers.h
+++ b/lldb/source/Plugins/InstrumentationRuntime/ASanLibsanitizers/InstrumentationRuntimeASanLibsanitizers.h
@@ -41,7 +41,7 @@ class InstrumentationRuntimeASanLibsanitizers
void Activate() override;
- void Deactivate();
+ void Deactivate() override;
static bool
NotifyBreakpointHit(void *baton,
diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h
index 206c34492d601..93025db579a99 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h
+++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h
@@ -46,7 +46,7 @@ class InstrumentationRuntimeBoundsSafety
void Activate() override;
- void Deactivate();
+ void Deactivate() override;
static bool NotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
diff --git a/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.h b/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.h
index 3bbbf13b7798e..fcbdd35b7f9e1 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.h
+++ b/lldb/source/Plugins/InstrumentationRuntime/MainThreadChecker/InstrumentationRuntimeMainThreadChecker.h
@@ -49,7 +49,7 @@ class InstrumentationRuntimeMainThreadChecker
void Activate() override;
- void Deactivate();
+ void Deactivate() override;
static bool NotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
diff --git a/lldb/source/Plugins/InstrumentationRuntime/TSan/InstrumentationRuntimeTSan.h b/lldb/source/Plugins/InstrumentationRuntime/TSan/InstrumentationRuntimeTSan.h
index db4466a131930..76e0387ddcc93 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/TSan/InstrumentationRuntimeTSan.h
+++ b/lldb/source/Plugins/InstrumentationRuntime/TSan/InstrumentationRuntimeTSan.h
@@ -48,7 +48,7 @@ class InstrumentationRuntimeTSan : public lldb_private::InstrumentationRuntime {
void Activate() override;
- void Deactivate();
+ void Deactivate() override;
static bool NotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
diff --git a/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.h b/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.h
index e0de158473de6..b6eac86639f43 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.h
+++ b/lldb/source/Plugins/InstrumentationRuntime/UBSan/InstrumentationRuntimeUBSan.h
@@ -51,7 +51,7 @@ class InstrumentationRuntimeUBSan
void Activate() override;
- void Deactivate();
+ void Deactivate() override;
static bool NotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
diff --git a/lldb/source/Target/InstrumentationRuntime.cpp b/lldb/source/Target/InstrumentationRuntime.cpp
index 66feba813304d..9b5c91bec06c7 100644
--- a/lldb/source/Target/InstrumentationRuntime.cpp
+++ b/lldb/source/Target/InstrumentationRuntime.cpp
@@ -59,6 +59,42 @@ void InstrumentationRuntime::ModulesDidLoad(
});
}
+bool InstrumentationRuntime::Enable() {
+ if (IsActive())
+ return true;
+
+ // Fast path. During a previous time when the plugin was active the relevant
+ // runtime module was found so we can just activate immediately.
+ // FIXME: What if the module was unloaded via dlclose()?
+ if (GetRuntimeModuleSP()) {
+ Activate();
+ return true;
+ }
+
+ // Slow path. The plugin has never found the relevant runtime module in the
+ // past so pretend the current list of modules in the target were just loaded
+ // to give the plugin a chance to activate.
+ if (ProcessSP process_sp = GetProcessSP()) {
+ ModuleList module_list;
+ // FIXME: Does this need a lock?
+ process_sp->GetTarget().GetImages().ForEach(
+ [&module_list](const lldb::ModuleSP module_sp) {
+ module_list.Append(module_sp);
+ return IterationAction::Continue;
+ });
+ // Give the plugin a chance to activate
+ ModulesDidLoad(module_list);
+ }
+ return true;
+}
+
+bool InstrumentationRuntime::Disable() {
+ if (IsActive())
+ Deactivate();
+
+ return !IsActive();
+}
+
lldb::ThreadCollectionSP
InstrumentationRuntime::GetBacktracesFromExtendedStopInfo(
StructuredData::ObjectSP info) {
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 7333365070061..af40c17df385c 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -573,6 +573,11 @@ void Process::Finalize(bool destructing) {
std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex);
m_language_runtimes.clear();
}
+ if (m_registered_for_instrumentation_runtime_enabled_changed) {
+ PluginManager::UnregisterProcessFromInstrumentationRuntimeNotifications(
+ shared_from_this());
+ m_registered_for_instrumentation_runtime_enabled_changed = false;
+ }
m_instrumentation_runtimes.clear();
m_next_event_action_up.reset();
// Clear the last natural stop ID since it has a strong reference to this
@@ -6181,6 +6186,15 @@ void Process::ModulesDidLoad(ModuleList &module_list) {
for (auto &runtime : m_instrumentation_runtimes)
runtime.second->ModulesDidLoad(module_list);
+ // Register with PluginManager so we get notified when instrumentation
+ // runtime plugins are enabled/disabled.
+ if (!m_registered_for_instrumentation_runtime_enabled_changed &&
+ !m_instrumentation_runtimes.empty()) {
+ PluginManager::RegisterProcessForInstrumentationRuntimeNotifications(
+ shared_from_this());
+ m_registered_for_instrumentation_runtime_enabled_changed = true;
+ }
+
// Give the language runtimes a chance to be created before informing them of
// the modified modules.
for (const lldb::LanguageType lang_type : Language::GetSupportedLanguages()) {
@@ -6266,6 +6280,45 @@ Process::GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type) {
return (*pos).second;
}
+bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
+ bool enabled) {
+ assert(IsAlive());
+
+ if (auto instrumentation_runtime = GetInstrumentationRuntime(irt)) {
+ // This process already has an instance of this plugin so just
+ // enable/disable it
+ if (enabled)
+ return instrumentation_runtime->Enable();
+ return instrumentation_runtime->Disable();
+ }
+
+ // There's no instance of this plugin for this process.
+
+ if (!enabled)
+ return true; // nothing to do
+
+ // We need to make an instance of this plugin for this process.
+ // This could happen because `plugin disable instrumentation-runtime.*`
+ // was in `.lldbinit` and so the plugin was never created until the user asked
+ // to enable it now.
+
+ // Create the plugin by finding its create callback and calling it.
+ lldb::InstrumentationRuntimeSP new_plugin = nullptr;
+ for (auto &cbs : PluginManager::GetInstrumentationRuntimeCallbacks()) {
+ InstrumentationRuntimeType other_irt = cbs.get_type_callback();
+ if (other_irt != irt)
+ continue;
+
+ new_plugin = cbs.create_callback(shared_from_this());
+ break;
+ }
+ if (!new_plugin)
+ return false;
+
+ m_instrumentation_runtimes[irt] = new_plugin;
+ return new_plugin->Enable(); // Now try to enable it
+}
+
bool Process::GetModuleSpec(const FileSpec &module_file_spec,
const ArchSpec &arch, ModuleSpec &module_spec) {
module_spec.Clear();
diff --git a/lldb/test/API/functionalities/ubsan/plugin_enable_disable/Makefile b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/Makefile
new file mode 100644
index 0000000000000..54e9492001c35
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -fsanitize=undefined -g -Wno-int-conversion -Wno-incompatible-pointer-types
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/ubsan/plugin_enable_disable/TestUbsanPluginEnableDisable.py b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/TestUbsanPluginEnableDisable.py
new file mode 100644
index 0000000000000..7a219d1df465d
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/TestUbsanPluginEnableDisable.py
@@ -0,0 +1,131 @@
+"""
+Tests enabling and disabling the UndefinedBehaviorSanitizer instrumentation
+runtime plugin during a debug session.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+import lldbsuite.test.lldbutil as lldbutil
+
+
+class UbsanPluginEnableDisableTestCase(TestBase):
+ def setUp(self):
+ TestBase.setUp(self)
+ self.line_first_ubsan_issue = line_number("main.c", "// first ubsan issue")
+ self.line_third_ubsan_issue = line_number("main.c", "// third ubsan issue")
+
+ def ubsan_plugin_is_enabled(self):
+ return self.plugin_is_enabled(
+ "instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+
+ def check_stopped_at_ubsan_issue(self, line_num):
+ process = self.dbg.GetSelectedTarget().process
+ thread = process.GetSelectedThread()
+ stop_reason = thread.GetStopReason()
+ self.assertStopReason(stop_reason, lldb.eStopReasonInstrumentation)
+ self.assertIn("__ubsan_on_report", thread.GetFrameAtIndex(0).GetFunctionName())
+ backtraces = thread.GetStopReasonExtendedBacktraces(
+ lldb.eInstrumentationRuntimeTypeUndefinedBehaviorSanitizer
+ )
+ self.assertEqual(backtraces.GetSize(), 1)
+
+ # Check that we stopped at the expected line somewhere in the stacktrace
+ found = False
+ for i in range(thread.GetNumFrames()):
+ frame = thread.GetFrameAtIndex(i)
+ if frame.GetLineEntry().GetFileSpec().GetFilename() == "main.c":
+ if frame.GetLineEntry().GetLine() == line_num:
+ found = True
+ self.assertTrue(found)
+
+ @skipUnlessUndefinedBehaviorSanitizer
+ @no_debug_info_test
+ def test_disable_plugin_after_hit(self):
+ """Test that disabling the UBSan plugin mid-session prevents further
+ instrumentation stops."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+ self.registerSanitizerLibrariesWithTarget(target)
+
+ self.runCmd("run")
+ self.assertTrue(self.ubsan_plugin_is_enabled())
+
+ process = self.dbg.GetSelectedTarget().process
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ # We should have stopped on the first UBSan issue.
+ self.check_stopped_at_ubsan_issue(self.line_first_ubsan_issue)
+
+ # Disable the UBSan plugin.
+ self.runCmd("plugin disable instrumentation-runtime.UndefinedBehaviorSanitizer")
+ self.assertFalse(self.ubsan_plugin_is_enabled())
+
+ # Continue. The remaining UBSan issues should not cause
+ # instrumentation stops and the process should exit cleanly.
+ try:
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+ finally:
+ # Restore the default so we don't affect other tests. This command
+ # affects the debugger session globally so we have to be careful
+ # to restore the global state after we are done.
+ self.runCmd(
+ "plugin enable instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertTrue(self.ubsan_plugin_is_enabled())
+
+ @skipUnlessUndefinedBehaviorSanitizer
+ @no_debug_info_test
+ def test_enable_plugin_after_disable(self):
+ """Test that disabling the UBSan plugin before launch prevents
+ instrumentation stops, and re-enabling it mid-session restores them."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+ self.registerSanitizerLibrariesWithTarget(target)
+
+ # Disable the UBSan plugin before launching the process.
+ self.runCmd("plugin disable instrumentation-runtime.UndefinedBehaviorSanitizer")
+ self.assertFalse(self.ubsan_plugin_is_enabled())
+
+ # Set a breakpoint on test_breakpoint which is called just before
+ # the last UBSan issue.
+ bp = target.BreakpointCreateByName("test_breakpoint")
+ self.assertTrue(bp.GetNumLocations() > 0)
+
+ self.runCmd("run")
+
+ process = self.dbg.GetSelectedTarget().process
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ # We should have skipped most UBSan issues and stopped at the
+ # test_breakpoint function.
+ stop_reason = thread.GetStopReason()
+ self.assertStopReason(stop_reason, lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint", frame.GetFunctionName())
+
+ # Re-enable the UBSan plugin.
+ self.runCmd("plugin enable instrumentation-runtime.UndefinedBehaviorSanitizer")
+ self.assertTrue(self.ubsan_plugin_is_enabled())
+
+ # Continue
+ process.Continue()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ # We should now hit the last UBSan issue.
+ self.check_stopped_at_ubsan_issue(self.line_third_ubsan_issue)
+
+ # Continue. The process should exit cleanly.
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+ self.assertTrue(self.ubsan_plugin_is_enabled())
diff --git a/lldb/test/API/functionalities/ubsan/plugin_enable_disable/main.c b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/main.c
new file mode 100644
index 0000000000000..22e0722855904
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/main.c
@@ -0,0 +1,16 @@
+#include <stdint.h>
+
+__attribute__((noinline, optnone)) void test_breakpoint(void) {}
+
+__attribute__((noinline, optnone)) int shift(void) { return 33; }
+
+int main() {
+ uint32_t x = 0;
+
+ x = x << shift(); // first ubsan issue
+ x = x << shift(); // second ubsan issue
+ test_breakpoint();
+ x = x << shift(); // third ubsan issue
+
+ return 0;
+}
diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py
index 0f4150400e69e..4d8e84571392a 100644
--- a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py
+++ b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py
@@ -11,10 +11,14 @@
SOFT_TRAP_FUNC_MINIMAL = "__bounds_safety_soft_trap"
SOFT_TRAP_FUNC_WITH_STR = "__bounds_safety_soft_trap_s"
-
class BoundsSafetyTestSoftTrapPlugin(TestBase):
SHARED_BUILD_TESTCASE = False
+ def setUp(self):
+ TestBase.setUp(self)
+ self.line_first_soft_trap = line_number("main.c", "// first soft trap:")
+ self.line_second_soft_trap = line_number("main.c", "// second soft trap:")
+
def _check_stop_reason_impl(
self,
expected_soft_trap_func: str,
@@ -76,6 +80,9 @@ def check_state_soft_trap_with_str(
expected_line_num=line_num,
)
+ def bs_plugin_is_enabled(self):
+ return self.plugin_is_enabled("instrumentation-runtime.BoundsSafety")
+
# Skip the tests on Windows because they fail due to the stop reason
# being `eStopReasonNon` instead of the expected
# `eStopReasonInstrumentation`.
@@ -91,13 +98,14 @@ def test_call_minimal(self):
self.runCmd("run")
process = self.test_target.process
+ self.assertTrue(self.bs_plugin_is_enabled())
# First soft trap hit
self.check_state_soft_trap_minimal(
"Soft Bounds check failed: indexing above upper bound in 'buffer[2]'",
"main",
"main.c",
- 7,
+ self.line_first_soft_trap,
)
process.Continue()
@@ -107,7 +115,7 @@ def test_call_minimal(self):
"Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'",
"main",
"main.c",
- 8,
+ self.line_second_soft_trap,
)
process.Continue()
@@ -126,13 +134,14 @@ def test_call_with_str(self):
self.runCmd("run")
process = self.test_target.process
+ self.assertTrue(self.bs_plugin_is_enabled())
# First soft trap hit
self.check_state_soft_trap_with_str(
"Soft Bounds check failed: indexing above upper bound in 'buffer[2]'",
"main",
"main.c",
- 7,
+ self.line_first_soft_trap,
)
process.Continue()
@@ -142,9 +151,100 @@ def test_call_with_str(self):
"Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'",
"main",
"main.c",
- 8,
+ self.line_second_soft_trap,
+ )
+
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+
+ @skipIfWindows
+ @skipUnlessBoundsSafety
+ @no_debug_info_test
+ def test_call_minimal_enable_then_disable_plugin(self):
+ """
+ Test starting with the plugin enabled on code built with
+ -fbounds-safety-soft-traps=call-minimal and then later disable the
+ plugin
+ """
+ self.build(make_targets=["soft-trap-test-minimal"])
+ self.test_target = self.createTestTarget()
+
+ # Check the plugin is enabled before we run
+ self.assertTrue(self.bs_plugin_is_enabled())
+ self.runCmd("run")
+
+ process = self.test_target.process
+
+ # First soft trap hit
+ self.check_state_soft_trap_minimal(
+ "Soft Bounds check failed: indexing above upper bound in 'buffer[2]'",
+ "main",
+ "main.c",
+ self.line_first_soft_trap,
+ )
+
+ # Disable the plugin so we do not stop at the second soft trap
+ self.runCmd("plugin disable instrumentation-runtime.BoundsSafety")
+ self.assertFalse(self.bs_plugin_is_enabled())
+
+ try:
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+ finally:
+ # Restore the default so we don't affect other tests. This command
+ # affects the debugger session globally so we have to be careful
+ # to restore the global state after we are done.
+ self.runCmd("plugin enable instrumentation-runtime.BoundsSafety")
+ self.assertTrue(self.bs_plugin_is_enabled())
+
+ @skipIfWindows
+ @skipUnlessBoundsSafety
+ @no_debug_info_test
+ def test_call_minimal_disable_then_enable_plugin(self):
+ """
+ Test starting with the plugin enabled on code built with
+ -fbounds-safety-soft-traps=call-minimal and then later disable the
+ plugin
+ """
+ self.build(make_targets=["soft-trap-test-minimal"])
+ self.test_target = self.createTestTarget()
+
+ # Disable the plugin so we do not stop at the second soft trap
+ self.runCmd("plugin disable instrumentation-runtime.BoundsSafety")
+ self.assertFalse(self.bs_plugin_is_enabled())
+
+ # Set a breakpoint on test_breakpoint which is called just before
+ # the last soft trap
+ bp = self.test_target.BreakpointCreateByName("test_breakpoint")
+ self.assertTrue(bp.GetNumLocations() > 0)
+ self.runCmd("run")
+
+ process = self.test_target.process
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ # We should have skipped all UBSan issues and stopped at the
+ # test_breakpoint function.
+ stop_reason = thread.GetStopReason()
+ self.assertStopReason(stop_reason, lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint", frame.GetFunctionName())
+
+ # Enable the plugin so we stop at the second soft trap
+ self.runCmd("plugin enable instrumentation-runtime.BoundsSafety")
+ self.assertTrue(self.bs_plugin_is_enabled())
+ process.Continue()
+
+ # Second soft trap hit
+ self.check_state_soft_trap_minimal(
+ "Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'",
+ "main",
+ "main.c",
+ self.line_second_soft_trap,
)
process.Continue()
self.assertEqual(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)
+ self.assertTrue(self.bs_plugin_is_enabled())
diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/main.c b/lldb/test/API/lang/BoundsSafety/soft_trap/main.c
index 518afaaa02e8c..580d9273e41c4 100644
--- a/lldb/test/API/lang/BoundsSafety/soft_trap/main.c
+++ b/lldb/test/API/lang/BoundsSafety/soft_trap/main.c
@@ -1,10 +1,13 @@
#include <ptrcheck.h>
+__attribute__((noinline, optnone)) void test_breakpoint(void) {}
+
int main(void) {
int pad;
int buffer[] = {0, 1};
int pad2;
- int tmp = buffer[2]; // access past upper bound
- tmp = buffer[-1]; // access below lower bound
+ int tmp = buffer[2]; // first soft trap: access past upper bound
+ test_breakpoint();
+ tmp = buffer[-1]; // second soft trap: access below lower bound
return 0;
}
diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c
new file mode 100644
index 0000000000000..06408ed7ba5d3
--- /dev/null
+++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c
@@ -0,0 +1,19 @@
+#include <stdio.h>
+
+static int bad_read(int index) {
+ int array[] = {0, 1, 2};
+ return array[index];
+}
+
+static void breakpoint_func(void) {}
+
+int main(int argc, char **argv) {
+ breakpoint_func();
+ bad_read(10);
+ printf("Execution continued\n");
+ breakpoint_func();
+ bad_read(20);
+ breakpoint_func();
+ bad_read(30);
+ return 0;
+}
diff --git a/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test b/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test
new file mode 100644
index 0000000000000..d53630d3cd3b8
--- /dev/null
+++ b/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test
@@ -0,0 +1,62 @@
+# UNSUPPORTED: system-windows
+# REQUIRES: clang-bounds-safety
+# RUN: %clang_host -c -g -fbounds-safety -fbounds-safety-soft-traps=call-minimal -O0 %S/../BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c -o %t.o
+# Note: Building the runtime without debug info is intentional because this is the common case
+# RUN: %clang_host -c -O0 %S/../BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o
+# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out
+# RUN: %lldb -b -o 'plugin disable instrumentation-runtime.BoundsSafety' -s %s %t.out | FileCheck %s
+
+b breakpoint_func
+run
+
+# Check we stop in breakpoint_func
+# CHECK: stop reason = breakpoint
+# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
+
+# Verify the plugin is disabled
+plugin list instrumentation-runtime.BoundsSafety
+# CHECK: [-] BoundsSafety
+
+# Verify the plugin's internal breakpoint does not exist
+breakpoint list --internal
+# CHECK-NOT: '__bounds_safety_soft_trap'
+
+# Resume execution and skip over soft trap
+# Resume execution and check we see the message from the trap handler
+c
+# CHECK: BoundsSafety check FAILED
+# Check we stop in breakpoint_func again
+# CHECK: stop reason = breakpoint
+bt
+# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
+
+# Verify the plugin is still disabled
+plugin list instrumentation-runtime.BoundsSafety
+# CHECK: [-] BoundsSafety
+
+# Enable the plugin — should set the internal breakpoint
+plugin enable instrumentation-runtime.BoundsSafety
+# CHECK: [+] BoundsSafety
+
+breakpoint list --internal
+# CHECK: '__bounds_safety_soft_trap'
+
+
+# Resume execution and check we hit soft trap
+c
+# CHECK: stop reason = Soft Bounds check failed
+bt
+# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
+c
+# CHECK: BoundsSafety check FAILED
+
+# Resume execution and check we hit soft trap
+c
+# CHECK: stop reason = Soft Bounds check failed
+bt
+# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
+c
+# CHECK: BoundsSafety check FAILED
+
+# CHECK: Process {{[0-9]+}} exited with status = 0
+q
diff --git a/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test b/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test
new file mode 100644
index 0000000000000..16be38f5dc630
--- /dev/null
+++ b/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test
@@ -0,0 +1,75 @@
+# UNSUPPORTED: system-windows
+# REQUIRES: clang-bounds-safety
+# RUN: %clang_host -c -g -fbounds-safety -fbounds-safety-soft-traps=call-minimal -O0 %S/../BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c -o %t.o
+# Note: Building the runtime without debug info is intentional because this is the common case
+# RUN: %clang_host -c -O0 %S/../BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o
+# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out
+# RUN: %lldb -b -s %s %t.out | FileCheck %s
+
+b breakpoint_func
+run
+
+# Check we stop in breakpoint_func
+# CHECK: stop reason = breakpoint
+# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
+
+# Verify the plugin is enabled
+plugin list instrumentation-runtime.BoundsSafety
+# CHECK: [+] BoundsSafety
+
+# Verify the plugin's internal breakpoint exists
+breakpoint list --internal
+# CHECK: '__bounds_safety_soft_trap'
+
+# Resume execution and hit the soft trap
+c
+# CHECK: stop reason = Soft Bounds check failed
+bt
+# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
+
+# Resume execution and check we see the message from the trap handler
+c
+# CHECK: BoundsSafety check FAILED
+
+# Check we stop in breakpoint_func again
+# CHECK: stop reason = breakpoint
+bt
+# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
+
+# Verify the plugin is enabled
+plugin list instrumentation-runtime.BoundsSafety
+# CHECK: [+] BoundsSafety
+
+# Disable the plugin — should remove the internal breakpoint
+plugin disable instrumentation-runtime.BoundsSafety
+# CHECK: [-] BoundsSafety
+
+# Verify internal breakpoint is gone
+breakpoint list --internal
+# CHECK-NOT: '__bounds_safety_soft_trap'
+
+# Resume execution and check we skip over the soft trap
+c
+# CHECK: BoundsSafety check FAILED
+# Check we stop in breakpoint_func again
+# CHECK: stop reason = breakpoint
+bt
+# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
+
+# Re-enable the plugin — should set the internal breakpoint
+plugin enable instrumentation-runtime.BoundsSafety
+# CHECK: [+] BoundsSafety
+
+# Verify internal breakpoint is back
+breakpoint list --internal
+# CHECK: '__bounds_safety_soft_trap'
+
+# Resume execution and check we hit soft trap
+c
+# CHECK: stop reason = Soft Bounds check failed
+bt
+# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
+c
+# CHECK: BoundsSafety check FAILED
+# CHECK: Process {{[0-9]+}} exited with status = 0
+q
>From 89b3ab5b75fd68d6bc16a967ea540a1a83b84c48 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 2 Apr 2026 10:35:33 -0700
Subject: [PATCH 02/15] Remove unneeded tests
---
.../Inputs/boundsSafetyMultipleSoftTraps.c | 19 -----
...ap_call_minimal_plugin_disable_enable.test | 62 ---------------
...ap_call_minimal_plugin_enable_disable.test | 75 -------------------
3 files changed, 156 deletions(-)
delete mode 100644 lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c
delete mode 100644 lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test
delete mode 100644 lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test
diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c
deleted file mode 100644
index 06408ed7ba5d3..0000000000000
--- a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c
+++ /dev/null
@@ -1,19 +0,0 @@
-#include <stdio.h>
-
-static int bad_read(int index) {
- int array[] = {0, 1, 2};
- return array[index];
-}
-
-static void breakpoint_func(void) {}
-
-int main(int argc, char **argv) {
- breakpoint_func();
- bad_read(10);
- printf("Execution continued\n");
- breakpoint_func();
- bad_read(20);
- breakpoint_func();
- bad_read(30);
- return 0;
-}
diff --git a/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test b/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test
deleted file mode 100644
index d53630d3cd3b8..0000000000000
--- a/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_disable_enable.test
+++ /dev/null
@@ -1,62 +0,0 @@
-# UNSUPPORTED: system-windows
-# REQUIRES: clang-bounds-safety
-# RUN: %clang_host -c -g -fbounds-safety -fbounds-safety-soft-traps=call-minimal -O0 %S/../BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c -o %t.o
-# Note: Building the runtime without debug info is intentional because this is the common case
-# RUN: %clang_host -c -O0 %S/../BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o
-# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out
-# RUN: %lldb -b -o 'plugin disable instrumentation-runtime.BoundsSafety' -s %s %t.out | FileCheck %s
-
-b breakpoint_func
-run
-
-# Check we stop in breakpoint_func
-# CHECK: stop reason = breakpoint
-# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
-
-# Verify the plugin is disabled
-plugin list instrumentation-runtime.BoundsSafety
-# CHECK: [-] BoundsSafety
-
-# Verify the plugin's internal breakpoint does not exist
-breakpoint list --internal
-# CHECK-NOT: '__bounds_safety_soft_trap'
-
-# Resume execution and skip over soft trap
-# Resume execution and check we see the message from the trap handler
-c
-# CHECK: BoundsSafety check FAILED
-# Check we stop in breakpoint_func again
-# CHECK: stop reason = breakpoint
-bt
-# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
-
-# Verify the plugin is still disabled
-plugin list instrumentation-runtime.BoundsSafety
-# CHECK: [-] BoundsSafety
-
-# Enable the plugin — should set the internal breakpoint
-plugin enable instrumentation-runtime.BoundsSafety
-# CHECK: [+] BoundsSafety
-
-breakpoint list --internal
-# CHECK: '__bounds_safety_soft_trap'
-
-
-# Resume execution and check we hit soft trap
-c
-# CHECK: stop reason = Soft Bounds check failed
-bt
-# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
-c
-# CHECK: BoundsSafety check FAILED
-
-# Resume execution and check we hit soft trap
-c
-# CHECK: stop reason = Soft Bounds check failed
-bt
-# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
-c
-# CHECK: BoundsSafety check FAILED
-
-# CHECK: Process {{[0-9]+}} exited with status = 0
-q
diff --git a/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test b/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test
deleted file mode 100644
index 16be38f5dc630..0000000000000
--- a/lldb/test/Shell/BoundsSafety/bounds_safety_soft_trap_call_minimal_plugin_enable_disable.test
+++ /dev/null
@@ -1,75 +0,0 @@
-# UNSUPPORTED: system-windows
-# REQUIRES: clang-bounds-safety
-# RUN: %clang_host -c -g -fbounds-safety -fbounds-safety-soft-traps=call-minimal -O0 %S/../BoundsSafety/Inputs/boundsSafetyMultipleSoftTraps.c -o %t.o
-# Note: Building the runtime without debug info is intentional because this is the common case
-# RUN: %clang_host -c -O0 %S/../BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o
-# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out
-# RUN: %lldb -b -s %s %t.out | FileCheck %s
-
-b breakpoint_func
-run
-
-# Check we stop in breakpoint_func
-# CHECK: stop reason = breakpoint
-# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
-
-# Verify the plugin is enabled
-plugin list instrumentation-runtime.BoundsSafety
-# CHECK: [+] BoundsSafety
-
-# Verify the plugin's internal breakpoint exists
-breakpoint list --internal
-# CHECK: '__bounds_safety_soft_trap'
-
-# Resume execution and hit the soft trap
-c
-# CHECK: stop reason = Soft Bounds check failed
-bt
-# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
-
-# Resume execution and check we see the message from the trap handler
-c
-# CHECK: BoundsSafety check FAILED
-
-# Check we stop in breakpoint_func again
-# CHECK: stop reason = breakpoint
-bt
-# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
-
-# Verify the plugin is enabled
-plugin list instrumentation-runtime.BoundsSafety
-# CHECK: [+] BoundsSafety
-
-# Disable the plugin — should remove the internal breakpoint
-plugin disable instrumentation-runtime.BoundsSafety
-# CHECK: [-] BoundsSafety
-
-# Verify internal breakpoint is gone
-breakpoint list --internal
-# CHECK-NOT: '__bounds_safety_soft_trap'
-
-# Resume execution and check we skip over the soft trap
-c
-# CHECK: BoundsSafety check FAILED
-# Check we stop in breakpoint_func again
-# CHECK: stop reason = breakpoint
-bt
-# CHECK: frame #{{.*}}`breakpoint_func at boundsSafetyMultipleSoftTraps.c
-
-# Re-enable the plugin — should set the internal breakpoint
-plugin enable instrumentation-runtime.BoundsSafety
-# CHECK: [+] BoundsSafety
-
-# Verify internal breakpoint is back
-breakpoint list --internal
-# CHECK: '__bounds_safety_soft_trap'
-
-# Resume execution and check we hit soft trap
-c
-# CHECK: stop reason = Soft Bounds check failed
-bt
-# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}}
-c
-# CHECK: BoundsSafety check FAILED
-# CHECK: Process {{[0-9]+}} exited with status = 0
-q
>From fd561292a8f8f937ef17f5f10639fce66d3dfec4 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 2 Apr 2026 15:30:24 -0700
Subject: [PATCH 03/15] Stop tracking processes with InstrumentationRuntime
plugins and instead have
`PluginManager::SetInstrumentationRuntimePluginEnabled` just iterate over all
alive processes.
---
lldb/include/lldb/Core/PluginManager.h | 5 ---
lldb/include/lldb/Target/Process.h | 1 -
lldb/source/Core/PluginManager.cpp | 54 +++++++-------------------
lldb/source/Target/Process.cpp | 14 -------
4 files changed, 15 insertions(+), 59 deletions(-)
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 1f9b29b54f107..9403d3c34abec 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -592,11 +592,6 @@ class PluginManager {
static llvm::SmallVector<InstrumentationRuntimeCallbacks>
GetInstrumentationRuntimeCallbacks();
- static void RegisterProcessForInstrumentationRuntimeNotifications(
- lldb::ProcessSP process);
- static void UnregisterProcessFromInstrumentationRuntimeNotifications(
- lldb::ProcessSP process);
-
// TypeSystem
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
TypeSystemCreateInstance create_callback,
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 7d751bd42fd0c..000f751d47507 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -3440,7 +3440,6 @@ void PruneThreadPlans();
LanguageRuntimeCollection m_language_runtimes;
std::recursive_mutex m_language_runtimes_mutex;
InstrumentationRuntimeCollection m_instrumentation_runtimes;
- bool m_registered_for_instrumentation_runtime_enabled_changed = false;
std::unique_ptr<NextEventAction> m_next_event_action_up;
std::vector<PreResumeCallbackAndBaton> m_pre_resume_actions;
bool m_currently_handling_do_on_removals;
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index a98937e56756d..460bb3502338b 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -14,6 +14,7 @@
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Symbol/SaveCoreOptions.h"
#include "lldb/Target/Process.h"
+#include "lldb/Target/Target.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StringList.h"
@@ -1914,33 +1915,6 @@ PluginManager::GetInstrumentationRuntimeCallbacks() {
return result;
}
-static std::mutex &GetInstrumentationRuntimeProcessesMutex() {
- static std::mutex g_mutex;
- return g_mutex;
-}
-
-static llvm::SmallVector<lldb::ProcessWP> &
-GetInstrumentationRuntimeProcesses() {
- static llvm::SmallVector<lldb::ProcessWP> g_processes;
- return g_processes;
-}
-
-void PluginManager::RegisterProcessForInstrumentationRuntimeNotifications(
- lldb::ProcessSP process) {
- std::lock_guard<std::mutex> guard(GetInstrumentationRuntimeProcessesMutex());
- GetInstrumentationRuntimeProcesses().push_back(process);
-}
-
-void PluginManager::UnregisterProcessFromInstrumentationRuntimeNotifications(
- lldb::ProcessSP process) {
- std::lock_guard<std::mutex> guard(GetInstrumentationRuntimeProcessesMutex());
- auto &processes = GetInstrumentationRuntimeProcesses();
- llvm::erase_if(processes, [process](const lldb::ProcessWP &wp) {
- auto sp = wp.lock();
- return !sp || sp == process;
- });
-}
-
#pragma mark TypeSystem
struct TypeSystemInstance : public PluginInstance<TypeSystemCreateInstance> {
@@ -2524,19 +2498,21 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
if (!type_cb)
return false;
auto instrumentation_ty = type_cb();
- std::lock_guard<std::mutex> guard(GetInstrumentationRuntimeProcessesMutex());
- auto &processes = GetInstrumentationRuntimeProcesses();
+
+ // Notify all active processes to enable/disable the plugin.
bool success = true;
- for (auto &process_wp : processes) {
- ProcessSP process_sp = process_wp.lock();
- if (!process_sp)
- continue; // FIXME: Should we try to erase this from the list?
-
- // If the process is dead don't try to enable the plugin because there's
- // nothing useful it can do and it might crash when enabled on a dead
- // process.
- // FIXME: Should we try to erase from the list if the process is dead?
- if (process_sp->IsAlive()) {
+ for (size_t di = 0; di < Debugger::GetNumDebuggers(); ++di) {
+ DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di);
+ if (!debugger_sp)
+ continue;
+ TargetList &target_list = debugger_sp->GetTargetList();
+ for (size_t ti = 0; ti < target_list.GetNumTargets(); ++ti) {
+ TargetSP target_sp = target_list.GetTargetAtIndex(ti);
+ if (!target_sp)
+ continue;
+ ProcessSP process_sp = target_sp->GetProcessSP();
+ if (!process_sp || !process_sp->IsAlive())
+ continue;
success &= process_sp->SetInstrumentationRuntimeEnabled(
instrumentation_ty, enable);
}
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index af40c17df385c..e26306b13fde4 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -573,11 +573,6 @@ void Process::Finalize(bool destructing) {
std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex);
m_language_runtimes.clear();
}
- if (m_registered_for_instrumentation_runtime_enabled_changed) {
- PluginManager::UnregisterProcessFromInstrumentationRuntimeNotifications(
- shared_from_this());
- m_registered_for_instrumentation_runtime_enabled_changed = false;
- }
m_instrumentation_runtimes.clear();
m_next_event_action_up.reset();
// Clear the last natural stop ID since it has a strong reference to this
@@ -6186,15 +6181,6 @@ void Process::ModulesDidLoad(ModuleList &module_list) {
for (auto &runtime : m_instrumentation_runtimes)
runtime.second->ModulesDidLoad(module_list);
- // Register with PluginManager so we get notified when instrumentation
- // runtime plugins are enabled/disabled.
- if (!m_registered_for_instrumentation_runtime_enabled_changed &&
- !m_instrumentation_runtimes.empty()) {
- PluginManager::RegisterProcessForInstrumentationRuntimeNotifications(
- shared_from_this());
- m_registered_for_instrumentation_runtime_enabled_changed = true;
- }
-
// Give the language runtimes a chance to be created before informing them of
// the modified modules.
for (const lldb::LanguageType lang_type : Language::GetSupportedLanguages()) {
>From 5d823511425084f97a8e1f2a91ae630afe178327 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 2 Apr 2026 15:51:45 -0700
Subject: [PATCH 04/15] Comment tweak
---
lldb/source/Core/PluginManager.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 460bb3502338b..07ad8e1848936 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -2492,14 +2492,14 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
if (!GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable))
return false;
- // Notify all registered processes to enable/disable the plugin.
+ // Find the `InstrumentationRuntimeType` from the plugin name
auto type_cb = GetInstrumentationRuntimeInstances().GetTypeCallbackForName(
name, /*enabled_only=*/false);
if (!type_cb)
return false;
auto instrumentation_ty = type_cb();
- // Notify all active processes to enable/disable the plugin.
+ // Notify all alive processes to enable/disable the plugin
bool success = true;
for (size_t di = 0; di < Debugger::GetNumDebuggers(); ++di) {
DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di);
>From 3607191470642d985cd23ccab15d7123e2b11df3 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:28:58 -0700
Subject: [PATCH 05/15] Update lldb/source/Core/PluginManager.cpp
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/source/Core/PluginManager.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 07ad8e1848936..fc26752daff8d 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -2492,7 +2492,7 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
if (!GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable))
return false;
- // Find the `InstrumentationRuntimeType` from the plugin name
+ // Find the `InstrumentationRuntimeType` from the plugin name.
auto type_cb = GetInstrumentationRuntimeInstances().GetTypeCallbackForName(
name, /*enabled_only=*/false);
if (!type_cb)
>From 434918449aa1e8e827d8764e1b895a776dd3205a Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:29:16 -0700
Subject: [PATCH 06/15] Update lldb/source/Core/PluginManager.cpp
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/source/Core/PluginManager.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index fc26752daff8d..3f4735e85a25b 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -2499,7 +2499,7 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
return false;
auto instrumentation_ty = type_cb();
- // Notify all alive processes to enable/disable the plugin
+ // Notify all alive processes to enable/disable the plugin.
bool success = true;
for (size_t di = 0; di < Debugger::GetNumDebuggers(); ++di) {
DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di);
>From 870626c15ad8f7755a26ad07c12ad4871d6d2968 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:29:26 -0700
Subject: [PATCH 07/15] Update
lldb/include/lldb/Target/InstrumentationRuntime.h
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/include/lldb/Target/InstrumentationRuntime.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lldb/include/lldb/Target/InstrumentationRuntime.h b/lldb/include/lldb/Target/InstrumentationRuntime.h
index 663dc8c255496..20bb2ef025873 100644
--- a/lldb/include/lldb/Target/InstrumentationRuntime.h
+++ b/lldb/include/lldb/Target/InstrumentationRuntime.h
@@ -95,10 +95,6 @@ class InstrumentationRuntime
bool IsActive() const { return m_is_active; }
- /// Called when the user runs
- /// `plugin enable instrumentation-runtime.<name>`
- ///
- /// \returns true iff on success
virtual bool Enable();
/// Called when the user runs
>From 51d842a1012bb896ff462fb5f25c0928072a85f2 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:29:38 -0700
Subject: [PATCH 08/15] Update
lldb/include/lldb/Target/InstrumentationRuntime.h
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/include/lldb/Target/InstrumentationRuntime.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lldb/include/lldb/Target/InstrumentationRuntime.h b/lldb/include/lldb/Target/InstrumentationRuntime.h
index 20bb2ef025873..d3b57c49e5d30 100644
--- a/lldb/include/lldb/Target/InstrumentationRuntime.h
+++ b/lldb/include/lldb/Target/InstrumentationRuntime.h
@@ -97,10 +97,6 @@ class InstrumentationRuntime
virtual bool Enable();
- /// Called when the user runs
- /// `plugin enable instrumentation-runtime.<name>`
- ///
- /// \returns true iff on success
virtual bool Disable();
virtual lldb::ThreadCollectionSP
>From 5f36b2eb08242619a152fa2c34bb7bc54c2ec827 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:33:09 -0700
Subject: [PATCH 09/15] Update lldb/source/Target/Process.cpp
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/source/Target/Process.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index e26306b13fde4..c0f3b88ca24d6 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6272,7 +6272,7 @@ bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
if (auto instrumentation_runtime = GetInstrumentationRuntime(irt)) {
// This process already has an instance of this plugin so just
- // enable/disable it
+ // enable/disable it.
if (enabled)
return instrumentation_runtime->Enable();
return instrumentation_runtime->Disable();
>From bfc9ca326ca1d434be4a3845e14e8ae36175b0f5 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:37:56 -0700
Subject: [PATCH 10/15] Update lldb/source/Target/Process.cpp
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/source/Target/Process.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index c0f3b88ca24d6..7b3a47e2cd4bf 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6302,7 +6302,7 @@ bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
return false;
m_instrumentation_runtimes[irt] = new_plugin;
- return new_plugin->Enable(); // Now try to enable it
+ return new_plugin->Enable();
}
bool Process::GetModuleSpec(const FileSpec &module_file_spec,
>From d2a94450d00e9dd4f2ead549f8ad9a6737631899 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 6 Apr 2026 16:38:15 -0700
Subject: [PATCH 11/15] Update lldb/source/Target/Process.cpp
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/source/Target/Process.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 7b3a47e2cd4bf..9dee5151f6c2b 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6281,7 +6281,7 @@ bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
// There's no instance of this plugin for this process.
if (!enabled)
- return true; // nothing to do
+ return true;
// We need to make an instance of this plugin for this process.
// This could happen because `plugin disable instrumentation-runtime.*`
>From 6e7b33f8aecdd5355b6bcf381b88add91d723056 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Tue, 7 Apr 2026 13:06:58 -0700
Subject: [PATCH 12/15] Use llvm::Error on some return types to address review
feedback
---
.../include/lldb/Target/InstrumentationRuntime.h | 5 +++--
lldb/include/lldb/Target/Process.h | 16 +++-------------
lldb/source/Core/PluginManager.cpp | 9 +++++----
lldb/source/Target/InstrumentationRuntime.cpp | 15 +++++++++------
lldb/source/Target/Process.cpp | 10 ++++++----
5 files changed, 26 insertions(+), 29 deletions(-)
diff --git a/lldb/include/lldb/Target/InstrumentationRuntime.h b/lldb/include/lldb/Target/InstrumentationRuntime.h
index d3b57c49e5d30..cb96fd0ad3847 100644
--- a/lldb/include/lldb/Target/InstrumentationRuntime.h
+++ b/lldb/include/lldb/Target/InstrumentationRuntime.h
@@ -17,6 +17,7 @@
#include "lldb/lldb-forward.h"
#include "lldb/lldb-private.h"
#include "lldb/lldb-types.h"
+#include "llvm/Support/Error.h"
namespace lldb_private {
@@ -95,9 +96,9 @@ class InstrumentationRuntime
bool IsActive() const { return m_is_active; }
- virtual bool Enable();
+ virtual llvm::Error Enable();
- virtual bool Disable();
+ virtual llvm::Error Disable();
virtual lldb::ThreadCollectionSP
GetBacktracesFromExtendedStopInfo(StructuredData::ObjectSP info);
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 000f751d47507..b6391ee975cc7 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2617,19 +2617,9 @@ void PruneThreadPlans();
lldb::InstrumentationRuntimeSP
GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type);
- /// Enable/Disable an instrumentation runtime plugin for this process.
- /// If the plugging doesn't currently exist it will be created.
- /// This is intended to be used by PluginManager to handle requests from
- /// the user to enable/disable a instrumentation runtime plugin during a
- /// debug session.
- ///
- /// \param irt - The type of instrumentation runtime plugin
- /// \param enabled - If true try to enable the plugin, otherwise try to
- /// disable it.
- ///
- /// \return true iff the plugin was successfully enabled/disabled.
- bool SetInstrumentationRuntimeEnabled(lldb::InstrumentationRuntimeType irt,
- bool enabled);
+ llvm::Error
+ SetInstrumentationRuntimeEnabled(lldb::InstrumentationRuntimeType irt,
+ bool enabled);
/// Try to fetch the module specification for a module with the given file
/// name and architecture. Process sub-classes have to override this method
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 3f4735e85a25b..fa42877bfa6b9 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -21,6 +21,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorExtras.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
@@ -2500,7 +2501,7 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
auto instrumentation_ty = type_cb();
// Notify all alive processes to enable/disable the plugin.
- bool success = true;
+ bool failed = false;
for (size_t di = 0; di < Debugger::GetNumDebuggers(); ++di) {
DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di);
if (!debugger_sp)
@@ -2513,12 +2514,12 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
ProcessSP process_sp = target_sp->GetProcessSP();
if (!process_sp || !process_sp->IsAlive())
continue;
- success &= process_sp->SetInstrumentationRuntimeEnabled(
- instrumentation_ty, enable);
+ failed |= llvm::errorToBool(process_sp->SetInstrumentationRuntimeEnabled(
+ instrumentation_ty, enable));
}
}
- return success;
+ return !failed;
}
llvm::SmallVector<RegisteredPluginInfo>
diff --git a/lldb/source/Target/InstrumentationRuntime.cpp b/lldb/source/Target/InstrumentationRuntime.cpp
index 9b5c91bec06c7..307cfb3f47a71 100644
--- a/lldb/source/Target/InstrumentationRuntime.cpp
+++ b/lldb/source/Target/InstrumentationRuntime.cpp
@@ -59,16 +59,16 @@ void InstrumentationRuntime::ModulesDidLoad(
});
}
-bool InstrumentationRuntime::Enable() {
+llvm::Error InstrumentationRuntime::Enable() {
if (IsActive())
- return true;
+ return llvm::Error::success();
// Fast path. During a previous time when the plugin was active the relevant
// runtime module was found so we can just activate immediately.
// FIXME: What if the module was unloaded via dlclose()?
if (GetRuntimeModuleSP()) {
Activate();
- return true;
+ return llvm::Error::success();
}
// Slow path. The plugin has never found the relevant runtime module in the
@@ -85,14 +85,17 @@ bool InstrumentationRuntime::Enable() {
// Give the plugin a chance to activate
ModulesDidLoad(module_list);
}
- return true;
+ return llvm::Error::success();
}
-bool InstrumentationRuntime::Disable() {
+llvm::Error InstrumentationRuntime::Disable() {
if (IsActive())
Deactivate();
- return !IsActive();
+ if (IsActive())
+ return llvm::createStringError(
+ "failed to deactivate instrumentation runtime");
+ return llvm::Error::success();
}
lldb::ThreadCollectionSP
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 9dee5151f6c2b..0f04e80595a3e 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6266,8 +6266,9 @@ Process::GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type) {
return (*pos).second;
}
-bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
- bool enabled) {
+llvm::Error
+Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
+ bool enabled) {
assert(IsAlive());
if (auto instrumentation_runtime = GetInstrumentationRuntime(irt)) {
@@ -6281,7 +6282,7 @@ bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
// There's no instance of this plugin for this process.
if (!enabled)
- return true;
+ return llvm::Error::success();
// We need to make an instance of this plugin for this process.
// This could happen because `plugin disable instrumentation-runtime.*`
@@ -6299,7 +6300,8 @@ bool Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
break;
}
if (!new_plugin)
- return false;
+ return llvm::createStringError(
+ "failed to create instrumentation runtime plugin");
m_instrumentation_runtimes[irt] = new_plugin;
return new_plugin->Enable();
>From afb6d391fa53dadacf9427601a88741e5412f58a Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Tue, 7 Apr 2026 13:59:26 -0700
Subject: [PATCH 13/15] Use `GetTarget().GetImages().Modules()` in a foreach
loop to get locking semantics with nicer syntax
---
lldb/source/Target/InstrumentationRuntime.cpp | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/lldb/source/Target/InstrumentationRuntime.cpp b/lldb/source/Target/InstrumentationRuntime.cpp
index 307cfb3f47a71..c5d682be701b4 100644
--- a/lldb/source/Target/InstrumentationRuntime.cpp
+++ b/lldb/source/Target/InstrumentationRuntime.cpp
@@ -76,13 +76,11 @@ llvm::Error InstrumentationRuntime::Enable() {
// to give the plugin a chance to activate.
if (ProcessSP process_sp = GetProcessSP()) {
ModuleList module_list;
- // FIXME: Does this need a lock?
- process_sp->GetTarget().GetImages().ForEach(
- [&module_list](const lldb::ModuleSP module_sp) {
- module_list.Append(module_sp);
- return IterationAction::Continue;
- });
- // Give the plugin a chance to activate
+ for (const auto &module_sp :
+ process_sp->GetTarget().GetImages().Modules()) {
+ module_list.Append(module_sp);
+ }
+ // Give the plugin a chance to activate.
ModulesDidLoad(module_list);
}
return llvm::Error::success();
>From 527afbbf1908ccc0ad364af7776ec79f6191aab5 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Tue, 7 Apr 2026 14:40:53 -0700
Subject: [PATCH 14/15] Some comment clean up
---
lldb/source/Target/Process.cpp | 7 +++----
.../soft_trap/TestBoundsSafetyInstrumentationPlugin.py | 4 ++--
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 0f04e80595a3e..c10e42e4f90da 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6284,10 +6284,9 @@ Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
if (!enabled)
return llvm::Error::success();
- // We need to make an instance of this plugin for this process.
- // This could happen because `plugin disable instrumentation-runtime.*`
- // was in `.lldbinit` and so the plugin was never created until the user asked
- // to enable it now.
+ // The requested plugin was never instantiated for this process so we need
+ // to create an instance so we can activate it. This can happen if
+ // `plugin disable instrumentation-runtime.*` is executed (e.g. in .lldbinit).
// Create the plugin by finding its create callback and calling it.
lldb::InstrumentationRuntimeSP new_plugin = nullptr;
diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py
index 4d8e84571392a..0405e7028acff 100644
--- a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py
+++ b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py
@@ -204,8 +204,8 @@ def test_call_minimal_enable_then_disable_plugin(self):
@no_debug_info_test
def test_call_minimal_disable_then_enable_plugin(self):
"""
- Test starting with the plugin enabled on code built with
- -fbounds-safety-soft-traps=call-minimal and then later disable the
+ Test starting with the plugin disabled on code built with
+ -fbounds-safety-soft-traps=call-minimal and then later enable the
plugin
"""
self.build(make_targets=["soft-trap-test-minimal"])
>From 2762d96ea43301210177467a6cecf12283d42e1d Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Tue, 7 Apr 2026 15:40:52 -0700
Subject: [PATCH 15/15] Add `Debugger::ForEachDebugger` and try to use it.
Unfortunately doing the obvious thing leads to a deadlock. The right fix
is for Debugger to use s std::recursive_mutex but to avoid such a risky
change I've added a hack for now.
---
lldb/include/lldb/Core/Debugger.h | 3 +++
lldb/source/Core/Debugger.cpp | 10 ++++++++++
lldb/source/Core/PluginManager.cpp | 19 ++++++++++++-------
3 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index fa4483c93e639..fe400a3f1312a 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -54,6 +54,7 @@
#include <cassert>
#include <cstddef>
#include <cstdio>
+#include <functional>
namespace llvm {
class raw_ostream;
@@ -136,6 +137,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
static lldb::DebuggerSP GetDebuggerAtIndex(size_t index);
+ static void ForEachDebugger(std::function<void(lldb::DebuggerSP)> callback);
+
static bool FormatDisassemblerAddress(const FormatEntity::Entry *format,
const SymbolContext *sc,
const SymbolContext *prev_sc,
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index e1b2ce1b063e0..9aec2e88c1348 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -1603,6 +1603,16 @@ lldb::DebuggerSP Debugger::GetDebuggerAtIndex(size_t index) {
return nullptr;
}
+void Debugger::ForEachDebugger(std::function<void(DebuggerSP)> callback) {
+ std::lock_guard<std::mutex> guard(GetDebuggerListMutex());
+ if (!g_debugger_list_ptr)
+ return;
+ for (const auto &debugger_sp : *g_debugger_list_ptr) {
+ if (debugger_sp)
+ callback(debugger_sp);
+ }
+}
+
DebuggerSP Debugger::FindDebuggerWithID(lldb::user_id_t id) {
std::lock_guard<std::mutex> guard(GetDebuggerListMutex());
if (!g_debugger_list_ptr)
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index fa42877bfa6b9..999df4d4fad36 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -2502,15 +2502,20 @@ bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name,
// Notify all alive processes to enable/disable the plugin.
bool failed = false;
- for (size_t di = 0; di < Debugger::GetNumDebuggers(); ++di) {
- DebuggerSP debugger_sp = Debugger::GetDebuggerAtIndex(di);
+ // FIXME: This is a hack. We can't call
+ // `Process::SetInstrumentationRuntimeEnabled()` while
+ // `Debugger::ForEachDebugger()` holds a its lock because runtime activation/
+ // deactivation might indirectly try to acquire the same lock and cause
+ // deadlock. To avoid this we make a copy of the list of debuggers while
+ // holding the lock and then release the lock and iterate over the copy.
+ // The right fix here is for Debugger to use a `std::recursive_mutex`.
+ llvm::SmallVector<DebuggerSP, 1> debuggers;
+ Debugger::ForEachDebugger(
+ [&](DebuggerSP debugger_sp) { debuggers.emplace_back(debugger_sp); });
+ for (const auto &debugger_sp : debuggers) {
if (!debugger_sp)
continue;
- TargetList &target_list = debugger_sp->GetTargetList();
- for (size_t ti = 0; ti < target_list.GetNumTargets(); ++ti) {
- TargetSP target_sp = target_list.GetTargetAtIndex(ti);
- if (!target_sp)
- continue;
+ for (const auto &target_sp : debugger_sp->GetTargetList().Targets()) {
ProcessSP process_sp = target_sp->GetProcessSP();
if (!process_sp || !process_sp->IsAlive())
continue;
More information about the lldb-commits
mailing list