//===-- 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 &plugin_info)> action) { int num_matching = 0; for (const PluginNamespace &plugin_namespace : PluginManager::GetPluginNamespaces()) { std::vector 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 &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(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 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 #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; 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 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 &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 &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 &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 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(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 GetDefinitions() override { return m_definitions; } PluginDomainKind m_domain = kDefaultDomain; private: llvm::ArrayRef 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 []") { 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;