[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
Thu Apr 2 15:52:34 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 1/4] [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 2/4] 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 3/4] 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 4/4] 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);
More information about the lldb-commits
mailing list