[Lldb-commits] [lldb] [LLDB][Part 3] Support enabling/disabling InstrumentationRuntime plugins in an debug session (PR #193334)
Dan Liew via lldb-commits
lldb-commits at lists.llvm.org
Thu May 14 15:56:27 PDT 2026
https://github.com/delcypher updated https://github.com/llvm/llvm-project/pull/193334
>From 716b894ca5b0db5959823a9014df67ce62c7e7c5 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 20 Apr 2026 21:11:44 -0700
Subject: [PATCH 1/6] [LLDB][Part 3] Support enabling/disabling
InstrumentationRuntime plugins in an debug session
This patch is the third part in a patch series that will allow
enabling/disabling InstrumentationRuntime plugins in a running debug
session. This depends on #193328 and #193331.
Previously enabling/disabling an instrumentation runtime plugin via the
`plugin enable/disable instrumentation-runtime.<name>` shell command was
only supported using the` global` domain. This only toggled the
plugin's enablement state in the PluginManager. This would affect the
enablement in newly launched targets but not any existing targets. 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. This meant 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 builds on the previous two patches in this series by adding
support for the `debugger`, and `target` domains for the `enable`,
`disable`, and `list` sub-commands when using InstrumentationRuntime
plugins. Prevoiusly the sub-commands supported consuming the `--domain`
flag with these domains but would error on all domains except `global`.
In the future we may want to generalize this to support other plugin
types but for now it makes sense to restrict to just
InstrumentationRuntime plugins.
When using the `enable` and `disable` sub commands with
InstrumentationRuntime plugins the behavior for the different domains
are as follows:
* `global` - The globally stored enablement flag is stored in the
relevant instance of `InstrumentationRuntimeInstance`.
Currently alive targets are **not** updated due an existing design
limitation that is noted in the comments. This preserves the existing
behavior and is the default behavior.
* `debugger` - The `PluginManager` iterates over all targets in the
current Debugger instance and enables/disables the requested plugin
in every target that is still alive.
* `target`- The plugin is enabled/disabled in the currently
selected target in the current Debugger instance.
When using the `list` sub commands with
InstrumentationRuntime plugins the behavior for the different domains
are as follows:
* `global` - Lists the global enablement flags that are stored in
instances of `InstrumentationRuntimeInstance`. This is the default
behavior and preserves the existing behavior.
* `debugger` - The command is disallowed because there's currently
no enablement flag stored per debugger instance.
* `target` - Lists the enablement that's stored in instances of
`InstrumentationRuntime` that are stored in a Process or by the
absense of the plugin in a Process in the currently selected target.
Making this change required that `InstrumentationRuntime` instances
store their own boolean flag to control enablement along with helper
methods to read and set the enablement state.
This design is **not** ideal.
* `plugin enable --domain debugger instrumentation-runtime.<name>` - Only affects the Targets in the Debugger instances with an alive process. It doesn't change the enablement for targets created after this command is executed. I think this a little unexpected.
* `plugin enable --domain global instrumentation-runtime.<name>` - Effectively only changes the enablement (i.e. the default) for Targets created after running this command. It doesn't change the enablement in any live processes which is probably a little confusing.
Both of these are direct result of me trying to keep the notion of a global
domain around to avoid changing too much in one patch set.
I think the direction we should go in in future patches is:
* Move default enablement of InstrumentationRuntime plugins into `Debugger` instances.
* enable/disable of InstrumentationRuntime plugins in the `debugger` domain can now modify
the "default enablement" of the plugins for that debugger instance as well as enabling/disabling any live targets.
* Remove supporting the `global` domain for InstrumentationRuntime because it is now has no meaning.
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 | 10 +-
.../lldb/Target/InstrumentationRuntime.h | 25 +-
lldb/include/lldb/Target/Process.h | 6 +
.../Python/lldbsuite/test/lldbtest.py | 37 ++
lldb/source/Commands/CommandObjectPlugin.cpp | 73 +++-
lldb/source/Core/PluginManager.cpp | 184 +++++++++-
.../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 | 44 +++
lldb/source/Target/Process.cpp | 52 +++
.../ubsan/plugin_enable_disable/Makefile | 11 +
.../TestUbsanPluginEnableDisable.py | 327 ++++++++++++++++++
.../ubsan/plugin_enable_disable/dylib.c | 9 +
.../ubsan/plugin_enable_disable/main.c | 52 +++
.../TestBoundsSafetyInstrumentationPlugin.py | 119 ++++++-
.../API/lang/BoundsSafety/soft_trap/main.c | 7 +-
...enable-disable-domain-flag-one-target.test | 85 +++++
...nable-disable-domain-flag-two-targets.test | 117 +++++++
...and-plugin-enable-disable-domain-flag.test | 8 +-
.../command-plugin-list-domain-flag.test | 16 +-
24 files changed, 1135 insertions(+), 59 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/dylib.c
create mode 100644 lldb/test/API/functionalities/ubsan/plugin_enable_disable/main.c
create mode 100644 lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-one-target.test
create mode 100644 lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-two-targets.test
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index b698f239dc7ac..2a2c216d296c7 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -200,7 +200,11 @@ class PluginManager {
// If pattern is given it will be used to filter the plugins that are
// are returned. The pattern filters the plugin names using the
// PluginManager::MatchPluginName() function.
- static llvm::json::Object GetJSON(llvm::StringRef pattern = "");
+ static llvm::json::Object
+ GetJSON(llvm::StringRef pattern = "",
+ lldb::DebuggerSP requesting_debugger = nullptr,
+ lldb::PluginDomainKind domain =
+ lldb::PluginDomainKind::ePluginDomainKindGlobal);
// Return true if the pattern matches the plugin name.
//
@@ -810,6 +814,10 @@ class PluginManager {
SetInstrumentationRuntimePluginEnabled(llvm::StringRef name, bool enable,
Debugger &requesting_debugger,
lldb::PluginDomainKind domain);
+ static llvm::Expected<bool>
+ IsInstrumentationRuntimePluginEnabled(llvm::StringRef name,
+ lldb::TargetSP target,
+ lldb::PluginDomainKind domain);
static llvm::SmallVector<RegisteredPluginInfo> GetJITLoaderPluginInfo();
static bool SetJITLoaderPluginEnabled(llvm::StringRef name, bool enable);
diff --git a/lldb/include/lldb/Target/InstrumentationRuntime.h b/lldb/include/lldb/Target/InstrumentationRuntime.h
index d2499528e97ab..97ebdef856236 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 {
@@ -41,8 +42,10 @@ class InstrumentationRuntime
bool m_is_active;
protected:
+ bool m_is_enabled;
+
InstrumentationRuntime(const lldb::ProcessSP &process_sp)
- : m_breakpoint_id(0), m_is_active(false) {
+ : m_breakpoint_id(0), m_is_active(false), m_is_enabled(true) {
if (process_sp)
m_process_wp = process_sp;
}
@@ -60,6 +63,7 @@ class InstrumentationRuntime
void SetBreakpointID(lldb::user_id_t ID) { m_breakpoint_id = ID; }
void SetActive(bool IsActive) { m_is_active = IsActive; }
+ void SetEnabled(bool enabled) { m_is_enabled = enabled; }
/// Return a regular expression which can be used to identify a valid version
/// of the runtime library.
@@ -73,6 +77,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
@@ -90,8 +97,24 @@ class InstrumentationRuntime
/// been done.
void ModulesDidLoad(lldb_private::ModuleList &module_list);
+ /// \return true if the plugin is active (e.g. for breakpoint-based plugins
+ /// this means the breakpoint has been set). This is distinct from
+ /// IsEnabled().
bool IsActive() const { return m_is_active; }
+ /// Enable the plugin and activate it if possible.
+ virtual llvm::Error Enable();
+
+ /// Disable the plugin. If the plugin is currently active it will be
+ /// deactivated first. Once disabled, the plugin cannot activate until
+ /// re-enabled via Enable().
+ virtual llvm::Error Disable();
+
+ /// \return true if the plugin is enabled and eligible for activation. An
+ /// enabled plugin is not necessarily active (e.g. the relevant runtime
+ /// library has not been loaded). See IsActive().
+ bool IsEnabled() const { return m_is_enabled; }
+
virtual lldb::ThreadCollectionSP
GetBacktracesFromExtendedStopInfo(StructuredData::ObjectSP info);
};
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 7893b7317b98c..b6c8519bac825 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2695,6 +2695,12 @@ void PruneThreadPlans();
lldb::InstrumentationRuntimeSP
GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type);
+ llvm::Error
+ SetInstrumentationRuntimeEnabled(lldb::InstrumentationRuntimeType irt,
+ bool enabled);
+
+ bool InstrumentationRuntimeIsEnabled(lldb::InstrumentationRuntimeType irt);
+
/// 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
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 3bbd7a21edd51..6c978ce477c9b 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -1831,6 +1831,43 @@ def get_stats(self, options=None):
metrics_json = return_obj.GetOutput()
return json.loads(metrics_json)
+ def plugin_is_enabled(self, namespace: str, name: str, domain: str = "global"):
+ assert domain in {"global", "debugger", "target"}
+ interp = self.dbg.GetCommandInterpreter()
+ result = lldb.SBCommandReturnObject()
+ cmd = f"plugin list --json --domain {domain} {namespace}.{name}"
+ interp.HandleCommand(cmd, result)
+ if not result.Succeeded():
+ raise Exception(f'Failed to run "{cmd}"')
+ output = result.GetOutput()
+ # Parse output like
+ # {
+ # "instrumentation-runtime": [
+ # {
+ # "enabled": true,
+ # "name": "BoundsSafety"
+ # }
+ # ]
+ # }
+ parsed_json = json.loads(output)
+ if not isinstance(parsed_json, dict):
+ raise TypeError(f"{parsed_json} is not a dict")
+ error_data = parsed_json.get("error")
+ if error_data:
+ raise Exception(f'Querying enablement failed with "{error_data}"')
+ namespace_data = parsed_json.get(namespace)
+ if not isinstance(namespace_data, list):
+ raise TypeError(f"{namespace_data} is not a list")
+ for entry in namespace_data:
+ if not isinstance(entry, dict):
+ continue
+ if entry.get("name") == name:
+ enabled = entry.get("enabled")
+ if not isinstance(enabled, bool):
+ raise TypeError("not bool")
+ return enabled
+ 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/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp
index 396910abfb8b8..4412bb1d38793 100644
--- a/lldb/source/Commands/CommandObjectPlugin.cpp
+++ b/lldb/source/Commands/CommandObjectPlugin.cpp
@@ -285,17 +285,11 @@ List only the plugin 'foo' matching a fully qualified name exactly
CommandReturnObject &result,
Debugger &requesting_debugger,
PluginDomainKind domain) {
- if (domain != PluginDomainKind::ePluginDomainKindGlobal) {
- result.AppendErrorWithFormatv(
- "{} domain is not supported",
- PluginManager::PluginDomainKindToStr(domain));
- return;
- }
-
llvm::json::Object obj;
bool found_empty = false;
for (const llvm::StringRef pattern : patterns) {
- llvm::json::Object pat_obj = PluginManager::GetJSON(pattern);
+ llvm::json::Object pat_obj = PluginManager::GetJSON(
+ pattern, requesting_debugger.shared_from_this(), domain);
if (pat_obj.empty()) {
found_empty = true;
result.AppendErrorWithFormat(
@@ -315,22 +309,63 @@ List only the plugin 'foo' matching a fully qualified name exactly
CommandReturnObject &result,
Debugger &requesting_debugger,
PluginDomainKind domain) {
- if (domain != PluginDomainKind::ePluginDomainKindGlobal) {
- result.AppendErrorWithFormatv(
- "{} domain is not supported",
- PluginManager::PluginDomainKindToStr(domain));
- return;
- }
+
+ auto PrintEnablement = [&](bool enabled,
+ const RegisteredPluginInfo &plugin) {
+ result.AppendMessageWithFormatv(" {0} {1, -30} {2}",
+ enabled ? "[+]" : "[-]", plugin.name,
+ plugin.description);
+ };
for (const llvm::StringRef pattern : patterns) {
int num_matching = ActOnMatchingPlugins(
pattern, [&](const PluginNamespace &plugin_namespace,
const std::vector<RegisteredPluginInfo> &plugins) {
- result.AppendMessage(plugin_namespace.name);
- for (auto &plugin : plugins) {
- result.AppendMessageWithFormatv(" {0} {1, -30} {2}",
- plugin.enabled ? "[+]" : "[-]",
- plugin.name, plugin.description);
+ switch (domain) {
+
+ case lldb::ePluginDomainKindGlobal: {
+ result.AppendMessage(plugin_namespace.name);
+ for (auto &plugin : plugins)
+ PrintEnablement(plugin.enabled, plugin);
+ break;
+ }
+ case lldb::ePluginDomainKindDebugger:
+ // Currently enablement status of plugins is not stored inside
+ // debugger instances. If that ever changes we can support
+ // querying enablement here.
+ result.AppendErrorWithFormatv(
+ "plugin namespace {0} does not support querying enablement "
+ "in the debugger domain",
+ plugin_namespace.name);
+ return;
+ case lldb::ePluginDomainKindTarget:
+ if (!plugin_namespace.SupportsDomain(
+ lldb::ePluginDomainKindTarget)) {
+ result.AppendErrorWithFormatv(
+ "plugin namespace {0} does not support querying enablement "
+ "in the target domain",
+ plugin_namespace.name);
+ return;
+ }
+ auto target = requesting_debugger.GetSelectedTarget();
+
+ // Only instrumentation-runtime plugins support the target domain
+ // currently so we can assume that's the case when querying
+ // enablement.
+ assert(plugin_namespace.name == "instrumentation-runtime");
+ result.AppendMessage(plugin_namespace.name);
+ for (auto &plugin : plugins) {
+ auto enabled =
+ PluginManager::IsInstrumentationRuntimePluginEnabled(
+ plugin.name, target, domain);
+ if (llvm::Error E = enabled.takeError()) {
+ result.AppendErrorWithFormatv("{}",
+ llvm::toString(std::move(E)));
+ continue;
+ }
+ PrintEnablement(*enabled, plugin);
+ }
+ break;
}
});
if (num_matching == 0) {
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 7e334ea1c008b..92c681a428821 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -14,12 +14,14 @@
#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"
#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"
@@ -436,20 +438,79 @@ llvm::ArrayRef<PluginNamespace> PluginManager::GetPluginNamespaces() {
return PluginNamespaces;
}
-llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern) {
+llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern,
+ lldb::DebuggerSP requesting_debugger,
+ lldb::PluginDomainKind domain) {
llvm::json::Object plugin_stats;
+ auto ErrorJSON = [&plugin_stats](llvm::Error error) -> llvm::json::Object & {
+ plugin_stats.clear();
+ // Try to put a diagnostic in JSON when a error occurs
+ plugin_stats.try_emplace("error", llvm::toString(std::move(error)));
+ return plugin_stats;
+ };
+
for (const PluginNamespace &plugin_ns : GetPluginNamespaces()) {
llvm::json::Array namespace_stats;
- for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
- if (MatchPluginName(pattern, plugin_ns, plugin)) {
- llvm::json::Object plugin_json;
- plugin_json.try_emplace("name", plugin.name);
- plugin_json.try_emplace("enabled", plugin.enabled);
- namespace_stats.emplace_back(std::move(plugin_json));
+ switch (domain) {
+ case lldb::ePluginDomainKindGlobal: {
+ for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
+ if (MatchPluginName(pattern, plugin_ns, plugin)) {
+ llvm::json::Object plugin_json;
+
+ plugin_json.try_emplace("name", plugin.name);
+ plugin_json.try_emplace("enabled", plugin.enabled);
+ namespace_stats.emplace_back(std::move(plugin_json));
+ }
+ }
+ break;
+ }
+ case lldb::ePluginDomainKindDebugger:
+ // Not supported currently by any plugin when checking for enablement.
+ for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
+ if (MatchPluginName(pattern, plugin_ns, plugin)) {
+ return ErrorJSON(llvm::createStringErrorV(
+ "plugin namespace {} does not support querying enablement "
+ "in the debugger domain",
+ plugin_ns.name));
+ }
+ }
+ break;
+ case lldb::ePluginDomainKindTarget: {
+
+ if (!requesting_debugger)
+ return ErrorJSON(llvm::createStringError("no debugger available"));
+
+ auto target = requesting_debugger->GetSelectedTarget();
+ for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
+ if (MatchPluginName(pattern, plugin_ns, plugin)) {
+ if (!plugin_ns.SupportsDomain(ePluginDomainKindTarget)) {
+ return ErrorJSON(llvm::createStringErrorV(
+ "plugin namespace {} does not support querying enablement "
+ "in the target domain",
+ plugin_ns.name));
+ }
+
+ llvm::json::Object plugin_json;
+
+ // Only instrumentation-runtime plugins support the target domain
+ // currently so we can assume that's the case when querying
+ // enablement.
+ assert(plugin_ns.name == "instrumentation-runtime");
+ auto enabled = PluginManager::IsInstrumentationRuntimePluginEnabled(
+ plugin.name, target, domain);
+ if (auto E = enabled.takeError()) {
+ return ErrorJSON(std::move(E));
+ }
+ plugin_json.try_emplace("name", plugin.name);
+ plugin_json.try_emplace("enabled", *enabled);
+ namespace_stats.emplace_back(std::move(plugin_json));
+ }
}
}
+ }
+
if (!namespace_stats.empty())
plugin_stats.try_emplace(plugin_ns.name, std::move(namespace_stats));
}
@@ -2503,13 +2564,110 @@ llvm::StringRef PluginManager::PluginDomainKindToStr(PluginDomainKind kind) {
llvm::Error PluginManager::SetInstrumentationRuntimePluginEnabled(
llvm::StringRef name, bool enable, Debugger &requesting_debugger,
PluginDomainKind domain) {
- if (domain != lldb::ePluginDomainKindGlobal)
- return llvm::createStringErrorV("{} domain is not supported",
- PluginDomainKindToStr(domain));
- if (!GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable))
- return llvm::createStringError("plugin could not be found");
- return llvm::Error::success();
+ auto GetInstrumentationRuntimeTy =
+ [&]() -> llvm::Expected<lldb::InstrumentationRuntimeType> {
+ auto type_cb = GetInstrumentationRuntimeInstances().GetTypeCallbackForName(
+ name, /*enabled_only=*/false);
+ if (!type_cb)
+ return llvm::createStringErrorV(
+ "Could not get InstrumentationRuntimeType for plugin {}", name);
+ return type_cb();
+ };
+
+ switch (domain) {
+ case lldb::ePluginDomainKindGlobal:
+ // Update the global enablement flag
+ if (!GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable))
+ return llvm::createStringErrorV("could not find plugin {}", name);
+ // We should in principle iterate over all debuggers and
+ // enable/disable their runtimes. However, this doesn't work because we need
+ // to because we need to hold `GetDebuggerListMutex` to safely iterate and
+ // can cause deadlock when trying to activate a plugin (creating a
+ // breakpoint might call `Debugger::ReportProgress` which also tries to lock
+ // the mutex). For now just don't update live processes.
+ // FIXME: We should probably emit a warning about this.
+ return llvm::Error::success();
+ case lldb::ePluginDomainKindDebugger: {
+ // Deliberately don't update the global enablement flag here.
+ auto instrumentation_runtime_ty = GetInstrumentationRuntimeTy();
+ if (auto E = instrumentation_runtime_ty.takeError())
+ return E;
+
+ // Loop over all targets in requesting debugger and enable the plugin in
+ // each of them.
+ llvm::Error errors = llvm::Error::success();
+ bool found_targets = false;
+ for (const auto &target_sp :
+ requesting_debugger.GetTargetList().Targets()) {
+ found_targets = true;
+ ProcessSP process_sp = target_sp->GetProcessSP();
+ if (!process_sp || !process_sp->IsAlive())
+ continue;
+ errors = llvm::joinErrors(std::move(errors),
+ process_sp->SetInstrumentationRuntimeEnabled(
+ *instrumentation_runtime_ty, enable));
+ }
+ if (!found_targets)
+ errors =
+ llvm::joinErrors(std::move(errors),
+ llvm::createStringError("debugger has no targets"));
+
+ return errors;
+ }
+ case lldb::ePluginDomainKindTarget: {
+ // Deliberately don't update the global enablement flag here.
+ auto instrumentation_runtime_ty = GetInstrumentationRuntimeTy();
+ if (auto E = instrumentation_runtime_ty.takeError())
+ return E;
+
+ // Enable/disable the plugin for the process associated with the currently
+ // selected target.
+ auto selected_target = requesting_debugger.GetSelectedTarget();
+ if (!selected_target)
+ return llvm::createStringError("no target is selected");
+ ProcessSP process_sp = selected_target->GetProcessSP();
+ if (!process_sp || !process_sp->IsAlive())
+ return llvm::createStringError(
+ "failed to find alive process for selected target");
+ return process_sp->SetInstrumentationRuntimeEnabled(
+ *instrumentation_runtime_ty, enable);
+ }
+ }
+ llvm_unreachable("Unhandled domain");
+}
+
+llvm::Expected<bool> PluginManager::IsInstrumentationRuntimePluginEnabled(
+ llvm::StringRef name, lldb::TargetSP target,
+ lldb::PluginDomainKind domain) {
+
+ switch (domain) {
+
+ case lldb::ePluginDomainKindGlobal:
+ if (auto instance = GetInstrumentationRuntimeInstances().GetInstanceForName(
+ name, /*enabled_only=*/false)) {
+ return instance->enabled;
+ }
+ return llvm::createStringErrorV("cannot find plugin {}", name);
+ case lldb::ePluginDomainKindDebugger:
+ // Not supported. Each process could have a different enablement setting
+ // so it doesn't make sense to return anything.
+ return llvm::createStringError("debugger domain is not supported");
+ case lldb::ePluginDomainKindTarget:
+ if (!target)
+ return llvm::createStringError("target not available");
+ ProcessSP process = target->GetProcessSP();
+ if (!process)
+ return llvm::createStringError("process not available");
+ auto type_cb = GetInstrumentationRuntimeInstances().GetTypeCallbackForName(
+ name, /*enabled_only=*/false);
+ if (!type_cb)
+ return llvm::createStringErrorV("cannot find plugin {} in process", name);
+ lldb::InstrumentationRuntimeType irt = type_cb();
+
+ return process->InstrumentationRuntimeIsEnabled(irt);
+ }
+ llvm_unreachable("Unhandled domain");
}
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..c90ae1189626a 100644
--- a/lldb/source/Target/InstrumentationRuntime.cpp
+++ b/lldb/source/Target/InstrumentationRuntime.cpp
@@ -29,6 +29,9 @@ void InstrumentationRuntime::ModulesDidLoad(
void InstrumentationRuntime::ModulesDidLoad(
lldb_private::ModuleList &module_list) {
+ if (!IsEnabled())
+ return;
+
if (IsActive())
return;
@@ -59,6 +62,47 @@ void InstrumentationRuntime::ModulesDidLoad(
});
}
+llvm::Error InstrumentationRuntime::Enable() {
+ SetEnabled(true);
+
+ if (IsActive())
+ 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 llvm::Error::success();
+ }
+
+ // 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;
+ 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();
+}
+
+llvm::Error InstrumentationRuntime::Disable() {
+ if (IsActive())
+ Deactivate();
+
+ if (IsActive())
+ return llvm::createStringError(
+ "failed to deactivate instrumentation runtime");
+
+ SetEnabled(false);
+ return llvm::Error::success();
+}
+
lldb::ThreadCollectionSP
InstrumentationRuntime::GetBacktracesFromExtendedStopInfo(
StructuredData::ObjectSP info) {
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 3355147862909..106fbb94e0808 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6538,6 +6538,58 @@ Process::GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type) {
return (*pos).second;
}
+llvm::Error
+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 llvm::Error::success();
+
+ // 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;
+ for (auto &cbs : PluginManager::GetInstrumentationRuntimeCallbacks(
+ /*enabled_only=*/false)) {
+ 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 llvm::createStringError("failed to create new instance of plugin");
+
+ m_instrumentation_runtimes[irt] = new_plugin;
+ return new_plugin->Enable();
+}
+
+bool Process::InstrumentationRuntimeIsEnabled(
+ lldb::InstrumentationRuntimeType irt) {
+ if (auto instrumentation_runtime = GetInstrumentationRuntime(irt)) {
+ // This process has a instrumentation runtime instance
+ return instrumentation_runtime->IsEnabled();
+ }
+
+ // There's no instance of the InstrumentationRuntimeType so the plugin must
+ // be disabled.
+ return false;
+}
+
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..78dde34371c73
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/Makefile
@@ -0,0 +1,11 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -fsanitize=undefined -g
+
+a.out: lib_dylib_with_ubsan_issue
+
+include Makefile.rules
+
+lib_dylib_with_ubsan_issue:
+ "$(MAKE)" -f $(MAKEFILE_RULES) \
+ DYLIB_ONLY=YES DYLIB_C_SOURCES=dylib.c DYLIB_NAME=dylib_with_ubsan_issue \
+ CFLAGS_EXTRAS="-fsanitize=undefined -g"
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..f89554693933c
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/TestUbsanPluginEnableDisable.py
@@ -0,0 +1,327 @@
+"""
+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")
+ self.line_fourth_ubsan_issue = line_number("main.c", "// fourth ubsan issue")
+
+ def ubsan_plugin_is_enabled(self, domain: str):
+ return self.plugin_is_enabled(
+ "instrumentation-runtime", "UndefinedBehaviorSanitizer", domain=domain
+ )
+
+ 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(domain="target"))
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="global"))
+
+ 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 for this target
+ self.runCmd(
+ "plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="target"))
+ # Globally the plugin is still marked as enabled
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="global"))
+
+ # Continue. The remaining UBSan issues should not cause
+ # instrumentation stops and the process should exit cleanly.
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+
+ @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 globally before launching the process so that
+ # it isn't loaded when the process starts.
+ self.runCmd("plugin disable instrumentation-runtime.UndefinedBehaviorSanitizer")
+ try:
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="global"))
+
+ # 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 for the target
+ self.runCmd(
+ "plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="target"))
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="global"))
+
+ # 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 past the fourth UBSan issue (plugin is still enabled).
+ process.Continue()
+ self.check_stopped_at_ubsan_issue(self.line_fourth_ubsan_issue)
+
+ # Continue. The process should exit cleanly.
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+ finally:
+ # Now restore the global state to avoid affecting other tests.
+ self.runCmd(
+ "plugin enable --domain global instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="global"))
+
+ @skipUnlessUndefinedBehaviorSanitizer
+ @no_debug_info_test
+ def test_enable_disable_enable(self):
+ """Test that the UBSan plugin can be toggled enable -> disable -> enable
+ in a single session."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+ self.registerSanitizerLibrariesWithTarget(target)
+
+ # Set breakpoints on both breakpoint functions.
+ bp1 = target.BreakpointCreateByName("test_breakpoint")
+ self.assertTrue(bp1.GetNumLocations() > 0)
+ bp2 = target.BreakpointCreateByName("test_breakpoint_2")
+ self.assertTrue(bp2.GetNumLocations() > 0)
+
+ self.runCmd("run")
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="target"))
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="global"))
+
+ process = self.dbg.GetSelectedTarget().process
+
+ # Step 1: ENABLED - we should hit the first UBSan issue.
+ self.check_stopped_at_ubsan_issue(self.line_first_ubsan_issue)
+
+ # Step 2: DISABLE the plugin for this target.
+ self.runCmd(
+ "plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="target"))
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="global"))
+
+ # Continue. Should skip the second UBSan issue and hit the breakpoint.
+ process.Continue()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint", frame.GetFunctionName())
+
+ # Step 3: RE-ENABLE the plugin for this target.
+ self.runCmd(
+ "plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="target"))
+
+ # Continue. Should hit the third UBSan issue.
+ process.Continue()
+ self.check_stopped_at_ubsan_issue(self.line_third_ubsan_issue)
+
+ # Continue. Should hit test_breakpoint_2.
+ process.Continue()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint_2", frame.GetFunctionName())
+
+ # Continue. Should hit the fourth UBSan issue.
+ process.Continue()
+ self.check_stopped_at_ubsan_issue(self.line_fourth_ubsan_issue)
+
+ # Continue. The process should exit cleanly.
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+
+ @skipUnlessUndefinedBehaviorSanitizer
+ @no_debug_info_test
+ def test_disable_enable_disable(self):
+ """Test that the UBSan plugin can be toggled disable -> enable -> disable
+ in a single session."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+ self.registerSanitizerLibrariesWithTarget(target)
+
+ # Disable the UBSan plugin globally before launching the process.
+ self.runCmd("plugin disable instrumentation-runtime.UndefinedBehaviorSanitizer")
+ try:
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="global"))
+
+ # Set breakpoints on both breakpoint functions.
+ bp1 = target.BreakpointCreateByName("test_breakpoint")
+ self.assertTrue(bp1.GetNumLocations() > 0)
+ bp2 = target.BreakpointCreateByName("test_breakpoint_2")
+ self.assertTrue(bp2.GetNumLocations() > 0)
+
+ self.runCmd("run")
+
+ process = self.dbg.GetSelectedTarget().process
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+
+ # Step 1: DISABLED - should skip issues 1 and 2, hit the breakpoint.
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint", frame.GetFunctionName())
+
+ # Step 2: ENABLE the plugin for this target.
+ self.runCmd(
+ "plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="target"))
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="global"))
+
+ # Continue. Should hit the third UBSan issue.
+ process.Continue()
+ self.check_stopped_at_ubsan_issue(self.line_third_ubsan_issue)
+
+ # Continue. Should hit test_breakpoint_2.
+ process.Continue()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint_2", frame.GetFunctionName())
+
+ # Step 3: DISABLE the plugin again.
+ self.runCmd(
+ "plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="target"))
+
+ # Continue. Should skip the fourth UBSan issue and exit cleanly.
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
+ finally:
+ # Restore the global state to avoid affecting other tests.
+ self.runCmd(
+ "plugin enable --domain global instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="global"))
+
+ @skipUnlessUndefinedBehaviorSanitizer
+ @no_debug_info_test
+ def test_disabled_plugin_stays_disabled_after_dlopen(self):
+ """Test that a disabled UBSan plugin is not re-activated when a new
+ shared library is loaded via dlopen (which triggers ModulesDidLoad)."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target, VALID_TARGET)
+ self.registerSanitizerLibrariesWithTarget(target)
+
+ # Set a breakpoint on test_breakpoint_dlopen which is called just
+ # before the dlopen call.
+ bp = target.BreakpointCreateByName("test_breakpoint_dlopen")
+ self.assertTrue(bp.GetNumLocations() > 0)
+
+ # Enable the dlopen test path via environment variable.
+ self.runCmd("settings set target.env-vars DO_DLOPEN=1")
+
+ self.runCmd("run")
+ self.assertTrue(self.ubsan_plugin_is_enabled(domain="target"))
+
+ process = self.dbg.GetSelectedTarget().process
+
+ # We should have stopped on the first UBSan issue.
+ self.check_stopped_at_ubsan_issue(self.line_first_ubsan_issue)
+
+ # Disable the UBSan plugin for this target.
+ self.runCmd(
+ "plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer"
+ )
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="target"))
+
+ # Continue. The plugin is disabled so we should skip remaining ubsan
+ # issues and hit the breakpoint before dlopen.
+ process.Continue()
+ thread = process.GetSelectedThread()
+ frame = thread.GetSelectedFrame()
+ self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint)
+ self.assertIn("test_breakpoint_dlopen", frame.GetFunctionName())
+
+ # The plugin should still be disabled before dlopen.
+ self.assertFalse(self.ubsan_plugin_is_enabled(domain="target"))
+
+ # Continue past dlopen. Loading a new shared library triggers
+ # ModulesDidLoad. The plugin must stay disabled and not re-activate.
+ # If the plugin re-activates, we would stop on the ubsan issue
+ # inside the dylib instead of exiting cleanly.
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateExited)
+ self.assertEqual(process.GetExitStatus(), 0)
diff --git a/lldb/test/API/functionalities/ubsan/plugin_enable_disable/dylib.c b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/dylib.c
new file mode 100644
index 0000000000000..7130c053c5bab
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/dylib.c
@@ -0,0 +1,9 @@
+#include <stdint.h>
+
+__attribute__((noinline, optnone)) int dylib_shift(void) { return 33; }
+
+int LLDB_DYLIB_EXPORT dylib_ubsan_issue(void) {
+ uint32_t x = 0;
+ x = x << dylib_shift(); // dylib ubsan issue
+ return x;
+}
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..534cb57b63736
--- /dev/null
+++ b/lldb/test/API/functionalities/ubsan/plugin_enable_disable/main.c
@@ -0,0 +1,52 @@
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _WIN32
+#include <Windows.h>
+#define DLOPEN(name) LoadLibraryA(name)
+#define DLSYM(handle, name) GetProcAddress((HMODULE)handle, name)
+#define DLCLOSE(handle) FreeLibrary((HMODULE)handle)
+#define DYLIB_NAME "dylib_with_ubsan_issue.dll"
+#else
+#include <dlfcn.h>
+#define DLOPEN(name) dlopen(name, RTLD_NOW)
+#define DLSYM(handle, name) dlsym(handle, name)
+#define DLCLOSE(handle) dlclose(handle)
+#ifdef __APPLE__
+#define DYLIB_NAME "libdylib_with_ubsan_issue.dylib"
+#else
+#define DYLIB_NAME "libdylib_with_ubsan_issue.so"
+#endif
+#endif
+
+__attribute__((noinline, optnone)) void test_breakpoint(void) {}
+__attribute__((noinline, optnone)) void test_breakpoint_2(void) {}
+__attribute__((noinline, optnone)) void test_breakpoint_dlopen(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
+ test_breakpoint_2();
+ x = x << shift(); // fourth ubsan issue
+
+ if (getenv("DO_DLOPEN")) {
+ // dlopen a shared library. This triggers ModulesDidLoad which could
+ // re-activate a disabled plugin.
+ test_breakpoint_dlopen();
+ void *handle = DLOPEN(DYLIB_NAME);
+ if (handle) {
+ int (*func)(void) = (int (*)(void))DLSYM(handle, "dylib_ubsan_issue");
+ if (func)
+ x += func(); // ubsan issue inside dylib
+ DLCLOSE(handle);
+ }
+ }
+
+ 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..90af1aa4ecb5e 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,11 @@ def check_state_soft_trap_with_str(
expected_line_num=line_num,
)
+ def bs_plugin_is_enabled(self, domain: str):
+ return self.plugin_is_enabled(
+ "instrumentation-runtime", "BoundsSafety", domain=domain
+ )
+
# Skip the tests on Windows because they fail due to the stop reason
# being `eStopReasonNon` instead of the expected
# `eStopReasonInstrumentation`.
@@ -91,13 +100,15 @@ def test_call_minimal(self):
self.runCmd("run")
process = self.test_target.process
+ self.assertTrue(self.bs_plugin_is_enabled(domain="global"))
+ self.assertTrue(self.bs_plugin_is_enabled(domain="target"))
# 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 +118,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 +137,15 @@ def test_call_with_str(self):
self.runCmd("run")
process = self.test_target.process
+ self.assertTrue(self.bs_plugin_is_enabled(domain="global"))
+ self.assertTrue(self.bs_plugin_is_enabled(domain="target"))
# 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 +155,105 @@ 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(domain="global"))
+ 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 on the target so we do not stop at the second soft trap
+ self.runCmd(
+ "plugin disable --domain target instrumentation-runtime.BoundsSafety"
+ )
+ self.assertFalse(self.bs_plugin_is_enabled(domain="target"))
+ self.assertTrue(self.bs_plugin_is_enabled(domain="global"))
+
process.Continue()
self.assertEqual(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)
+
+ @skipIfWindows
+ @skipUnlessBoundsSafety
+ @no_debug_info_test
+ def test_call_minimal_disable_then_enable_plugin(self):
+ """
+ Test starting with the plugin disabled on code built with
+ -fbounds-safety-soft-traps=call-minimal and then later enable the
+ plugin
+ """
+ # 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(domain="global"))
+
+ self.build(make_targets=["soft-trap-test-minimal"])
+ self.test_target = self.createTestTarget()
+
+ try:
+ # 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")
+ self.assertFalse(self.bs_plugin_is_enabled(domain="global"))
+ self.assertFalse(self.bs_plugin_is_enabled(domain="target"))
+
+ 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 --domain target instrumentation-runtime.BoundsSafety"
+ )
+ self.assertTrue(self.bs_plugin_is_enabled(domain="target"))
+ self.assertFalse(self.bs_plugin_is_enabled(domain="global"))
+ 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)
+ finally:
+ # Restore the global state to avoid affecting other tests
+ self.runCmd("plugin enable instrumentation-runtime.BoundsSafety")
+ self.assertTrue(self.bs_plugin_is_enabled(domain="global"))
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/Commands/command-plugin-enable-disable-domain-flag-one-target.test b/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-one-target.test
new file mode 100644
index 0000000000000..7f01c784ea502
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-one-target.test
@@ -0,0 +1,85 @@
+# This test validates the --domain flag on the plugin enable and disable
+# commands when one target with an alive process exists.
+
+# RUN: %clang_host -g %S/Inputs/main.c -o %t
+# RUN: %lldb -o "target create %t" -o "b main" -o "run" \
+# RUN: -s %s -o exit 2>&1 | FileCheck %s
+
+# Verify that the plugin is enabled globally and per-target by default.
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Disable via --domain target.
+plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Verify disabled per-target but still enabled globally.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Re-enable via --domain target.
+plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Verify re-enabled per-target and still enabled globally.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Disable via --domain debugger.
+plugin disable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin disable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Verify disabled per-target but still enabled globally.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Re-enable via --domain debugger.
+plugin enable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin enable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Verify re-enabled per-target and still enabled globally.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
diff --git a/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-two-targets.test b/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-two-targets.test
new file mode 100644
index 0000000000000..3aec73a1b8225
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag-two-targets.test
@@ -0,0 +1,117 @@
+# This test validates the --domain flag on the plugin enable and disable
+# commands when two targets with alive processes exist. It verifies that
+# --domain target only affects the selected target while --domain debugger
+# affects both targets.
+
+# RUN: %clang_host -g %S/Inputs/main.c -o %t
+# RUN: %lldb \
+# RUN: -o "target create %t" -o "b main" -o "run" \
+# RUN: -o "target create %t" -o "b main" -o "run" \
+# RUN: -s %s -o exit 2>&1 | FileCheck %s
+
+# Verify initial state: global and target 1 (selected) are enabled.
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# --- Test --domain target only affects the selected target ---
+
+# Disable the plugin for target 1 (currently selected).
+plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Verify target 1 shows disabled.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Verify global is still enabled.
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Switch to target 0 and verify it is still enabled (unaffected).
+target select 0
+# CHECK-LABEL: target select 0
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Re-enable target 1.
+target select 1
+# CHECK-LABEL: target select 1
+plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Verify target 1 is re-enabled.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# --- Test --domain debugger affects both targets ---
+
+# Disable the plugin for all targets via debugger domain.
+plugin disable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin disable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Verify target 1 (selected) shows disabled.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Verify global is still enabled.
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Switch to target 0 and verify it is also disabled.
+target select 0
+# CHECK-LABEL: target select 0
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [-] UndefinedBehaviorSanitizer
+
+# Re-enable both targets via debugger domain.
+plugin enable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin enable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Verify target 0 (selected) shows re-enabled.
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Switch to target 1 and verify it is also re-enabled.
+target select 1
+# CHECK-LABEL: target select 1
+plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
+
+# Verify global is still enabled.
+plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK-LABEL: plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
+# CHECK: instrumentation-runtime
+# CHECK: [+] UndefinedBehaviorSanitizer
diff --git a/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag.test b/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag.test
index 6cdc900c261c1..ff4385e659450 100644
--- a/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag.test
+++ b/lldb/test/Shell/Commands/command-plugin-enable-disable-domain-flag.test
@@ -46,13 +46,13 @@ plugin enable --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
# (instrumentation-runtime.UndefinedBehaviorSanitizer).
# RUN: %lldb -o "plugin enable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_ENABLE_IR_DEBUGGER
-# ERROR_ENABLE_IR_DEBUGGER: error: failed to enable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: debugger domain is not supported
+# ERROR_ENABLE_IR_DEBUGGER: error: failed to enable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: debugger has no targets
# RUN: %lldb -o "plugin enable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_ENABLE_IR_TARGET
-# ERROR_ENABLE_IR_TARGET: error: failed to enable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: target domain is not supported
+# ERROR_ENABLE_IR_TARGET: error: failed to enable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: no target is selected
# RUN: %lldb -o "plugin disable --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_DISABLE_IR_DEBUGGER
-# ERROR_DISABLE_IR_DEBUGGER: error: failed to disable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: debugger domain is not supported
+# ERROR_DISABLE_IR_DEBUGGER: error: failed to disable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: debugger has no targets
# RUN: %lldb -o "plugin disable --domain target instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_DISABLE_IR_TARGET
-# ERROR_DISABLE_IR_TARGET: error: failed to disable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: target domain is not supported
+# ERROR_DISABLE_IR_TARGET: error: failed to disable plugin instrumentation-runtime.UndefinedBehaviorSanitizer: no target is selected
diff --git a/lldb/test/Shell/Commands/command-plugin-list-domain-flag.test b/lldb/test/Shell/Commands/command-plugin-list-domain-flag.test
index 7aacb522843c7..63d622ab981bc 100644
--- a/lldb/test/Shell/Commands/command-plugin-list-domain-flag.test
+++ b/lldb/test/Shell/Commands/command-plugin-list-domain-flag.test
@@ -23,34 +23,34 @@ plugin list --domain global instrumentation-runtime.UndefinedBehaviorSanitizer
# Test plugin list --domain debugger returns an error (text format).
# RUN: %lldb -o "plugin list --domain debugger language.cplusplus" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_DEBUGGER
-# ERROR_LIST_DEBUGGER: error: debugger domain is not supported
+# ERROR_LIST_DEBUGGER: error: plugin namespace language does not support querying enablement in the debugger domain
# Test plugin list --domain target returns an error (text format).
# RUN: %lldb -o "plugin list --domain target language.cplusplus" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_TARGET
-# ERROR_LIST_TARGET: error: target domain is not supported
+# ERROR_LIST_TARGET: error: plugin namespace language does not support querying enablement in the target domain
# Test plugin list --domain debugger --json returns an error (json format).
# RUN: %lldb -o "plugin list --domain debugger --json language.cplusplus" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_JSON_DEBUGGER
-# ERROR_LIST_JSON_DEBUGGER: error: debugger domain is not supported
+# ERROR_LIST_JSON_DEBUGGER: "error": "plugin namespace language does not support querying enablement in the debugger domain"
# Test plugin list --domain target --json returns an error (json format).
# RUN: %lldb -o "plugin list --domain target --json language.cplusplus" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_JSON_TARGET
-# ERROR_LIST_JSON_TARGET: error: target domain is not supported
+# ERROR_LIST_JSON_TARGET: "error": "plugin namespace language does not support querying enablement in the target domain"
# Error cases with on a plugin that supports all domains.
# Test plugin list --domain debugger returns an error for instrumentation-runtime plugin (text format).
# RUN: %lldb -o "plugin list --domain debugger instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_DEBUGGER_IR
-# ERROR_LIST_DEBUGGER_IR: error: debugger domain is not supported
+# ERROR_LIST_DEBUGGER_IR: error: plugin namespace instrumentation-runtime does not support querying enablement in the debugger domain
# Test plugin list --domain target returns an error for instrumentation-runtime plugin (text format).
# RUN: %lldb -o "plugin list --domain target instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_TARGET_IR
-# ERROR_LIST_TARGET_IR: error: target domain is not supported
+# ERROR_LIST_TARGET_IR: error: target not available
# Test plugin list --domain debugger --json returns an error for instrumentation-runtime plugin (json format).
# RUN: %lldb -o "plugin list --domain debugger --json instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_JSON_DEBUGGER_IR
-# ERROR_LIST_JSON_DEBUGGER_IR: error: debugger domain is not supported
+# ERROR_LIST_JSON_DEBUGGER_IR: "error": "plugin namespace instrumentation-runtime does not support querying enablement in the debugger domain"
# Test plugin list --domain target --json returns an error for instrumentation-runtime plugin (json format).
# RUN: %lldb -o "plugin list --domain target --json instrumentation-runtime.UndefinedBehaviorSanitizer" 2>&1 | FileCheck %s --check-prefix=ERROR_LIST_JSON_TARGET_IR
-# ERROR_LIST_JSON_TARGET_IR: error: target domain is not supported
+# ERROR_LIST_JSON_TARGET_IR: "error": "target not available"
>From 8f5d5c0e0b6795a573ddd7b629f9bc991596c0bd Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Mon, 11 May 2026 20:45:25 -0700
Subject: [PATCH 2/6] Refactor some plugin enablement code out of
`OutputTextFormat` into `PluginManager::IsPluginEnabled()`
---
lldb/include/lldb/Core/PluginManager.h | 5 ++
lldb/source/Commands/CommandObjectPlugin.cpp | 61 ++++----------------
lldb/source/Core/PluginManager.cpp | 25 ++++++++
3 files changed, 40 insertions(+), 51 deletions(-)
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 2a2c216d296c7..46b4d0242e10d 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -217,6 +217,11 @@ class PluginManager {
const PluginNamespace &plugin_ns,
const RegisteredPluginInfo &plugin);
+ static llvm::Expected<bool>
+ IsPluginEnabled(const PluginNamespace &plugin_ns,
+ const RegisteredPluginInfo &plugin,
+ Debugger &requesting_debugger, lldb::PluginDomainKind domain);
+
// ABI
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
ABICreateInstance create_callback);
diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp
index 4412bb1d38793..6dd413f44707a 100644
--- a/lldb/source/Commands/CommandObjectPlugin.cpp
+++ b/lldb/source/Commands/CommandObjectPlugin.cpp
@@ -309,63 +309,22 @@ List only the plugin 'foo' matching a fully qualified name exactly
CommandReturnObject &result,
Debugger &requesting_debugger,
PluginDomainKind domain) {
-
- auto PrintEnablement = [&](bool enabled,
- const RegisteredPluginInfo &plugin) {
- result.AppendMessageWithFormatv(" {0} {1, -30} {2}",
- enabled ? "[+]" : "[-]", plugin.name,
- plugin.description);
- };
-
for (const llvm::StringRef pattern : patterns) {
int num_matching = ActOnMatchingPlugins(
pattern, [&](const PluginNamespace &plugin_namespace,
const std::vector<RegisteredPluginInfo> &plugins) {
- switch (domain) {
-
- case lldb::ePluginDomainKindGlobal: {
- result.AppendMessage(plugin_namespace.name);
- for (auto &plugin : plugins)
- PrintEnablement(plugin.enabled, plugin);
- break;
- }
- case lldb::ePluginDomainKindDebugger:
- // Currently enablement status of plugins is not stored inside
- // debugger instances. If that ever changes we can support
- // querying enablement here.
- result.AppendErrorWithFormatv(
- "plugin namespace {0} does not support querying enablement "
- "in the debugger domain",
- plugin_namespace.name);
- return;
- case lldb::ePluginDomainKindTarget:
- if (!plugin_namespace.SupportsDomain(
- lldb::ePluginDomainKindTarget)) {
- result.AppendErrorWithFormatv(
- "plugin namespace {0} does not support querying enablement "
- "in the target domain",
- plugin_namespace.name);
+ result.AppendMessage(plugin_namespace.name);
+ for (auto &plugin : plugins) {
+ auto enabled = PluginManager::IsPluginEnabled(
+ plugin_namespace, plugin, requesting_debugger, domain);
+ if (llvm::Error E = enabled.takeError()) {
+ result.AppendErrorWithFormatv("{}",
+ llvm::toString(std::move(E)));
return;
}
- auto target = requesting_debugger.GetSelectedTarget();
-
- // Only instrumentation-runtime plugins support the target domain
- // currently so we can assume that's the case when querying
- // enablement.
- assert(plugin_namespace.name == "instrumentation-runtime");
- result.AppendMessage(plugin_namespace.name);
- for (auto &plugin : plugins) {
- auto enabled =
- PluginManager::IsInstrumentationRuntimePluginEnabled(
- plugin.name, target, domain);
- if (llvm::Error E = enabled.takeError()) {
- result.AppendErrorWithFormatv("{}",
- llvm::toString(std::move(E)));
- continue;
- }
- PrintEnablement(*enabled, plugin);
- }
- break;
+ result.AppendMessageWithFormatv(" {0} {1, -30} {2}",
+ *enabled ? "[+]" : "[-]",
+ plugin.name, plugin.description);
}
});
if (num_matching == 0) {
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 92c681a428821..0bcd80c473119 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -534,6 +534,31 @@ bool PluginManager::MatchPluginName(llvm::StringRef pattern,
return pattern == qualified_name;
}
+llvm::Expected<bool> PluginManager::IsPluginEnabled(
+ const PluginNamespace &plugin_ns, const RegisteredPluginInfo &plugin,
+ Debugger &requesting_debugger, lldb::PluginDomainKind domain) {
+ switch (domain) {
+ case lldb::ePluginDomainKindGlobal:
+ return plugin.enabled;
+ case lldb::ePluginDomainKindDebugger:
+ return llvm::createStringErrorV(
+ "plugin namespace {0} does not support querying "
+ "enablement in the debugger domain",
+ plugin_ns.name);
+ case lldb::ePluginDomainKindTarget:
+ if (!plugin_ns.SupportsDomain(lldb::ePluginDomainKindTarget))
+ return llvm::createStringErrorV(
+ "plugin namespace {0} does not support querying "
+ "enablement in the target domain",
+ plugin_ns.name);
+ {
+ auto target = requesting_debugger.GetSelectedTarget();
+ return IsInstrumentationRuntimePluginEnabled(plugin.name, target, domain);
+ }
+ }
+ llvm_unreachable("Unhandled domain");
+}
+
template <typename Callback> struct PluginInstance {
typedef Callback CallbackType;
>From 16a96cd0490b2b195a970e5393f58e1140ec19cb Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Tue, 12 May 2026 01:13:22 -0700
Subject: [PATCH 3/6] Refactor
---
lldb/include/lldb/Core/PluginManager.h | 7 +-
lldb/source/Commands/CommandObjectPlugin.cpp | 3 +-
lldb/source/Core/PluginManager.cpp | 72 ++++----------------
3 files changed, 20 insertions(+), 62 deletions(-)
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 46b4d0242e10d..c9a9dfb5ef213 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -217,10 +217,9 @@ class PluginManager {
const PluginNamespace &plugin_ns,
const RegisteredPluginInfo &plugin);
- static llvm::Expected<bool>
- IsPluginEnabled(const PluginNamespace &plugin_ns,
- const RegisteredPluginInfo &plugin,
- Debugger &requesting_debugger, lldb::PluginDomainKind domain);
+ static llvm::Expected<bool> IsPluginEnabled(
+ const PluginNamespace &plugin_ns, const RegisteredPluginInfo &plugin,
+ lldb::DebuggerSP requesting_debugger, lldb::PluginDomainKind domain);
// ABI
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp
index 6dd413f44707a..2f76cacaf9cc1 100644
--- a/lldb/source/Commands/CommandObjectPlugin.cpp
+++ b/lldb/source/Commands/CommandObjectPlugin.cpp
@@ -316,7 +316,8 @@ List only the plugin 'foo' matching a fully qualified name exactly
result.AppendMessage(plugin_namespace.name);
for (auto &plugin : plugins) {
auto enabled = PluginManager::IsPluginEnabled(
- plugin_namespace, plugin, requesting_debugger, domain);
+ plugin_namespace, plugin,
+ requesting_debugger.shared_from_this(), domain);
if (llvm::Error E = enabled.takeError()) {
result.AppendErrorWithFormatv("{}",
llvm::toString(std::move(E)));
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 0bcd80c473119..4c273639b6e1d 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -445,7 +445,6 @@ llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern,
auto ErrorJSON = [&plugin_stats](llvm::Error error) -> llvm::json::Object & {
plugin_stats.clear();
- // Try to put a diagnostic in JSON when a error occurs
plugin_stats.try_emplace("error", llvm::toString(std::move(error)));
return plugin_stats;
};
@@ -453,62 +452,19 @@ llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern,
for (const PluginNamespace &plugin_ns : GetPluginNamespaces()) {
llvm::json::Array namespace_stats;
- switch (domain) {
- case lldb::ePluginDomainKindGlobal: {
- for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
- if (MatchPluginName(pattern, plugin_ns, plugin)) {
- llvm::json::Object plugin_json;
-
- plugin_json.try_emplace("name", plugin.name);
- plugin_json.try_emplace("enabled", plugin.enabled);
- namespace_stats.emplace_back(std::move(plugin_json));
- }
- }
- break;
- }
- case lldb::ePluginDomainKindDebugger:
- // Not supported currently by any plugin when checking for enablement.
- for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
- if (MatchPluginName(pattern, plugin_ns, plugin)) {
- return ErrorJSON(llvm::createStringErrorV(
- "plugin namespace {} does not support querying enablement "
- "in the debugger domain",
- plugin_ns.name));
- }
- }
- break;
- case lldb::ePluginDomainKindTarget: {
+ for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
+ if (!MatchPluginName(pattern, plugin_ns, plugin))
+ continue;
- if (!requesting_debugger)
- return ErrorJSON(llvm::createStringError("no debugger available"));
+ llvm::Expected<bool> enabled =
+ IsPluginEnabled(plugin_ns, plugin, requesting_debugger, domain);
+ if (auto E = enabled.takeError())
+ return ErrorJSON(std::move(E));
- auto target = requesting_debugger->GetSelectedTarget();
- for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
- if (MatchPluginName(pattern, plugin_ns, plugin)) {
- if (!plugin_ns.SupportsDomain(ePluginDomainKindTarget)) {
- return ErrorJSON(llvm::createStringErrorV(
- "plugin namespace {} does not support querying enablement "
- "in the target domain",
- plugin_ns.name));
- }
-
- llvm::json::Object plugin_json;
-
- // Only instrumentation-runtime plugins support the target domain
- // currently so we can assume that's the case when querying
- // enablement.
- assert(plugin_ns.name == "instrumentation-runtime");
- auto enabled = PluginManager::IsInstrumentationRuntimePluginEnabled(
- plugin.name, target, domain);
- if (auto E = enabled.takeError()) {
- return ErrorJSON(std::move(E));
- }
- plugin_json.try_emplace("name", plugin.name);
- plugin_json.try_emplace("enabled", *enabled);
- namespace_stats.emplace_back(std::move(plugin_json));
- }
- }
- }
+ llvm::json::Object plugin_json;
+ plugin_json.try_emplace("name", plugin.name);
+ plugin_json.try_emplace("enabled", *enabled);
+ namespace_stats.emplace_back(std::move(plugin_json));
}
if (!namespace_stats.empty())
@@ -536,7 +492,7 @@ bool PluginManager::MatchPluginName(llvm::StringRef pattern,
llvm::Expected<bool> PluginManager::IsPluginEnabled(
const PluginNamespace &plugin_ns, const RegisteredPluginInfo &plugin,
- Debugger &requesting_debugger, lldb::PluginDomainKind domain) {
+ lldb::DebuggerSP requesting_debugger, lldb::PluginDomainKind domain) {
switch (domain) {
case lldb::ePluginDomainKindGlobal:
return plugin.enabled;
@@ -551,8 +507,10 @@ llvm::Expected<bool> PluginManager::IsPluginEnabled(
"plugin namespace {0} does not support querying "
"enablement in the target domain",
plugin_ns.name);
+ if (!requesting_debugger)
+ return llvm::createStringError("no debugger available");
{
- auto target = requesting_debugger.GetSelectedTarget();
+ auto target = requesting_debugger->GetSelectedTarget();
return IsInstrumentationRuntimePluginEnabled(plugin.name, target, domain);
}
}
>From 7a8c15587b4ffd2961aabb4bb1bce41d7c0dd1ec Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Tue, 12 May 2026 14:25:22 -0700
Subject: [PATCH 4/6] Fix typo in comment
---
lldb/source/Core/PluginManager.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 4c273639b6e1d..d7bf9f6481829 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -2563,12 +2563,12 @@ llvm::Error PluginManager::SetInstrumentationRuntimePluginEnabled(
// Update the global enablement flag
if (!GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable))
return llvm::createStringErrorV("could not find plugin {}", name);
- // We should in principle iterate over all debuggers and
- // enable/disable their runtimes. However, this doesn't work because we need
- // to because we need to hold `GetDebuggerListMutex` to safely iterate and
- // can cause deadlock when trying to activate a plugin (creating a
- // breakpoint might call `Debugger::ReportProgress` which also tries to lock
- // the mutex). For now just don't update live processes.
+ // We should in principle iterate over all debuggers and enable/disable
+ // their runtimes. However, this doesn't work because we need to hold
+ // `GetDebuggerListMutex` to safely iterate and this can cause deadlock when
+ // trying to activate a plugin (creating a breakpoint might call
+ // `Debugger::ReportProgress` which also tries to lock the mutex). For now
+ // just don't update live processes.
// FIXME: We should probably emit a warning about this.
return llvm::Error::success();
case lldb::ePluginDomainKindDebugger: {
>From 1cb5c298cef4c4d089524e7b4a424a36f4b524f9 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 14 May 2026 15:13:20 -0700
Subject: [PATCH 5/6] Address Jim's feedback to do
`requesting_debugger.GetSelectedTarget()` as early as possible.
---
lldb/include/lldb/Core/PluginManager.h | 12 +++----
lldb/source/Commands/CommandObjectPlugin.cpp | 37 +++++++++++++-------
lldb/source/Core/PluginManager.cpp | 20 +++++------
3 files changed, 39 insertions(+), 30 deletions(-)
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index c9a9dfb5ef213..e6c278080c8b9 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -80,7 +80,7 @@ struct RegisteredPluginInfo {
using GetPluginInfo = std::function<llvm::SmallVector<RegisteredPluginInfo>()>;
using SetPluginEnabledGlobalDomain = std::function<bool(llvm::StringRef, bool)>;
using SetPluginEnabledAllDomains = std::function<llvm::Error(
- llvm::StringRef, bool, Debugger &, lldb::PluginDomainKind)>;
+ llvm::StringRef, bool, Debugger &, lldb::TargetSP, lldb::PluginDomainKind)>;
class PluginNamespace {
public:
static constexpr uint8_t kAllDomains = lldb::ePluginDomainKindGlobal |
@@ -203,6 +203,7 @@ class PluginManager {
static llvm::json::Object
GetJSON(llvm::StringRef pattern = "",
lldb::DebuggerSP requesting_debugger = nullptr,
+ lldb::TargetSP selected_target = nullptr,
lldb::PluginDomainKind domain =
lldb::PluginDomainKind::ePluginDomainKindGlobal);
@@ -219,7 +220,7 @@ class PluginManager {
static llvm::Expected<bool> IsPluginEnabled(
const PluginNamespace &plugin_ns, const RegisteredPluginInfo &plugin,
- lldb::DebuggerSP requesting_debugger, lldb::PluginDomainKind domain);
+ lldb::TargetSP selected_target, lldb::PluginDomainKind domain);
// ABI
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
@@ -814,10 +815,9 @@ class PluginManager {
static llvm::SmallVector<RegisteredPluginInfo>
GetInstrumentationRuntimePluginInfo();
static llvm::StringRef PluginDomainKindToStr(lldb::PluginDomainKind kind);
- static llvm::Error
- SetInstrumentationRuntimePluginEnabled(llvm::StringRef name, bool enable,
- Debugger &requesting_debugger,
- lldb::PluginDomainKind domain);
+ static llvm::Error SetInstrumentationRuntimePluginEnabled(
+ llvm::StringRef name, bool enable, Debugger &requesting_debugger,
+ lldb::TargetSP selected_target, lldb::PluginDomainKind domain);
static llvm::Expected<bool>
IsInstrumentationRuntimePluginEnabled(llvm::StringRef name,
lldb::TargetSP target,
diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp
index 2f76cacaf9cc1..7c993e5b85784 100644
--- a/lldb/source/Commands/CommandObjectPlugin.cpp
+++ b/lldb/source/Commands/CommandObjectPlugin.cpp
@@ -87,6 +87,7 @@ static int ActOnMatchingPlugins(
int SetEnableOnMatchingPlugins(const llvm::StringRef &pattern,
CommandReturnObject &result, bool enabled,
Debugger &requesting_debugger,
+ lldb::TargetSP selected_target,
PluginDomainKind domain) {
return ActOnMatchingPlugins(
pattern, [&](const PluginNamespace &plugin_namespace,
@@ -136,7 +137,8 @@ int SetEnableOnMatchingPlugins(const llvm::StringRef &pattern,
}
assert(plugin_namespace.GetSetEnabledAllDomainsFn().has_value());
llvm::Error error = (*plugin_namespace.GetSetEnabledAllDomainsFn())(
- plugin.name, enabled, requesting_debugger, domain);
+ plugin.name, enabled, requesting_debugger, selected_target,
+ domain);
if (error) {
result.AppendErrorWithFormatv("failed to {} plugin {}.{}: {}",
@@ -274,22 +276,28 @@ List only the plugin 'foo' matching a fully qualified name exactly
for (size_t i = 0; i < argc; ++i)
patterns.push_back(command[i].ref());
+ Debugger &requesting_debugger = GetDebugger();
+ TargetSP selected_target = requesting_debugger.GetSelectedTarget();
if (m_options.m_json_format)
- OutputJsonFormat(patterns, result, GetDebugger(), m_options.m_domain);
+ OutputJsonFormat(patterns, result, requesting_debugger, selected_target,
+ m_options.m_domain);
else
- OutputTextFormat(patterns, result, GetDebugger(), m_options.m_domain);
+ OutputTextFormat(patterns, result, requesting_debugger, selected_target,
+ m_options.m_domain);
}
private:
void OutputJsonFormat(const std::vector<llvm::StringRef> &patterns,
CommandReturnObject &result,
Debugger &requesting_debugger,
+ lldb::TargetSP selected_target,
PluginDomainKind domain) {
llvm::json::Object obj;
bool found_empty = false;
for (const llvm::StringRef pattern : patterns) {
llvm::json::Object pat_obj = PluginManager::GetJSON(
- pattern, requesting_debugger.shared_from_this(), domain);
+ pattern, requesting_debugger.shared_from_this(), selected_target,
+ domain);
if (pat_obj.empty()) {
found_empty = true;
result.AppendErrorWithFormat(
@@ -308,6 +316,7 @@ List only the plugin 'foo' matching a fully qualified name exactly
void OutputTextFormat(const std::vector<llvm::StringRef> &patterns,
CommandReturnObject &result,
Debugger &requesting_debugger,
+ lldb::TargetSP selected_target,
PluginDomainKind domain) {
for (const llvm::StringRef pattern : patterns) {
int num_matching = ActOnMatchingPlugins(
@@ -316,8 +325,7 @@ List only the plugin 'foo' matching a fully qualified name exactly
result.AppendMessage(plugin_namespace.name);
for (auto &plugin : plugins) {
auto enabled = PluginManager::IsPluginEnabled(
- plugin_namespace, plugin,
- requesting_debugger.shared_from_this(), domain);
+ plugin_namespace, plugin, selected_target, domain);
if (llvm::Error E = enabled.takeError()) {
result.AppendErrorWithFormatv("{}",
llvm::toString(std::move(E)));
@@ -341,6 +349,7 @@ List only the plugin 'foo' matching a fully qualified name exactly
static void DoPluginEnableDisable(Args &command, CommandReturnObject &result,
bool enable, Debugger &requesting_debugger,
+ lldb::TargetSP selected_target,
PluginDomainKind domain) {
const char *name = enable ? "enable" : "disable";
size_t argc = command.GetArgumentCount();
@@ -353,8 +362,8 @@ static void DoPluginEnableDisable(Args &command, CommandReturnObject &result,
for (size_t i = 0; i < argc; ++i) {
llvm::StringRef pattern = command[i].ref();
- int num_matching = SetEnableOnMatchingPlugins(pattern, result, enable,
- requesting_debugger, domain);
+ int num_matching = SetEnableOnMatchingPlugins(
+ pattern, result, enable, requesting_debugger, selected_target, domain);
if (num_matching == 0) {
result.AppendErrorWithFormat(
@@ -434,8 +443,10 @@ class CommandObjectPluginEnable : public CommandObjectParsed {
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
- DoPluginEnableDisable(command, result, /*enable=*/true, GetDebugger(),
- m_options.m_domain);
+ Debugger &debugger = GetDebugger();
+ auto selected_target = debugger.GetSelectedTarget();
+ DoPluginEnableDisable(command, result, /*enable=*/true, debugger,
+ selected_target, m_options.m_domain);
}
PluginDomainOptions m_options;
@@ -464,8 +475,10 @@ class CommandObjectPluginDisable : public CommandObjectParsed {
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
- DoPluginEnableDisable(command, result, /*enable=*/false, GetDebugger(),
- m_options.m_domain);
+ Debugger &debugger = GetDebugger();
+ auto selected_target = debugger.GetSelectedTarget();
+ DoPluginEnableDisable(command, result, /*enable=*/false, debugger,
+ selected_target, m_options.m_domain);
}
PluginDomainOptions m_options;
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index d7bf9f6481829..039d67abe5298 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -440,6 +440,7 @@ llvm::ArrayRef<PluginNamespace> PluginManager::GetPluginNamespaces() {
llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern,
lldb::DebuggerSP requesting_debugger,
+ lldb::TargetSP selected_target,
lldb::PluginDomainKind domain) {
llvm::json::Object plugin_stats;
@@ -457,7 +458,7 @@ llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern,
continue;
llvm::Expected<bool> enabled =
- IsPluginEnabled(plugin_ns, plugin, requesting_debugger, domain);
+ IsPluginEnabled(plugin_ns, plugin, selected_target, domain);
if (auto E = enabled.takeError())
return ErrorJSON(std::move(E));
@@ -492,7 +493,7 @@ bool PluginManager::MatchPluginName(llvm::StringRef pattern,
llvm::Expected<bool> PluginManager::IsPluginEnabled(
const PluginNamespace &plugin_ns, const RegisteredPluginInfo &plugin,
- lldb::DebuggerSP requesting_debugger, lldb::PluginDomainKind domain) {
+ lldb::TargetSP selected_target, lldb::PluginDomainKind domain) {
switch (domain) {
case lldb::ePluginDomainKindGlobal:
return plugin.enabled;
@@ -507,12 +508,10 @@ llvm::Expected<bool> PluginManager::IsPluginEnabled(
"plugin namespace {0} does not support querying "
"enablement in the target domain",
plugin_ns.name);
- if (!requesting_debugger)
- return llvm::createStringError("no debugger available");
- {
- auto target = requesting_debugger->GetSelectedTarget();
- return IsInstrumentationRuntimePluginEnabled(plugin.name, target, domain);
- }
+ // Currently only instrumentation-runtime plugins support this domain.
+ assert(plugin_ns.name == "instrumentation-runtime");
+ return IsInstrumentationRuntimePluginEnabled(plugin.name, selected_target,
+ domain);
}
llvm_unreachable("Unhandled domain");
}
@@ -2546,7 +2545,7 @@ llvm::StringRef PluginManager::PluginDomainKindToStr(PluginDomainKind kind) {
llvm::Error PluginManager::SetInstrumentationRuntimePluginEnabled(
llvm::StringRef name, bool enable, Debugger &requesting_debugger,
- PluginDomainKind domain) {
+ lldb::TargetSP selected_target, PluginDomainKind domain) {
auto GetInstrumentationRuntimeTy =
[&]() -> llvm::Expected<lldb::InstrumentationRuntimeType> {
@@ -2604,9 +2603,6 @@ llvm::Error PluginManager::SetInstrumentationRuntimePluginEnabled(
if (auto E = instrumentation_runtime_ty.takeError())
return E;
- // Enable/disable the plugin for the process associated with the currently
- // selected target.
- auto selected_target = requesting_debugger.GetSelectedTarget();
if (!selected_target)
return llvm::createStringError("no target is selected");
ProcessSP process_sp = selected_target->GetProcessSP();
>From 8c38f8f3e911ebe4e13ef70fcb5d8a0c4d47e31a Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Thu, 14 May 2026 15:55:43 -0700
Subject: [PATCH 6/6] Make `IsAlive()` a runtime check rather than an assert
---
lldb/source/Target/Process.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 106fbb94e0808..9d71986f5c34b 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -6541,7 +6541,8 @@ Process::GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type) {
llvm::Error
Process::SetInstrumentationRuntimeEnabled(InstrumentationRuntimeType irt,
bool enabled) {
- assert(IsAlive());
+ if (!IsAlive())
+ return llvm::createStringErrorV("process {} is not alive", GetID());
if (auto instrumentation_runtime = GetInstrumentationRuntime(irt)) {
// This process already has an instance of this plugin so just
More information about the lldb-commits
mailing list