diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index a6dab045adf27..3c02b37593ae0 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -18,10 +18,12 @@ #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-interfaces.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include #include +#include #include #define LLDB_PLUGIN_DEFINE_ADV(ClassName, PluginName) \ @@ -54,12 +56,33 @@ struct RegisteredPluginInfo { bool enabled = 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 plugins. For example SystemRuntime +// and ObjectFile plugins. +// +// The namespace name is used a prefix when matching plugin names. For example, +// if we have an "macosx" plugin in the "system-runtime" namespace then we will +// match a plugin name pattern against the "system-runtime.macosx" 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()>; +using SetPluginEnabled = std::function; +struct PluginNamespace { + llvm::StringRef name; + GetPluginInfo get_info; + SetPluginEnabled set_enabled; +}; + class PluginManager { public: static void Initialize(); static void Terminate(); + static llvm::ArrayRef GetPluginNamespaces(); + // ABI static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, ABICreateInstance create_callback); diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h index 1875ff6a048d4..8535dfcf46da5 100644 --- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h +++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h @@ -314,6 +314,7 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = { { lldb::eArgTypeModule, "module", lldb::CompletionType::eModuleCompletion, {}, { nullptr, false }, "The name of a module loaded into the current target." }, { lldb::eArgTypeCPUName, "cpu-name", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of a CPU." }, { lldb::eArgTypeCPUFeatures, "cpu-features", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The CPU feature string." }, + { lldb::eArgTypeManagedPlugin, "managed-plugin", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "Plugins managed by the PluginManager" }, // clang-format on }; diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h index ee365357fcf31..bffd361a8146b 100644 --- a/lldb/include/lldb/Target/Statistics.h +++ b/lldb/include/lldb/Target/Statistics.h @@ -174,12 +174,21 @@ struct StatisticsOptions { return !GetSummaryOnly(); } + void SetIncludePlugins(bool value) { m_include_plugins = value; } + bool GetIncludePlugins() const { + if (m_include_plugins.has_value()) + return m_include_plugins.value(); + // Default to true in both default mode and summary mode. + return true; + } + private: std::optional m_summary_only; std::optional m_load_all_debug_info; std::optional m_include_targets; std::optional m_include_modules; std::optional m_include_transcript; + std::optional m_include_plugins; }; /// A class that represents statistics about a TypeSummaryProviders invocations diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 8e962428260f8..6bfd02dcd7e88 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -663,6 +663,7 @@ enum CommandArgumentType { eArgTypeModule, eArgTypeCPUName, eArgTypeCPUFeatures, + eArgTypeManagedPlugin, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp index f3108b8a768d2..417ab1ecba899 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,206 @@ class CommandObjectPluginLoad : public CommandObjectParsed { } }; +namespace { +// 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( + const llvm::StringRef pattern, + std::function &plugin_info)> + action) { + int num_matching = 0; + + for (const PluginNamespace &plugin_namespace : + PluginManager::GetPluginNamespaces()) { + const bool match_namespace = + pattern.empty() || pattern == plugin_namespace.name; + + std::vector matching_plugins; + for (const RegisteredPluginInfo &plugin_info : + plugin_namespace.get_info()) { + + // If we match the namespace, we can skip the plugin name check. + bool match_qualified_name = false; + if (!match_namespace) { + std::string qualified_name = + (plugin_namespace.name + "." + plugin_info.name).str(); + match_qualified_name = pattern == qualified_name; + } + + if (match_namespace || 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; +} + +// 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::StringRef &pattern, + CommandReturnObject &result, bool enabled) { + return ActOnMatchingPlugins( + pattern, [&](const PluginNamespace &plugin_namespace, + const std::vector &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(eArgTypeManagedPlugin); + SetHelpLong(R"( +Display information about registered plugins. +The plugin information is formatted as shown below: + + + [+] Plugin #1 description + [-] Plugin #2 description + +An enabled plugin is marked with [+] and a disabled plugin is marked with [-]. + +Plugins can be listed by namespace and name with: + + plugin list [.] + +Plugins can be listed by namespace alone or with a fully qualified name. When listed +with just a namespace all plugins in that namespace are listed. When no arguments +are given all plugins are listed. + +Examples: +List all plugins + + (lldb) plugin list + +List all plugins in the system-runtime namespace + + (lldb) plugin list system-runtime + +List only the plugin 'foo' matching a fully qualified name exactly + + (lldb) plugin list system-runtime.foo +)"); + } + + ~CommandObjectPluginList() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + size_t argc = command.GetArgumentCount(); + if (argc > 1) { + result.AppendError("'plugin load' requires one argument"); + return; + } + llvm::StringRef pattern = argc ? command[0].ref() : ""; + result.SetStatus(eReturnStatusSuccessFinishResult); + + int num_matching = ActOnMatchingPlugins( + pattern, [&](const PluginNamespace &plugin_namespace, + const std::vector &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"); + } +}; + +class CommandObjectPluginEnable : public CommandObjectParsed { +public: + CommandObjectPluginEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin enable", + "Enable registered LLDB plugins.", nullptr) { + AddSimpleArgumentList(eArgTypeManagedPlugin); + } + + ~CommandObjectPluginEnable() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + size_t argc = command.GetArgumentCount(); + if (argc > 1) { + result.AppendError("'plugin enable' requires one argument"); + return; + } + llvm::StringRef pattern = argc ? command[0].ref() : ""; + result.SetStatus(eReturnStatusSuccessFinishResult); + + int num_matching = SetEnableOnMatchingPlugins(pattern, result, true); + + if (num_matching == 0) + result.AppendErrorWithFormat("Found no matching plugins to enable"); + } +}; + +class CommandObjectPluginDisable : public CommandObjectParsed { +public: + CommandObjectPluginDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin disable", + "Disable registered LLDB plugins.", nullptr) { + AddSimpleArgumentList(eArgTypeManagedPlugin); + } + + ~CommandObjectPluginDisable() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + size_t argc = command.GetArgumentCount(); + if (argc > 1) { + result.AppendError("'plugin disable' requires one argument"); + return; + } + llvm::StringRef pattern = argc ? command[0].ref() : ""; + result.SetStatus(eReturnStatusSuccessFinishResult); + + int num_matching = SetEnableOnMatchingPlugins(pattern, result, false); + + if (num_matching == 0) + result.AppendErrorWithFormat("Found no matching plugins to disable"); + } +}; + CommandObjectPlugin::CommandObjectPlugin(CommandInterpreter &interpreter) : CommandObjectMultiword(interpreter, "plugin", "Commands for managing LLDB plugins.", "plugin []") { 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/CommandObjectStats.cpp b/lldb/source/Commands/CommandObjectStats.cpp index 7d333afc231ba..b77c44bdf5d09 100644 --- a/lldb/source/Commands/CommandObjectStats.cpp +++ b/lldb/source/Commands/CommandObjectStats.cpp @@ -103,6 +103,13 @@ class CommandObjectStatsDump : public CommandObjectParsed { else error = Status::FromError(bool_or_error.takeError()); break; + case 'p': + if (llvm::Expected bool_or_error = + OptionArgParser::ToBoolean("--plugins", option_arg)) + m_stats_options.SetIncludePlugins(*bool_or_error); + else + error = Status::FromError(bool_or_error.takeError()); + break; default: llvm_unreachable("Unimplemented option"); } diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index cc579d767eb06..c381f51925916 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1467,5 +1467,11 @@ let Command = "statistics dump" in { "scripts executed during a debug session. " "Defaults to true, unless the '--summary' mode is enabled, in which case " "this is turned off unless specified.">; - + def statistics_dump_plugins + : Option<"plugins", "p">, + Group<1>, + Arg<"Boolean">, + Desc<"Dump statistics for known plugins including name, order, and " + "enabled state. Defaults to true for both summary and default " + "mode.">; } diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index e6cb248ef31ce..8551c9f9a73e3 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -181,6 +181,16 @@ void PluginManager::Terminate() { plugin_map.clear(); } +llvm::ArrayRef PluginManager::GetPluginNamespaces() { + // Currently supported set of plugin namespaces. This will be expanded + // over time. + static PluginNamespace PluginNamespaces[] = { + {"system-runtime", PluginManager::GetSystemRuntimePluginInfo, + PluginManager::SetSystemRuntimePluginEnabled}}; + + return PluginNamespaces; +} + template struct PluginInstance { typedef Callback CallbackType; diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp index b5d2e7bda1edf..a00404f347ffd 100644 --- a/lldb/source/Target/Statistics.cpp +++ b/lldb/source/Target/Statistics.cpp @@ -10,6 +10,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Target/DynamicLoader.h" @@ -285,6 +286,7 @@ llvm::json::Value DebuggerStats::ReportStatistics( const bool include_targets = options.GetIncludeTargets(); const bool include_modules = options.GetIncludeModules(); const bool include_transcript = options.GetIncludeTranscript(); + const bool include_plugins = options.GetIncludePlugins(); json::Array json_targets; json::Array json_modules; @@ -464,6 +466,24 @@ llvm::json::Value DebuggerStats::ReportStatistics( } } + if (include_plugins) { + json::Object plugin_stats; + for (const PluginNamespace &plugin_ns : + PluginManager::GetPluginNamespaces()) { + json::Array namespace_stats; + + for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) { + 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)); + } + plugin_stats.try_emplace(plugin_ns.name, std::move(namespace_stats)); + } + global_stats.try_emplace("plugins", std::move(plugin_stats)); + } + return std::move(global_stats); } diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py index 54881c13bcb68..4e635c34459e0 100644 --- a/lldb/test/API/commands/statistics/basic/TestStats.py +++ b/lldb/test/API/commands/statistics/basic/TestStats.py @@ -1022,3 +1022,89 @@ def test_multiple_targets(self): all_targets_stats = self.get_stats("--all-targets") self.assertIsNotNone(self.find_module_in_metrics(main_exe, all_targets_stats)) self.assertIsNotNone(self.find_module_in_metrics(second_exe, all_targets_stats)) + + # Return some level of the plugin stats hierarchy. + # Will return either the top-level node, the namespace node, or a specific + # plugin node based on requested values. + # + # If any of the requested keys are not found in the stats then return None. + # + # Plugin stats look like this: + # + # "plugins": { + # "system-runtime": [ + # { + # "enabled": true, + # "name": "systemruntime-macosx" + # } + # ] + # }, + def get_plugin_stats(self, debugger_stats, plugin_namespace=None, plugin_name=None): + # Get top level plugin stats. + if "plugins" not in debugger_stats: + return None + plugins = debugger_stats["plugins"] + if not plugin_namespace: + return plugins + + # Plugin namespace stats. + if plugin_namespace not in plugins: + return None + plugins_for_namespace = plugins[plugin_namespace] + if not plugin_name: + return plugins_for_namespace + + # Specific plugin stats. + for plugin in debugger_stats["plugins"][plugin_namespace]: + if plugin["name"] == plugin_name: + return plugin + return None + + def test_plugin_stats(self): + """ + Test "statistics dump" contains plugin info. + """ + self.build() + exe = self.getBuildArtifact("a.out") + target = self.createTestTarget(file_path=exe) + debugger_stats = self.get_stats() + + # Verify that the statistics dump contains the plugin information. + plugins = self.get_plugin_stats(debugger_stats) + self.assertIsNotNone(plugins) + + # Check for a known plugin namespace that should be in the stats. + system_runtime_plugins = self.get_plugin_stats(debugger_stats, "system-runtime") + self.assertIsNotNone(system_runtime_plugins) + + # Validate the keys exists for the bottom-level plugin stats. + plugin_keys_exist = [ + "name", + "enabled", + ] + for plugin in system_runtime_plugins: + self.verify_keys( + plugin, 'debugger_stats["plugins"]["system-runtime"]', plugin_keys_exist + ) + + # Check for a known plugin that is enabled by default. + system_runtime_macosx_plugin = self.get_plugin_stats( + debugger_stats, "system-runtime", "systemruntime-macosx" + ) + self.assertIsNotNone(system_runtime_macosx_plugin) + self.assertTrue(system_runtime_macosx_plugin["enabled"]) + + # Now disable the plugin and check the stats again. + # The stats should show the plugin is disabled. + self.runCmd("plugin disable system-runtime.systemruntime-macosx") + debugger_stats = self.get_stats() + system_runtime_macosx_plugin = self.get_plugin_stats( + debugger_stats, "system-runtime", "systemruntime-macosx" + ) + self.assertIsNotNone(system_runtime_macosx_plugin) + self.assertFalse(system_runtime_macosx_plugin["enabled"]) + + # Plugins should not show up in the stats when disabled with an option. + debugger_stats = self.get_stats("--plugins false") + plugins = self.get_plugin_stats(debugger_stats) + self.assertIsNone(plugins) 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..eb16114ce79bf --- /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 system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin disable system-runtime.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 system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin enable system-runtime.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 namespace works. +plugin disable system-runtime +# CHECK-LABEL: plugin disable system-runtime +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable with namespace works. +plugin enable system-runtime +# CHECK-LABEL: plugin enable system-runtime +# 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..4730588988451 --- /dev/null +++ b/lldb/test/Shell/Commands/command-plugin-list.test @@ -0,0 +1,36 @@ +# 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 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 list on plugin namespace works. +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 does not match a plugin name by substring. +# RUN: %lldb -o "plugin list macosx" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND + +# Test plugin list does not match a plugin namespace by substring. +# RUN: %lldb -o "plugin list system-runtime." 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