| //===-- CommandObjectPlugin.cpp -------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "CommandObjectPlugin.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Host/OptionParser.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/CommandOptionArgumentTable.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Interpreter/OptionArgParser.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| class CommandObjectPluginLoad : public CommandObjectParsed { | |
| public: | |
| CommandObjectPluginLoad(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "plugin load", | |
| "Import a dylib that implements an LLDB plugin.", | |
| nullptr) { | |
| AddSimpleArgumentList(eArgTypeFilename); | |
| } | |
| ~CommandObjectPluginLoad() 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; | |
| } | |
| Status error; | |
| FileSpec dylib_fspec(command[0].ref()); | |
| FileSystem::Instance().Resolve(dylib_fspec); | |
| if (GetDebugger().LoadPlugin(dylib_fspec, error)) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else { | |
| result.AppendError(error.AsCString()); | |
| } | |
| } | |
| }; | |
| 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<void(const PluginNamespace &plugin_namespace, | |
| const std::vector<RegisteredPluginInfo> &plugin_info)> | |
| action) { | |
| int num_matching = 0; | |
| for (const PluginNamespace &plugin_namespace : | |
| PluginManager::GetPluginNamespaces()) { | |
| std::vector<RegisteredPluginInfo> matching_plugins; | |
| for (const RegisteredPluginInfo &plugin_info : | |
| plugin_namespace.get_info()) { | |
| if (PluginManager::MatchPluginName(pattern, plugin_namespace, | |
| plugin_info)) | |
| 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, | |
| Debugger &requesting_debugger, | |
| PluginDomainKind domain) { | |
| return ActOnMatchingPlugins( | |
| pattern, [&](const PluginNamespace &plugin_namespace, | |
| const std::vector<RegisteredPluginInfo> &plugins) { | |
| auto PrintEnablement = [enabled, | |
| &result](const RegisteredPluginInfo plugin) { | |
| result.AppendMessageWithFormatv(" {0} {1, -30} {2}", | |
| enabled ? "[+]" : "[-]", plugin.name, | |
| plugin.description); | |
| }; | |
| result.AppendMessage(plugin_namespace.name); | |
| for (const auto &plugin : plugins) { | |
| if (plugin_namespace.SupportsOnlyDomain( | |
| PluginDomainKind::ePluginDomainKindGlobal)) { | |
| bool success = true; | |
| if (domain != ePluginDomainKindGlobal) { | |
| result.AppendErrorWithFormatv( | |
| "failed to {} plugin {}.{}: {} domain is not supported", | |
| enabled ? "enable" : "disable", plugin_namespace.name, | |
| plugin.name, PluginManager::PluginDomainKindToStr(domain)); | |
| continue; | |
| } | |
| success = (*plugin_namespace.GetSetEnabledGlobalFn())(plugin.name, | |
| enabled); | |
| if (!success) { | |
| result.AppendErrorWithFormatv("failed to {} plugin {}.{}", | |
| enabled ? "enable" : "disable", | |
| plugin_namespace.name, plugin.name); | |
| continue; | |
| } | |
| PrintEnablement(plugin); | |
| continue; | |
| } | |
| // Handle plugin namespace that supports more than just the global | |
| // domain. Currently this is just the instrumentation-runtime | |
| // namespace. | |
| if (!plugin_namespace.SupportsDomain(domain)) { | |
| result.AppendErrorWithFormatv( | |
| "failed to {0} plugin {1}.{2}: the {1} namespace " | |
| "does not support the {3} domain", | |
| enabled ? "enable" : "disable", plugin_namespace.name, | |
| plugin.name, PluginManager::PluginDomainKindToStr(domain)); | |
| continue; | |
| } | |
| assert(plugin_namespace.GetSetEnabledAllDomainsFn().has_value()); | |
| llvm::Error error = (*plugin_namespace.GetSetEnabledAllDomainsFn())( | |
| plugin.name, enabled, requesting_debugger, domain); | |
| if (error) { | |
| result.AppendErrorWithFormatv("failed to {} plugin {}.{}: {}", | |
| enabled ? "enable" : "disable", | |
| plugin_namespace.name, plugin.name, | |
| llvm::toString(std::move(error))); | |
| continue; | |
| } | |
| PrintEnablement(plugin); | |
| } | |
| }); | |
| } | |
| static std::string ConvertJSONToPrettyString(const llvm::json::Value &json) { | |
| std::string str; | |
| llvm::raw_string_ostream os(str); | |
| os << llvm::formatv("{0:2}", json).str(); | |
| os.flush(); | |
| return str; | |
| } | |
| #define LLDB_OPTIONS_plugin_list | |
| #include "CommandOptions.inc" | |
| // These option definitions are used by the plugin list command. | |
| class PluginListCommandOptions : public Options { | |
| static constexpr const PluginDomainKind kDefaultDomain = | |
| ePluginDomainKindGlobal; | |
| 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 'j': | |
| m_json_format = true; | |
| break; | |
| case 'd': | |
| m_domain = static_cast<PluginDomainKind>(OptionArgParser::ToOptionEnum( | |
| option_arg, GetDefinitions()[option_idx].enum_values, kDefaultDomain, | |
| error)); | |
| break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| m_json_format = false; | |
| m_domain = kDefaultDomain; | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_plugin_list_options); | |
| } | |
| // Instance variables to hold the values for command options. | |
| bool m_json_format = false; | |
| PluginDomainKind m_domain = kDefaultDomain; | |
| }; | |
| } // 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-namespace> | |
| [+] <plugin-name> Plugin #1 description | |
| [-] <plugin-name> 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 <plugin-namespace>[.<plugin-name>] | |
| 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; | |
| Options *GetOptions() override { return &m_options; } | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( | |
| GetCommandInterpreter(), lldb::eManagedPluginCompletion, request, | |
| nullptr); | |
| } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| size_t argc = command.GetArgumentCount(); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| // Create a temporary vector to hold the patterns to simplify the logic | |
| // for the case when the user passes no patterns | |
| std::vector<llvm::StringRef> patterns; | |
| patterns.reserve(argc == 0 ? 1 : argc); | |
| if (argc == 0) | |
| patterns.push_back(""); | |
| else | |
| for (size_t i = 0; i < argc; ++i) | |
| patterns.push_back(command[i].ref()); | |
| if (m_options.m_json_format) | |
| OutputJsonFormat(patterns, result, GetDebugger(), m_options.m_domain); | |
| else | |
| OutputTextFormat(patterns, result, GetDebugger(), m_options.m_domain); | |
| } | |
| private: | |
| void OutputJsonFormat(const std::vector<llvm::StringRef> &patterns, | |
| 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); | |
| if (pat_obj.empty()) { | |
| found_empty = true; | |
| result.AppendErrorWithFormat( | |
| "Found no matching plugins for pattern '%s'", pattern.data()); | |
| break; | |
| } | |
| for (auto &entry : pat_obj) { | |
| obj[entry.first] = std::move(entry.second); | |
| } | |
| } | |
| if (!found_empty) { | |
| result.AppendMessage(ConvertJSONToPrettyString(std::move(obj))); | |
| } | |
| } | |
| void OutputTextFormat(const std::vector<llvm::StringRef> &patterns, | |
| CommandReturnObject &result, | |
| Debugger &requesting_debugger, | |
| PluginDomainKind domain) { | |
| if (domain != PluginDomainKind::ePluginDomainKindGlobal) { | |
| result.AppendErrorWithFormatv( | |
| "{} domain is not supported", | |
| PluginManager::PluginDomainKindToStr(domain)); | |
| return; | |
| } | |
| 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); | |
| } | |
| }); | |
| if (num_matching == 0) { | |
| result.AppendErrorWithFormat( | |
| "Found no matching plugins for pattern '%s'", pattern.data()); | |
| break; | |
| } | |
| } | |
| } | |
| PluginListCommandOptions m_options; | |
| }; | |
| static void DoPluginEnableDisable(Args &command, CommandReturnObject &result, | |
| bool enable, Debugger &requesting_debugger, | |
| PluginDomainKind domain) { | |
| const char *name = enable ? "enable" : "disable"; | |
| size_t argc = command.GetArgumentCount(); | |
| if (argc == 0) { | |
| result.AppendErrorWithFormat("'plugin %s' requires one or more arguments", | |
| name); | |
| return; | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| for (size_t i = 0; i < argc; ++i) { | |
| llvm::StringRef pattern = command[i].ref(); | |
| int num_matching = SetEnableOnMatchingPlugins(pattern, result, enable, | |
| requesting_debugger, domain); | |
| if (num_matching == 0) { | |
| result.AppendErrorWithFormat( | |
| "Found no matching plugins to %s for pattern '%s'", name, | |
| pattern.data()); | |
| break; | |
| } | |
| } | |
| } | |
| #define LLDB_OPTIONS_plugin_enable | |
| #include "CommandOptions.inc" | |
| #define LLDB_OPTIONS_plugin_disable | |
| #include "CommandOptions.inc" | |
| // Options class for the --domain flag, shared by plugin enable and | |
| // plugin disable (and reusable by plugin status in the future). | |
| class PluginDomainOptions : public Options { | |
| static constexpr const PluginDomainKind kDefaultDomain = | |
| ePluginDomainKindGlobal; | |
| public: | |
| PluginDomainOptions(llvm::ArrayRef<OptionDefinition> definitions) | |
| : m_definitions(definitions) {} | |
| 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 'd': | |
| m_domain = static_cast<PluginDomainKind>(OptionArgParser::ToOptionEnum( | |
| option_arg, GetDefinitions()[option_idx].enum_values, kDefaultDomain, | |
| error)); | |
| break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| m_domain = kDefaultDomain; | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return m_definitions; | |
| } | |
| PluginDomainKind m_domain = kDefaultDomain; | |
| private: | |
| llvm::ArrayRef<OptionDefinition> m_definitions; | |
| }; | |
| class CommandObjectPluginEnable : public CommandObjectParsed { | |
| public: | |
| CommandObjectPluginEnable(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "plugin enable", | |
| "Enable registered LLDB plugins.", nullptr), | |
| m_options(llvm::ArrayRef(g_plugin_enable_options)) { | |
| AddSimpleArgumentList(eArgTypeManagedPlugin); | |
| } | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( | |
| GetCommandInterpreter(), lldb::eManagedPluginCompletion, request, | |
| nullptr); | |
| } | |
| ~CommandObjectPluginEnable() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| DoPluginEnableDisable(command, result, /*enable=*/true, GetDebugger(), | |
| m_options.m_domain); | |
| } | |
| PluginDomainOptions m_options; | |
| }; | |
| class CommandObjectPluginDisable : public CommandObjectParsed { | |
| public: | |
| CommandObjectPluginDisable(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "plugin disable", | |
| "Disable registered LLDB plugins.", nullptr), | |
| m_options(llvm::ArrayRef(g_plugin_disable_options)) { | |
| AddSimpleArgumentList(eArgTypeManagedPlugin); | |
| } | |
| void | |
| HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &opt_element_vector) override { | |
| lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( | |
| GetCommandInterpreter(), lldb::eManagedPluginCompletion, request, | |
| nullptr); | |
| } | |
| ~CommandObjectPluginDisable() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| DoPluginEnableDisable(command, result, /*enable=*/false, GetDebugger(), | |
| m_options.m_domain); | |
| } | |
| PluginDomainOptions 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; |