llvm-project/lldb/source/Commands/CommandObjectPlugin.cpp
David Peixotto d4fe522eb4
Add commands to list/enable/disable plugins (#134418)
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 and InstrumentationRuntime plugins are currently
supported. We can easily extend the existing implementation to support
more types. The scope was limited to these plugins to keep the PR size
manageable.

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.
2025-06-09 13:30:13 -07:00

335 lines
11 KiB
C++

//===-- 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/CommandReturnObject.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) {
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());
}
});
}
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 {
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;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_json_format = false;
}
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;
};
} // 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; }
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);
else
OutputTextFormat(patterns, result);
}
private:
void OutputJsonFormat(const std::vector<llvm::StringRef> &patterns,
CommandReturnObject &result) {
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) {
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.AppendMessageWithFormat(
" %s %-30s %s\n", plugin.enabled ? "[+]" : "[-]",
plugin.name.data(), plugin.description.data());
}
});
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) {
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);
if (num_matching == 0) {
result.AppendErrorWithFormat(
"Found no matching plugins to %s for pattern '%s'", name,
pattern.data());
break;
}
}
}
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 {
DoPluginEnableDisable(command, result, /*enable=*/true);
}
};
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 {
DoPluginEnableDisable(command, result, /*enable=*/false);
}
};
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;