[Lldb-commits] [lldb] Plugin toggle roadmap (PR #132876)

David Peixotto via lldb-commits lldb-commits at lists.llvm.org
Thu Mar 27 15:19:19 PDT 2025


https://github.com/dmpots updated https://github.com/llvm/llvm-project/pull/132876

>From 21ac6ae78d53feba8322e3aca724ecb01ff7c847 Mon Sep 17 00:00:00 2001
From: David Peixotto <peix at meta.com>
Date: Thu, 13 Mar 2025 16:13:45 -0700
Subject: [PATCH 1/3] Remove raw access to PluginInstances vector

This commit modifies the PluginInstances class to remove direct access
to the m_instances vector. Instead, we expose a new `GetSnapshot` method
that returns a copy of the current state of the instances vector.  All
external iteration over the instances is updated to use the new method.

The motivation for the change is to allow modifying the way we store
instances without having to change all the clients. This is a
preliminary change to allow enabling/disabling of plugins in which case
we want to iterate over only enabled plugins.

We also considered using a custom iterator that wraps the vector
iterator and can skip over disabled instances. That works, but the
iterator code is a bit messy with all template and typedefs to make a
compliant iterator.
---
 lldb/source/Core/PluginManager.cpp | 168 ++++++++++++++---------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 80c9465f9af72..95eb940efcef2 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -226,30 +226,26 @@ template <typename Instance> class PluginInstances {
   }
 
   typename Instance::CallbackType GetCallbackAtIndex(uint32_t idx) {
-    if (Instance *instance = GetInstanceAtIndex(idx))
+    if (const Instance *instance = GetInstanceAtIndex(idx))
       return instance->create_callback;
     return nullptr;
   }
 
   llvm::StringRef GetDescriptionAtIndex(uint32_t idx) {
-    if (Instance *instance = GetInstanceAtIndex(idx))
+    if (const Instance *instance = GetInstanceAtIndex(idx))
       return instance->description;
     return "";
   }
 
   llvm::StringRef GetNameAtIndex(uint32_t idx) {
-    if (Instance *instance = GetInstanceAtIndex(idx))
+    if (const Instance *instance = GetInstanceAtIndex(idx))
       return instance->name;
     return "";
   }
 
   typename Instance::CallbackType GetCallbackForName(llvm::StringRef name) {
-    if (name.empty())
-      return nullptr;
-    for (auto &instance : m_instances) {
-      if (name == instance.name)
-        return instance.create_callback;
-    }
+    if (const Instance *instance = GetInstanceForName(name))
+      return instance->create_callback;
     return nullptr;
   }
 
@@ -260,12 +256,33 @@ template <typename Instance> class PluginInstances {
     }
   }
 
-  const std::vector<Instance> &GetInstances() const { return m_instances; }
-  std::vector<Instance> &GetInstances() { return m_instances; }
+  // Return a copy of all the enabled instances.
+  // Note that this is a copy of the internal state so modifications
+  // to the returned instances will not be reflected back to instances
+  // stored by the PluginInstances object.
+  std::vector<Instance> GetSnapshot() { return m_instances; }
+
+  const Instance *GetInstanceAtIndex(uint32_t idx) {
+    uint32_t count = 0;
+
+    return FindEnabledInstance(
+        [&](const Instance &instance) { return count++ == idx; });
+  }
+
+  const Instance *GetInstanceForName(llvm::StringRef name) {
+    if (name.empty())
+      return nullptr;
 
-  Instance *GetInstanceAtIndex(uint32_t idx) {
-    if (idx < m_instances.size())
-      return &m_instances[idx];
+    return FindEnabledInstance(
+        [&](const Instance &instance) { return instance.name == name; });
+  }
+
+  const Instance *
+  FindEnabledInstance(std::function<bool(const Instance &)> predicate) const {
+    for (const auto &instance : m_instances) {
+      if (predicate(instance))
+        return &instance;
+    }
     return nullptr;
   }
 
@@ -571,17 +588,15 @@ PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(uint32_t idx) {
 
 LanguageRuntimeGetCommandObject
 PluginManager::GetLanguageRuntimeGetCommandObjectAtIndex(uint32_t idx) {
-  const auto &instances = GetLanguageRuntimeInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].command_callback;
+  if (auto instance = GetLanguageRuntimeInstances().GetInstanceAtIndex(idx))
+    return instance->command_callback;
   return nullptr;
 }
 
 LanguageRuntimeGetExceptionPrecondition
 PluginManager::GetLanguageRuntimeGetExceptionPreconditionAtIndex(uint32_t idx) {
-  const auto &instances = GetLanguageRuntimeInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].precondition_callback;
+  if (auto instance = GetLanguageRuntimeInstances().GetInstanceAtIndex(idx))
+    return instance->precondition_callback;
   return nullptr;
 }
 
@@ -643,12 +658,7 @@ bool PluginManager::IsRegisteredObjectFilePluginName(llvm::StringRef name) {
   if (name.empty())
     return false;
 
-  const auto &instances = GetObjectFileInstances().GetInstances();
-  for (auto &instance : instances) {
-    if (instance.name == name)
-      return true;
-  }
-  return false;
+  return GetObjectFileInstances().GetInstanceForName(name) != nullptr;
 }
 
 bool PluginManager::RegisterPlugin(
@@ -674,29 +684,24 @@ PluginManager::GetObjectFileCreateCallbackAtIndex(uint32_t idx) {
 
 ObjectFileCreateMemoryInstance
 PluginManager::GetObjectFileCreateMemoryCallbackAtIndex(uint32_t idx) {
-  const auto &instances = GetObjectFileInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].create_memory_callback;
+  if (auto instance = GetObjectFileInstances().GetInstanceAtIndex(idx))
+    return instance->create_memory_callback;
   return nullptr;
 }
 
 ObjectFileGetModuleSpecifications
 PluginManager::GetObjectFileGetModuleSpecificationsCallbackAtIndex(
     uint32_t idx) {
-  const auto &instances = GetObjectFileInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].get_module_specifications;
+  if (auto instance = GetObjectFileInstances().GetInstanceAtIndex(idx))
+    return instance->get_module_specifications;
   return nullptr;
 }
 
 ObjectFileCreateMemoryInstance
 PluginManager::GetObjectFileCreateMemoryCallbackForPluginName(
     llvm::StringRef name) {
-  const auto &instances = GetObjectFileInstances().GetInstances();
-  for (auto &instance : instances) {
-    if (instance.name == name)
-      return instance.create_memory_callback;
-  }
+  if (auto instance = GetObjectFileInstances().GetInstanceForName(name))
+    return instance->create_memory_callback;
   return nullptr;
 }
 
@@ -729,7 +734,7 @@ Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp,
 
   // Fall back to object plugins.
   const auto &plugin_name = options.GetPluginName().value_or("");
-  auto &instances = GetObjectFileInstances().GetInstances();
+  auto instances = GetObjectFileInstances().GetSnapshot();
   for (auto &instance : instances) {
     if (plugin_name.empty() || instance.name == plugin_name) {
       if (instance.save_core && instance.save_core(process_sp, options, error))
@@ -791,18 +796,16 @@ PluginManager::GetObjectContainerCreateCallbackAtIndex(uint32_t idx) {
 
 ObjectContainerCreateMemoryInstance
 PluginManager::GetObjectContainerCreateMemoryCallbackAtIndex(uint32_t idx) {
-  const auto &instances = GetObjectContainerInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].create_memory_callback;
+  if (auto instance = GetObjectContainerInstances().GetInstanceAtIndex(idx))
+    return instance->create_memory_callback;
   return nullptr;
 }
 
 ObjectFileGetModuleSpecifications
 PluginManager::GetObjectContainerGetModuleSpecificationsCallbackAtIndex(
     uint32_t idx) {
-  const auto &instances = GetObjectContainerInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].get_module_specifications;
+  if (auto instance = GetObjectContainerInstances().GetInstanceAtIndex(idx))
+    return instance->get_module_specifications;
   return nullptr;
 }
 
@@ -849,7 +852,7 @@ PluginManager::GetPlatformCreateCallbackForPluginName(llvm::StringRef name) {
 
 void PluginManager::AutoCompletePlatformName(llvm::StringRef name,
                                              CompletionRequest &request) {
-  for (const auto &instance : GetPlatformInstances().GetInstances()) {
+  for (const auto &instance : GetPlatformInstances().GetSnapshot()) {
     if (instance.name.starts_with(name))
       request.AddCompletion(instance.name);
   }
@@ -897,7 +900,7 @@ PluginManager::GetProcessCreateCallbackForPluginName(llvm::StringRef name) {
 
 void PluginManager::AutoCompleteProcessName(llvm::StringRef name,
                                             CompletionRequest &request) {
-  for (const auto &instance : GetProcessInstances().GetInstances()) {
+  for (const auto &instance : GetProcessInstances().GetSnapshot()) {
     if (instance.name.starts_with(name))
       request.AddCompletion(instance.name, instance.description);
   }
@@ -935,11 +938,11 @@ bool PluginManager::UnregisterPlugin(
 
 lldb::RegisterTypeBuilderSP
 PluginManager::GetRegisterTypeBuilder(Target &target) {
-  const auto &instances = GetRegisterTypeBuilderInstances().GetInstances();
   // We assume that RegisterTypeBuilderClang is the only instance of this plugin
   // type and is always present.
-  assert(instances.size());
-  return instances[0].create_callback(target);
+  auto instance = GetRegisterTypeBuilderInstances().GetInstanceAtIndex(0);
+  assert(instance);
+  return instance->create_callback(target);
 }
 
 #pragma mark ScriptInterpreter
@@ -984,7 +987,7 @@ PluginManager::GetScriptInterpreterCreateCallbackAtIndex(uint32_t idx) {
 lldb::ScriptInterpreterSP
 PluginManager::GetScriptInterpreterForLanguage(lldb::ScriptLanguage script_lang,
                                                Debugger &debugger) {
-  const auto &instances = GetScriptInterpreterInstances().GetInstances();
+  const auto instances = GetScriptInterpreterInstances().GetSnapshot();
   ScriptInterpreterCreateInstance none_instance = nullptr;
   for (const auto &instance : instances) {
     if (instance.language == lldb::eScriptLanguageNone)
@@ -1046,13 +1049,12 @@ PluginManager::GetStructuredDataPluginCreateCallbackAtIndex(uint32_t idx) {
 StructuredDataFilterLaunchInfo
 PluginManager::GetStructuredDataFilterCallbackAtIndex(
     uint32_t idx, bool &iteration_complete) {
-  const auto &instances = GetStructuredDataPluginInstances().GetInstances();
-  if (idx < instances.size()) {
+  if (auto instance =
+          GetStructuredDataPluginInstances().GetInstanceAtIndex(idx)) {
     iteration_complete = false;
-    return instances[idx].filter_callback;
-  } else {
-    iteration_complete = true;
+    return instance->filter_callback;
   }
+  iteration_complete = true;
   return nullptr;
 }
 
@@ -1167,7 +1169,7 @@ PluginManager::GetSymbolLocatorCreateCallbackAtIndex(uint32_t idx) {
 
 ModuleSpec
 PluginManager::LocateExecutableObjectFile(const ModuleSpec &module_spec) {
-  auto &instances = GetSymbolLocatorInstances().GetInstances();
+  auto instances = GetSymbolLocatorInstances().GetSnapshot();
   for (auto &instance : instances) {
     if (instance.locate_executable_object_file) {
       std::optional<ModuleSpec> result =
@@ -1181,7 +1183,7 @@ PluginManager::LocateExecutableObjectFile(const ModuleSpec &module_spec) {
 
 FileSpec PluginManager::LocateExecutableSymbolFile(
     const ModuleSpec &module_spec, const FileSpecList &default_search_paths) {
-  auto &instances = GetSymbolLocatorInstances().GetInstances();
+  auto instances = GetSymbolLocatorInstances().GetSnapshot();
   for (auto &instance : instances) {
     if (instance.locate_executable_symbol_file) {
       std::optional<FileSpec> result = instance.locate_executable_symbol_file(
@@ -1197,7 +1199,7 @@ bool PluginManager::DownloadObjectAndSymbolFile(ModuleSpec &module_spec,
                                                 Status &error,
                                                 bool force_lookup,
                                                 bool copy_executable) {
-  auto &instances = GetSymbolLocatorInstances().GetInstances();
+  auto instances = GetSymbolLocatorInstances().GetSnapshot();
   for (auto &instance : instances) {
     if (instance.download_object_symbol_file) {
       if (instance.download_object_symbol_file(module_spec, error, force_lookup,
@@ -1211,7 +1213,7 @@ bool PluginManager::DownloadObjectAndSymbolFile(ModuleSpec &module_spec,
 FileSpec PluginManager::FindSymbolFileInBundle(const FileSpec &symfile_bundle,
                                                const UUID *uuid,
                                                const ArchSpec *arch) {
-  auto &instances = GetSymbolLocatorInstances().GetInstances();
+  auto instances = GetSymbolLocatorInstances().GetSnapshot();
   for (auto &instance : instances) {
     if (instance.find_symbol_file_in_bundle) {
       std::optional<FileSpec> result =
@@ -1272,21 +1274,20 @@ PluginManager::GetTraceCreateCallback(llvm::StringRef plugin_name) {
 
 TraceCreateInstanceForLiveProcess
 PluginManager::GetTraceCreateCallbackForLiveProcess(llvm::StringRef plugin_name) {
-  for (const TraceInstance &instance : GetTracePluginInstances().GetInstances())
-    if (instance.name == plugin_name)
-      return instance.create_callback_for_live_process;
+  if (auto instance = GetTracePluginInstances().GetInstanceForName(plugin_name))
+    return instance->create_callback_for_live_process;
+
   return nullptr;
 }
 
 llvm::StringRef PluginManager::GetTraceSchema(llvm::StringRef plugin_name) {
-  for (const TraceInstance &instance : GetTracePluginInstances().GetInstances())
-    if (instance.name == plugin_name)
-      return instance.schema;
+  if (auto instance = GetTracePluginInstances().GetInstanceForName(plugin_name))
+    return instance->schema;
   return llvm::StringRef();
 }
 
 llvm::StringRef PluginManager::GetTraceSchema(size_t index) {
-  if (TraceInstance *instance =
+  if (const TraceInstance *instance =
           GetTracePluginInstances().GetInstanceAtIndex(index))
     return instance->schema;
   return llvm::StringRef();
@@ -1335,7 +1336,7 @@ bool PluginManager::UnregisterPlugin(
 
 ThreadTraceExportCommandCreator
 PluginManager::GetThreadTraceExportCommandCreatorAtIndex(uint32_t index) {
-  if (TraceExporterInstance *instance =
+  if (const TraceExporterInstance *instance =
           GetTraceExporterInstances().GetInstanceAtIndex(index))
     return instance->create_thread_trace_export_command;
   return nullptr;
@@ -1438,9 +1439,9 @@ bool PluginManager::UnregisterPlugin(
 
 InstrumentationRuntimeGetType
 PluginManager::GetInstrumentationRuntimeGetTypeCallbackAtIndex(uint32_t idx) {
-  const auto &instances = GetInstrumentationRuntimeInstances().GetInstances();
-  if (idx < instances.size())
-    return instances[idx].get_type_callback;
+  if (auto instance =
+          GetInstrumentationRuntimeInstances().GetInstanceAtIndex(idx))
+    return instance->get_type_callback;
   return nullptr;
 }
 
@@ -1493,7 +1494,7 @@ PluginManager::GetTypeSystemCreateCallbackAtIndex(uint32_t idx) {
 }
 
 LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForTypes() {
-  const auto &instances = GetTypeSystemInstances().GetInstances();
+  const auto instances = GetTypeSystemInstances().GetSnapshot();
   LanguageSet all;
   for (unsigned i = 0; i < instances.size(); ++i)
     all.bitvector |= instances[i].supported_languages_for_types.bitvector;
@@ -1501,7 +1502,7 @@ LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForTypes() {
 }
 
 LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForExpressions() {
-  const auto &instances = GetTypeSystemInstances().GetInstances();
+  const auto instances = GetTypeSystemInstances().GetSnapshot();
   LanguageSet all;
   for (unsigned i = 0; i < instances.size(); ++i)
     all.bitvector |= instances[i].supported_languages_for_expressions.bitvector;
@@ -1545,7 +1546,7 @@ bool PluginManager::UnregisterPlugin(
 }
 
 uint32_t PluginManager::GetNumScriptedInterfaces() {
-  return GetScriptedInterfaceInstances().GetInstances().size();
+  return GetScriptedInterfaceInstances().GetSnapshot().size();
 }
 
 llvm::StringRef PluginManager::GetScriptedInterfaceNameAtIndex(uint32_t index) {
@@ -1559,17 +1560,16 @@ PluginManager::GetScriptedInterfaceDescriptionAtIndex(uint32_t index) {
 
 lldb::ScriptLanguage
 PluginManager::GetScriptedInterfaceLanguageAtIndex(uint32_t idx) {
-  const auto &instances = GetScriptedInterfaceInstances().GetInstances();
-  return idx < instances.size() ? instances[idx].language
-                                : ScriptLanguage::eScriptLanguageNone;
+  if (auto instance = GetScriptedInterfaceInstances().GetInstanceAtIndex(idx))
+    return instance->language;
+  return ScriptLanguage::eScriptLanguageNone;
 }
 
 ScriptedInterfaceUsages
 PluginManager::GetScriptedInterfaceUsagesAtIndex(uint32_t idx) {
-  const auto &instances = GetScriptedInterfaceInstances().GetInstances();
-  if (idx >= instances.size())
-    return {};
-  return instances[idx].usages;
+  if (auto instance = GetScriptedInterfaceInstances().GetInstanceAtIndex(idx))
+    return instance->usages;
+  return {};
 }
 
 #pragma mark REPL
@@ -1606,13 +1606,13 @@ REPLCreateInstance PluginManager::GetREPLCreateCallbackAtIndex(uint32_t idx) {
 }
 
 LanguageSet PluginManager::GetREPLSupportedLanguagesAtIndex(uint32_t idx) {
-  const auto &instances = GetREPLInstances().GetInstances();
-  return idx < instances.size() ? instances[idx].supported_languages
-                                : LanguageSet();
+  if (auto instance = GetREPLInstances().GetInstanceAtIndex(idx))
+    return instance->supported_languages;
+  return LanguageSet();
 }
 
 LanguageSet PluginManager::GetREPLAllTypeSystemSupportedLanguages() {
-  const auto &instances = GetREPLInstances().GetInstances();
+  const auto instances = GetREPLInstances().GetSnapshot();
   LanguageSet all;
   for (unsigned i = 0; i < instances.size(); ++i)
     all.bitvector |= instances[i].supported_languages.bitvector;

>From 20e934a1ab929dfaaba1cea15681368c3a5409c9 Mon Sep 17 00:00:00 2001
From: David Peixotto <peix at meta.com>
Date: Fri, 7 Mar 2025 17:27:11 -0800
Subject: [PATCH 2/3] Add enable/disable api for SystemRuntime plugins

This commit adds support for enabling and disabling plugins by name.
The changes are made generically in the `PluginInstances` class, but
currently we only expose the ability to SystemRuntime plugins. Other
plugins types can be added easily.

We had a few design goals for how disabled plugins should work

  1. Plugins that are disabled should still be visible to the system.
     This allows us to dynamically enable and disable plugins
     and report their state to the user.
  2. Plugin order should be stable across disable and enable changes.
     We want avoid changing the order of plugin lookup. When a plugin
     is re-enabled it should return to its original slot in the
     creation order.
  3. Disabled plugins should not appear in PluginManager operations.
     Clients should be able to assume that only enabled plugins will
     be returned from the PluginManager.

We explored two other ways to implement this change:

  1. Keep two separate lists and move the instances between them when
     enabling and disabling plugins. This requires maintaining the
     original insert order to satisfy the goals above and thus adds
     some complexity to the code.
  2. Return a copy of the instances list with disabled plugins removed
     when clients iterate over them. This requires extra copies and
     would potentially lead to the copies getting out of sync with
     the actual stored instances because any modifications to the
     instances would be lost.

Instead, we modify the plugin instance to maintain a bool of its enabled
state.  Clients external to Instances class expect to iterate over only
enabled instance so we skip over disabed instances in the query and
snapshot apis. This way the client does not have to manually check which
instances are enabled.
---
 lldb/include/lldb/Core/PluginManager.h    |  13 +
 lldb/source/Core/PluginManager.cpp        |  55 +++-
 lldb/unittests/Core/CMakeLists.txt        |   1 +
 lldb/unittests/Core/PluginManagerTest.cpp | 367 ++++++++++++++++++++++
 4 files changed, 433 insertions(+), 3 deletions(-)
 create mode 100644 lldb/unittests/Core/PluginManagerTest.cpp

diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index e4e0c3eea67f8..a6dab045adf27 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -22,6 +22,7 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <vector>
 
 #define LLDB_PLUGIN_DEFINE_ADV(ClassName, PluginName)                          \
   namespace lldb_private {                                                     \
@@ -47,6 +48,12 @@ class CommandInterpreter;
 class Debugger;
 class StringList;
 
+struct RegisteredPluginInfo {
+  llvm::StringRef name = "";
+  llvm::StringRef description = "";
+  bool enabled = false;
+};
+
 class PluginManager {
 public:
   static void Initialize();
@@ -168,6 +175,12 @@ class PluginManager {
   static SystemRuntimeCreateInstance
   GetSystemRuntimeCreateCallbackAtIndex(uint32_t idx);
 
+  static std::vector<RegisteredPluginInfo> GetSystemRuntimePluginInfo();
+
+  // Modify the enabled state of a SystemRuntime plugin.
+  // Returns false if the plugin name is not found.
+  static bool SetSystemRuntimePluginEnabled(llvm::StringRef name, bool enabled);
+
   // ObjectFile
   static bool
   RegisterPlugin(llvm::StringRef name, llvm::StringRef description,
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 95eb940efcef2..e6cb248ef31ce 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -188,11 +188,13 @@ template <typename Callback> struct PluginInstance {
   PluginInstance(llvm::StringRef name, llvm::StringRef description,
                  Callback create_callback,
                  DebuggerInitializeCallback debugger_init_callback = nullptr)
-      : name(name), description(description), create_callback(create_callback),
+      : name(name), description(description), enabled(true),
+        create_callback(create_callback),
         debugger_init_callback(debugger_init_callback) {}
 
   llvm::StringRef name;
   llvm::StringRef description;
+  bool enabled;
   Callback create_callback;
   DebuggerInitializeCallback debugger_init_callback;
 };
@@ -250,7 +252,9 @@ template <typename Instance> class PluginInstances {
   }
 
   void PerformDebuggerCallback(Debugger &debugger) {
-    for (auto &instance : m_instances) {
+    for (const auto &instance : m_instances) {
+      if (!instance.enabled)
+        continue;
       if (instance.debugger_init_callback)
         instance.debugger_init_callback(debugger);
     }
@@ -260,7 +264,14 @@ template <typename Instance> class PluginInstances {
   // Note that this is a copy of the internal state so modifications
   // to the returned instances will not be reflected back to instances
   // stored by the PluginInstances object.
-  std::vector<Instance> GetSnapshot() { return m_instances; }
+  std::vector<Instance> GetSnapshot() {
+    std::vector<Instance> enabled_instances;
+    for (const auto &instance : m_instances) {
+      if (instance.enabled)
+        enabled_instances.push_back(instance);
+    }
+    return enabled_instances;
+  }
 
   const Instance *GetInstanceAtIndex(uint32_t idx) {
     uint32_t count = 0;
@@ -280,12 +291,41 @@ template <typename Instance> class PluginInstances {
   const Instance *
   FindEnabledInstance(std::function<bool(const Instance &)> predicate) const {
     for (const auto &instance : m_instances) {
+      if (!instance.enabled)
+        continue;
       if (predicate(instance))
         return &instance;
     }
     return nullptr;
   }
 
+  // Return a list of all the registered plugin instances. This includes both
+  // enabled and disabled instances. The instances are listed in the order they
+  // were registered which is the order they would be queried if they were all
+  // enabled.
+  std::vector<RegisteredPluginInfo> GetPluginInfoForAllInstances() {
+    // Lookup the plugin info for each instance in the sorted order.
+    std::vector<RegisteredPluginInfo> plugin_infos;
+    plugin_infos.reserve(m_instances.size());
+    for (const Instance &instance : m_instances)
+      plugin_infos.push_back(
+          {instance.name, instance.description, instance.enabled});
+
+    return plugin_infos;
+  }
+
+  bool SetInstanceEnabled(llvm::StringRef name, bool enable) {
+    auto it = std::find_if(
+        m_instances.begin(), m_instances.end(),
+        [&](const Instance &instance) { return instance.name == name; });
+
+    if (it == m_instances.end())
+      return false;
+
+    it->enabled = enable;
+    return true;
+  }
+
 private:
   std::vector<Instance> m_instances;
 };
@@ -627,6 +667,15 @@ PluginManager::GetSystemRuntimeCreateCallbackAtIndex(uint32_t idx) {
   return GetSystemRuntimeInstances().GetCallbackAtIndex(idx);
 }
 
+std::vector<RegisteredPluginInfo> PluginManager::GetSystemRuntimePluginInfo() {
+  return GetSystemRuntimeInstances().GetPluginInfoForAllInstances();
+}
+
+bool PluginManager::SetSystemRuntimePluginEnabled(llvm::StringRef name,
+                                                  bool enable) {
+  return GetSystemRuntimeInstances().SetInstanceEnabled(name, enable);
+}
+
 #pragma mark ObjectFile
 
 struct ObjectFileInstance : public PluginInstance<ObjectFileCreateInstance> {
diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt
index 60265f794b5e8..8580f5887ea2b 100644
--- a/lldb/unittests/Core/CMakeLists.txt
+++ b/lldb/unittests/Core/CMakeLists.txt
@@ -7,6 +7,7 @@ add_lldb_unittest(LLDBCoreTests
   FormatEntityTest.cpp
   MangledTest.cpp
   ModuleSpecTest.cpp
+  PluginManagerTest.cpp
   ProgressReportTest.cpp
   RichManglingContextTest.cpp
   SourceLocationSpecTest.cpp
diff --git a/lldb/unittests/Core/PluginManagerTest.cpp b/lldb/unittests/Core/PluginManagerTest.cpp
new file mode 100644
index 0000000000000..ca1003ca9a85a
--- /dev/null
+++ b/lldb/unittests/Core/PluginManagerTest.cpp
@@ -0,0 +1,367 @@
+
+#include "lldb/Core/PluginManager.h"
+
+#include "gtest/gtest.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+// Mock system runtime plugin create functions.
+SystemRuntime *CreateSystemRuntimePluginA(Process *process) { return nullptr; }
+
+SystemRuntime *CreateSystemRuntimePluginB(Process *process) { return nullptr; }
+
+SystemRuntime *CreateSystemRuntimePluginC(Process *process) { return nullptr; }
+
+// Test class for testing the PluginManager.
+// The PluginManager modifies global state when registering new plugins. This
+// class is intended to undo those modifications in the destructor to give each
+// test a clean slate with no registered plugins at the start of a test.
+class PluginManagerTest : public testing::Test {
+public:
+  // Remove any pre-registered plugins so we have a known starting point.
+  static void SetUpTestSuite() { RemoveAllRegisteredSystemRuntimePlugins(); }
+
+  // Add mock system runtime plugins for testing.
+  void RegisterMockSystemRuntimePlugins() {
+    ASSERT_TRUE(PluginManager::RegisterPlugin("a", "test instance A",
+                                              CreateSystemRuntimePluginA));
+    ASSERT_TRUE(PluginManager::RegisterPlugin("b", "test instance B",
+                                              CreateSystemRuntimePluginB));
+    ASSERT_TRUE(PluginManager::RegisterPlugin("c", "test instance C",
+                                              CreateSystemRuntimePluginC));
+  }
+
+  // Remove any plugins added during the tests.
+  virtual ~PluginManagerTest() override {
+    RemoveAllRegisteredSystemRuntimePlugins();
+  }
+
+protected:
+  std::vector<SystemRuntimeCreateInstance> m_system_runtime_plugins;
+
+  static void RemoveAllRegisteredSystemRuntimePlugins() {
+    // Enable all currently registered plugins so we can get a handle to
+    // their create callbacks in the loop below. Only enabled plugins
+    // are returned from the PluginManager Get*CreateCallbackAtIndex apis.
+    for (const RegisteredPluginInfo &PluginInfo :
+         PluginManager::GetSystemRuntimePluginInfo()) {
+      PluginManager::SetSystemRuntimePluginEnabled(PluginInfo.name, true);
+    }
+
+    // Get a handle to the create call backs for all the registered plugins.
+    std::vector<SystemRuntimeCreateInstance> registered_plugin_callbacks;
+    SystemRuntimeCreateInstance create_callback = nullptr;
+    for (uint32_t idx = 0;
+         (create_callback =
+              PluginManager::GetSystemRuntimeCreateCallbackAtIndex(idx)) !=
+         nullptr;
+         ++idx) {
+      registered_plugin_callbacks.push_back((create_callback));
+    }
+
+    // Remove all currently registered plugins.
+    for (SystemRuntimeCreateInstance create_callback :
+         registered_plugin_callbacks) {
+      PluginManager::UnregisterPlugin(create_callback);
+    }
+  }
+};
+
+// Test basic register functionality.
+TEST_F(PluginManagerTest, RegisterSystemRuntimePlugin) {
+  RegisterMockSystemRuntimePlugins();
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginC);
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(3), nullptr);
+}
+
+// Test basic un-register functionality.
+TEST_F(PluginManagerTest, UnRegisterSystemRuntimePlugin) {
+  RegisterMockSystemRuntimePlugins();
+
+  ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB));
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginC);
+
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), nullptr);
+}
+
+// Test registered plugin info functionality.
+TEST_F(PluginManagerTest, SystemRuntimePluginInfo) {
+  RegisterMockSystemRuntimePlugins();
+
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 3u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[0].description, "test instance A");
+  ASSERT_EQ(plugin_info[0].enabled, true);
+  ASSERT_EQ(plugin_info[1].name, "b");
+  ASSERT_EQ(plugin_info[1].description, "test instance B");
+  ASSERT_EQ(plugin_info[1].enabled, true);
+  ASSERT_EQ(plugin_info[2].name, "c");
+  ASSERT_EQ(plugin_info[2].description, "test instance C");
+  ASSERT_EQ(plugin_info[2].enabled, true);
+}
+
+// Test basic un-register functionality.
+TEST_F(PluginManagerTest, UnRegisterSystemRuntimePluginInfo) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Initial plugin info has all three registered plugins.
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 3u);
+
+  ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB));
+
+  // After un-registering a plugin it should be removed from plugin info.
+  plugin_info = PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 2u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[0].enabled, true);
+  ASSERT_EQ(plugin_info[1].name, "c");
+  ASSERT_EQ(plugin_info[1].enabled, true);
+}
+
+// Test plugin disable functionality.
+TEST_F(PluginManagerTest, SystemRuntimePluginDisable) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Disable plugin should succeed.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
+
+  // Disabling a plugin does not remove it from plugin info.
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 3u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[0].enabled, true);
+  ASSERT_EQ(plugin_info[1].name, "b");
+  ASSERT_EQ(plugin_info[1].enabled, false);
+  ASSERT_EQ(plugin_info[2].name, "c");
+  ASSERT_EQ(plugin_info[2].enabled, true);
+
+  // Disabling a plugin does remove it from available plugins.
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginC);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), nullptr);
+}
+
+// Test plugin disable and enable functionality.
+TEST_F(PluginManagerTest, SystemRuntimePluginDisableThenEnable) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Initially plugin b is available in slot 1.
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+
+  // Disabling it will remove it from available plugins.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginC);
+
+  // We can re-enable the plugin later and it should go back to the original
+  // slot.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginC);
+
+  // And show up in the plugin info correctly.
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 3u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[0].enabled, true);
+  ASSERT_EQ(plugin_info[1].name, "b");
+  ASSERT_EQ(plugin_info[1].enabled, true);
+  ASSERT_EQ(plugin_info[2].name, "c");
+  ASSERT_EQ(plugin_info[2].enabled, true);
+}
+
+// Test calling disable on an already disabled plugin is ok.
+TEST_F(PluginManagerTest, SystemRuntimePluginDisableDisabled) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Initial call to disable the plugin should succeed.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
+
+  // The second call should also succeed because the plugin is already disabled.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
+
+  // The call to re-enable the plugin should succeed.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true));
+
+  // The second call should also succeed since the plugin is already enabled.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true));
+}
+
+// Test calling disable on an already disabled plugin is ok.
+TEST_F(PluginManagerTest, SystemRuntimePluginDisableNonExistent) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Both enable and disable should return false for a non-existent plugin.
+  ASSERT_FALSE(
+      PluginManager::SetSystemRuntimePluginEnabled("does_not_exist", true));
+  ASSERT_FALSE(
+      PluginManager::SetSystemRuntimePluginEnabled("does_not_exist", false));
+}
+
+// Test disabling all plugins and then re-enabling them in a different
+// order will restore the original plugin order.
+TEST_F(PluginManagerTest, SystemRuntimePluginDisableAll) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Validate initial state of registered plugins.
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginC);
+
+  // Disable all the active plugins.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("a", false));
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", false));
+
+  // Should have no active plugins.
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0), nullptr);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1), nullptr);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), nullptr);
+
+  // And show up in the plugin info correctly.
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 3u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[0].enabled, false);
+  ASSERT_EQ(plugin_info[1].name, "b");
+  ASSERT_EQ(plugin_info[1].enabled, false);
+  ASSERT_EQ(plugin_info[2].name, "c");
+  ASSERT_EQ(plugin_info[2].enabled, false);
+
+  // Enable plugins in reverse order and validate expected indicies.
+  // They should show up in the original plugin order.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", true));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginC);
+
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("a", true));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginC);
+
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", true));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginC);
+}
+
+// Test un-registering a disabled plugin works.
+TEST_F(PluginManagerTest, UnRegisterDisabledSystemRuntimePlugin) {
+  RegisterMockSystemRuntimePlugins();
+
+  // Initial plugin info has all three registered plugins.
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 3u);
+
+  // First disable a plugin, then unregister it. Both should succeed.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false));
+  ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB));
+
+  // After un-registering a plugin it should be removed from plugin info.
+  plugin_info = PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(plugin_info.size(), 2u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[0].enabled, true);
+  ASSERT_EQ(plugin_info[1].name, "c");
+  ASSERT_EQ(plugin_info[1].enabled, true);
+}
+
+// Test un-registering and then re-registering a plugin will change the order of
+// loaded plugins.
+TEST_F(PluginManagerTest, UnRegisterSystemRuntimePluginChangesOrder) {
+  RegisterMockSystemRuntimePlugins();
+
+  std::vector<RegisteredPluginInfo> plugin_info =
+      PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginC);
+
+  ASSERT_EQ(plugin_info.size(), 3u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[1].name, "b");
+  ASSERT_EQ(plugin_info[2].name, "c");
+
+  // Unregister and then registering a plugin puts it at the end of the order
+  // list.
+  ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB));
+  ASSERT_TRUE(PluginManager::RegisterPlugin("b", "New test instance B",
+                                            CreateSystemRuntimePluginB));
+
+  // Check the callback indices match as expected.
+  plugin_info = PluginManager::GetSystemRuntimePluginInfo();
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginC);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginB);
+
+  // And plugin info should match as well.
+  ASSERT_EQ(plugin_info.size(), 3u);
+  ASSERT_EQ(plugin_info[0].name, "a");
+  ASSERT_EQ(plugin_info[1].name, "c");
+  ASSERT_EQ(plugin_info[2].name, "b");
+  ASSERT_EQ(plugin_info[2].description, "New test instance B");
+
+  // Disabling and re-enabling the "c" plugin should slot it back
+  // into the middle of the order. Originally it was last, but after
+  // un-registering and re-registering "b" it should now stay in
+  // the middle of the order.
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", false));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginB);
+
+  // And re-enabling
+  ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("c", true));
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(0),
+            CreateSystemRuntimePluginA);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(1),
+            CreateSystemRuntimePluginC);
+  ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2),
+            CreateSystemRuntimePluginB);
+}

>From aaa3ab3d063580747bb106f39e6792232cc0af00 Mon Sep 17 00:00:00 2001
From: David Peixotto <peix at meta.com>
Date: Tue, 11 Mar 2025 13:02:14 -0700
Subject: [PATCH 3/3] Add commands to list/enable/disable plugins

This commit adds three new commands for managing plugins. The `list`
command will show which plugins are currently registered and their
enabled state. The `enable` and `disable` commands can be used to
enable or disable plugins.

A disabled plugin will not show up to the PluginManager when it iterates
over available plugins of a particular type.

The purpose of these commands is to provide more visibility into registered
plugins and allow users to disable plugins for experimental perf reasons.

There are a few limitations to the current implementation

  1. Only SystemRuntime plugins are currently supported. We can easily
     extend the existing implementation to support more types.
  2. Only "statically" know plugin types are supported (i.e. those
     managed by the PluginManager and not from `plugin load`). It is
     possibly we could support dynamic plugins as well, but I have
     not looked into it yet.
---
 lldb/source/Commands/CommandObjectPlugin.cpp  | 335 ++++++++++++++++++
 .../source/Commands/CommandObjectSettings.cpp |   1 +
 lldb/source/Commands/Options.td               |   5 +
 .../command-plugin-enable+disable.test        |  53 +++
 .../Shell/Commands/command-plugin-list.test   |  51 +++
 5 files changed, 445 insertions(+)
 create mode 100644 lldb/test/Shell/Commands/command-plugin-enable+disable.test
 create mode 100644 lldb/test/Shell/Commands/command-plugin-list.test

diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp
index f3108b8a768d2..68261d24ffe1f 100644
--- a/lldb/source/Commands/CommandObjectPlugin.cpp
+++ b/lldb/source/Commands/CommandObjectPlugin.cpp
@@ -7,8 +7,11 @@
 //===----------------------------------------------------------------------===//
 
 #include "CommandObjectPlugin.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Host/OptionParser.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
+#include "llvm/Support/GlobPattern.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -46,12 +49,344 @@ class CommandObjectPluginLoad : public CommandObjectParsed {
   }
 };
 
+namespace {
+#define LLDB_OPTIONS_plugin_list
+#include "CommandOptions.inc"
+
+// These option definitions are shared by the plugin list/enable/disable
+// commands.
+class PluginListCommandOptions : public Options {
+public:
+  PluginListCommandOptions() = default;
+
+  ~PluginListCommandOptions() override = default;
+
+  Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                        ExecutionContext *execution_context) override {
+    Status error;
+    const int short_option = m_getopt_table[option_idx].val;
+
+    switch (short_option) {
+    case 'x':
+      m_exact_name_match = true;
+      break;
+    default:
+      llvm_unreachable("Unimplemented option");
+    }
+
+    return error;
+  }
+
+  void OptionParsingStarting(ExecutionContext *execution_context) override {
+    m_exact_name_match = false;
+  }
+
+  llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
+    return llvm::ArrayRef(g_plugin_list_options);
+  }
+
+  // Instance variables to hold the values for command options.
+  bool m_exact_name_match = false;
+};
+
+// Define some data structures to describe known plugin "namespaces".
+// The PluginManager is organized into a series of static functions
+// that operate on different types of plugin. For example SystemRuntime
+// and ObjectFile plugins.
+//
+// The namespace name is used a prefix when matching plugin names. For example,
+// if we have an "elf" plugin in the "object-file" namespace then we will
+// match a plugin name pattern against the "object-file.elf" name.
+//
+// The plugin namespace here is used so we can operate on all the plugins
+// of a given type so it is easy to enable or disable them as a group.
+using GetPluginInfo = std::function<std::vector<RegisteredPluginInfo>()>;
+using SetPluginEnabled = std::function<bool(llvm::StringRef, bool)>;
+struct PluginNamespace {
+  llvm::StringRef name;
+  GetPluginInfo get_info;
+  SetPluginEnabled set_enabled;
+};
+
+// Currently supported set of plugin namespaces. This will be expanded
+// over time.
+PluginNamespace PluginNamespaces[] = {
+    {"system-runtime", PluginManager::GetSystemRuntimePluginInfo,
+     PluginManager::SetSystemRuntimePluginEnabled}};
+
+// Helper function to perform an action on each matching plugin.
+// The action callback is given the containing namespace along with plugin info
+// for each matching plugin.
+static int ActOnMatchingPlugins(
+    llvm::GlobPattern pattern,
+    std::function<void(const PluginNamespace &plugin_namespace,
+                       const std::vector<RegisteredPluginInfo> &plugin_info)>
+        action) {
+  int num_matching = 0;
+
+  for (const PluginNamespace &plugin_namespace : PluginNamespaces) {
+    std::vector<RegisteredPluginInfo> all_plugins = plugin_namespace.get_info();
+    std::vector<RegisteredPluginInfo> matching_plugins;
+    for (const RegisteredPluginInfo &plugin_info : all_plugins) {
+      std::string qualified_name =
+          (plugin_namespace.name + "." + plugin_info.name).str();
+      if (pattern.match(qualified_name)) {
+        matching_plugins.push_back(plugin_info);
+      }
+    }
+
+    if (!matching_plugins.empty()) {
+      num_matching += matching_plugins.size();
+      action(plugin_namespace, matching_plugins);
+    }
+  }
+
+  return num_matching;
+}
+
+// Return a string in glob syntax for matching plugins.
+static std::string GetPluginNamePatternString(llvm::StringRef user_input,
+                                              bool add_default_glob) {
+  std::string pattern_str;
+  if (user_input.empty())
+    pattern_str = "*";
+  else
+    pattern_str = user_input;
+
+  if (add_default_glob && pattern_str != "*") {
+    pattern_str = "*" + pattern_str + "*";
+  }
+
+  return pattern_str;
+}
+
+// Attempts to create a glob pattern for a plugin name based on plugin command
+// input. Writes an error message to the `result` object if the glob cannot be
+// created successfully.
+//
+// The `glob_storage` is used to hold the string data for the glob pattern. The
+// llvm::GlobPattern only contains pointers into the string data so we need a
+// stable location that can outlive the glob pattern itself.
+std::optional<llvm::GlobPattern>
+TryCreatePluginPattern(const char *plugin_command_name, const Args &command,
+                       const PluginListCommandOptions &options,
+                       CommandReturnObject &result, std::string &glob_storage) {
+  size_t argc = command.GetArgumentCount();
+  if (argc > 1) {
+    result.AppendErrorWithFormat("'%s' requires one argument",
+                                 plugin_command_name);
+    return {};
+  }
+
+  llvm::StringRef user_pattern;
+  if (argc == 1) {
+    user_pattern = command[0].ref();
+  }
+
+  glob_storage =
+      GetPluginNamePatternString(user_pattern, !options.m_exact_name_match);
+
+  auto glob_pattern = llvm::GlobPattern::create(glob_storage);
+
+  if (auto error = glob_pattern.takeError()) {
+    std::string error_message =
+        (llvm::Twine("Invalid plugin glob pattern: '") + glob_storage +
+         "': " + llvm::toString(std::move(error)))
+            .str();
+    result.AppendError(error_message);
+    return {};
+  }
+
+  return *glob_pattern;
+}
+
+// Call the "SetEnable" function for each matching plugins.
+// Used to share the majority of the code between the enable
+// and disable commands.
+int SetEnableOnMatchingPlugins(const llvm::GlobPattern &pattern,
+                               CommandReturnObject &result, bool enabled) {
+  return ActOnMatchingPlugins(
+      pattern, [&](const PluginNamespace &plugin_namespace,
+                   const std::vector<RegisteredPluginInfo> &plugins) {
+        result.AppendMessage(plugin_namespace.name);
+        for (const auto &plugin : plugins) {
+          if (!plugin_namespace.set_enabled(plugin.name, enabled)) {
+            result.AppendErrorWithFormat("failed to enable plugin %s.%s",
+                                         plugin_namespace.name.data(),
+                                         plugin.name.data());
+            continue;
+          }
+
+          result.AppendMessageWithFormat(
+              "  %s %-30s %s\n", enabled ? "[+]" : "[-]", plugin.name.data(),
+              plugin.description.data());
+        }
+      });
+}
+} // namespace
+
+class CommandObjectPluginList : public CommandObjectParsed {
+public:
+  CommandObjectPluginList(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "plugin list",
+                            "Report info about registered LLDB plugins.",
+                            nullptr) {
+    AddSimpleArgumentList(eArgTypePlugin);
+    SetHelpLong(R"(
+Display information about registered plugins.
+The plugin information is formatted as shown below
+
+    <plugin-namespace>
+      [+] <plugin-name>                  Plugin #1 description
+      [-] <plugin-name>                  Plugin #2 description
+
+An enabled plugin is marked with [+] and a disabled plugin is marked with [-].
+
+Selecting plugins
+------------------
+plugin list [<plugin-namespace>.][<plugin-name>]
+
+Plugin names are specified using glob patterns. The pattern will be matched
+against the plugins fully qualified name, which is composed of the namespace,
+followed by a '.', followed by the plugin name.
+
+When no arguments are given the plugin selection string is the wildcard '*'.
+By default wildcards are added around the input to enable searching by
+substring. You can prevent these implicit wild cards by using the
+-x flag.
+
+Examples
+-----------------
+List all plugins in the system-runtime namespace
+
+  (lldb) plugin list system-runtime.*
+
+List all plugins containing the string foo
+
+  (lldb) plugin list foo
+
+This is equivalent to
+
+  (lldb) plugin list *foo*
+
+List only a plugin matching a fully qualified name exactly
+
+  (lldb) plugin list -x system-runtime.foo
+)");
+  }
+
+  ~CommandObjectPluginList() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  void DoExecute(Args &command, CommandReturnObject &result) override {
+    std::string glob_storage;
+    std::optional<llvm::GlobPattern> plugin_glob = TryCreatePluginPattern(
+        "plugin list", command, m_options, result, glob_storage);
+
+    if (!plugin_glob) {
+      assert(!result.Succeeded());
+      return;
+    }
+
+    int num_matching = ActOnMatchingPlugins(
+        *plugin_glob, [&](const PluginNamespace &plugin_namespace,
+                          const std::vector<RegisteredPluginInfo> &plugins) {
+          result.AppendMessage(plugin_namespace.name);
+          for (auto &plugin : plugins) {
+            result.AppendMessageWithFormat(
+                "  %s %-30s %s\n", plugin.enabled ? "[+]" : "[-]",
+                plugin.name.data(), plugin.description.data());
+          }
+        });
+
+    if (num_matching == 0) {
+      result.AppendErrorWithFormat("Found no matching plugins");
+    }
+  }
+
+  PluginListCommandOptions m_options;
+};
+
+class CommandObjectPluginEnable : public CommandObjectParsed {
+public:
+  CommandObjectPluginEnable(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "plugin enable",
+                            "Enable registered LLDB plugins.", nullptr) {
+    AddSimpleArgumentList(eArgTypePlugin);
+  }
+
+  ~CommandObjectPluginEnable() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  void DoExecute(Args &command, CommandReturnObject &result) override {
+    std::string glob_storage;
+    std::optional<llvm::GlobPattern> plugin_glob = TryCreatePluginPattern(
+        "plugin enable", command, m_options, result, glob_storage);
+
+    if (!plugin_glob) {
+      assert(!result.Succeeded());
+      return;
+    }
+
+    int num_matching = SetEnableOnMatchingPlugins(*plugin_glob, result, true);
+
+    if (num_matching == 0) {
+      result.AppendErrorWithFormat("Found no matching plugins to enable");
+    }
+  }
+
+  PluginListCommandOptions m_options;
+};
+
+class CommandObjectPluginDisable : public CommandObjectParsed {
+public:
+  CommandObjectPluginDisable(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "plugin disable",
+                            "Disable registered LLDB plugins.", nullptr) {
+    AddSimpleArgumentList(eArgTypePlugin);
+  }
+
+  ~CommandObjectPluginDisable() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  void DoExecute(Args &command, CommandReturnObject &result) override {
+    std::string glob_storage;
+    std::optional<llvm::GlobPattern> plugin_glob = TryCreatePluginPattern(
+        "plugin disable", command, m_options, result, glob_storage);
+
+    if (!plugin_glob) {
+      assert(!result.Succeeded());
+      return;
+    }
+
+    int num_matching = SetEnableOnMatchingPlugins(*plugin_glob, result, false);
+
+    if (num_matching == 0) {
+      result.AppendErrorWithFormat("Found no matching plugins to disable");
+    }
+  }
+
+  PluginListCommandOptions m_options;
+};
+
 CommandObjectPlugin::CommandObjectPlugin(CommandInterpreter &interpreter)
     : CommandObjectMultiword(interpreter, "plugin",
                              "Commands for managing LLDB plugins.",
                              "plugin <subcommand> [<subcommand-options>]") {
   LoadSubCommand("load",
                  CommandObjectSP(new CommandObjectPluginLoad(interpreter)));
+  LoadSubCommand("list",
+                 CommandObjectSP(new CommandObjectPluginList(interpreter)));
+  LoadSubCommand("enable",
+                 CommandObjectSP(new CommandObjectPluginEnable(interpreter)));
+  LoadSubCommand("disable",
+                 CommandObjectSP(new CommandObjectPluginDisable(interpreter)));
 }
 
 CommandObjectPlugin::~CommandObjectPlugin() = default;
diff --git a/lldb/source/Commands/CommandObjectSettings.cpp b/lldb/source/Commands/CommandObjectSettings.cpp
index 7bbb0dd567ab1..1b60d441264d6 100644
--- a/lldb/source/Commands/CommandObjectSettings.cpp
+++ b/lldb/source/Commands/CommandObjectSettings.cpp
@@ -15,6 +15,7 @@
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Interpreter/OptionValue.h"
 #include "lldb/Interpreter/OptionValueProperties.h"
 
 using namespace lldb;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index cc579d767eb06..ac8fd78f4a894 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -683,6 +683,11 @@ let Command = "platform shell" in {
     Desc<"Shell interpreter path. This is the binary used to run the command.">;
 }
 
+let Command = "plugin list" in {
+  def plugin_info_exact : Option<"exact", "x">,
+                          Desc<"Do not add implicit * glob around plugin name">;
+}
+
 let Command = "process launch" in {
   def process_launch_stop_at_entry : Option<"stop-at-entry", "s">,
     Desc<"Stop at the entry point of the program when launching a process.">;
diff --git a/lldb/test/Shell/Commands/command-plugin-enable+disable.test b/lldb/test/Shell/Commands/command-plugin-enable+disable.test
new file mode 100644
index 0000000000000..fcc1994b7697c
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-plugin-enable+disable.test
@@ -0,0 +1,53 @@
+# This test validates the plugin enable and disable commands.
+# Currently it works only for system-runtime plugins and we only have one
+# system runtime plugin so testing is a bit limited.
+#
+# Note that commands that return errors will stop running a script, so we
+# have new RUN lines for any command that is expected to return an error.
+
+# RUN: %lldb -s %s -o exit 2>&1 | FileCheck %s
+
+# Test plugin list shows the default state which is expected to be enabled.
+plugin list
+# CHECK-LABEL: plugin list
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin disable disables a plugin.
+plugin disable systemruntime-macosx
+# CHECK-LABEL: plugin disable systemruntime-macosx
+# CHECK: system-runtime
+# CHECK:  [-] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Make sure plugin list shows it disabled as well.
+plugin list
+# CHECK: system-runtime
+# CHECK:  [-] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin enable re-enables a plugin.
+plugin enable systemruntime-macosx
+# CHECK-LABEL: plugin enable systemruntime-macosx
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Make sure plugin list shows it enabled as well.
+plugin list
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin disable with wildcard works.
+plugin disable system*
+# CHECK-LABEL: plugin disable system*
+# CHECK: system-runtime
+# CHECK:  [-] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin enable with wildcard works.
+plugin enable system*
+# CHECK-LABEL: plugin enable system*
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin enable/disable for unknown plugin returns an error.
+# RUN: %lldb -o "plugin enable some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND
+# RUN: %lldb -o "plugin disable some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND
+# ERROR_PLUGIN_NOT_FOUND: error: Found no matching plugins
diff --git a/lldb/test/Shell/Commands/command-plugin-list.test b/lldb/test/Shell/Commands/command-plugin-list.test
new file mode 100644
index 0000000000000..7e5c9c671a783
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-plugin-list.test
@@ -0,0 +1,51 @@
+# This test validates the plugin list command.
+# Currently it works only for system-runtime plugins and we only have one
+# system runtime plugin so testing is a bit limited.
+#
+# Note that commands that return errors will stop running a script, so we
+# have new RUN lines for any command that is expected to return an error.
+
+# RUN: %lldb -s %s -o exit 2>&1 | FileCheck %s
+
+# Test plugin list without an argument will list all plugins.
+plugin list
+# CHECK-LABEL: plugin list
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin list with an argument will match a plugin name by substring.
+plugin list macosx
+# CHECK-LABEL: plugin list macosx
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin exact list works with fully qualified name.
+plugin list -x system-runtime.systemruntime-macosx
+# CHECK-LABEL: plugin list -x system-runtime.systemruntime-macosx
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin glob list works with fully qualified name.
+plugin list system-runtime.systemruntime-macosx
+# CHECK-LABEL: plugin list system-runtime.systemruntime-macosx
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin exact list still allows glob patterns.
+plugin list -x system*
+# CHECK-LABEL: plugin list -x system*
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin glob list still allows glob patterns.
+plugin list system-runtime.*
+# CHECK-LABEL: plugin list system-runtime.*
+# CHECK: system-runtime
+# CHECK:  [+] systemruntime-macosx           System runtime plugin for Mac OS X native libraries
+
+# Test plugin list can disable implicit glob patterns.
+# RUN: %lldb -o "plugin list -x macosx" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND
+
+# Test plugin list for unknown plugin returns an error.
+# RUN: %lldb -o "plugin list some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND
+# ERROR_PLUGIN_NOT_FOUND: error: Found no matching plugins



More information about the lldb-commits mailing list