Files
llvm/lldb/source/Commands/CommandObjectLog.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

600 lines
20 KiB
C++
Raw Normal View History

//===-- CommandObjectLog.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 "CommandObjectLog.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Host/OptionParser.h"
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionArgParser.h"
#include "lldb/Interpreter/OptionValueEnumeration.h"
#include "lldb/Interpreter/OptionValueUInt64.h"
#include "lldb/Interpreter/Options.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/Timer.h"
using namespace lldb;
using namespace lldb_private;
#define LLDB_OPTIONS_log_enable
#include "CommandOptions.inc"
#define LLDB_OPTIONS_log_dump
#include "CommandOptions.inc"
/// Common completion logic for log enable/disable.
static void CompleteEnableDisable(CompletionRequest &request) {
size_t arg_index = request.GetCursorIndex();
if (arg_index == 0) { // We got: log enable/disable x[tab]
for (llvm::StringRef channel : Log::ListChannels())
request.TryCompleteCurrentArg(channel);
} else if (arg_index >= 1) { // We got: log enable/disable channel x[tab]
llvm::StringRef channel = request.GetParsedLine().GetArgumentAtIndex(0);
Log::ForEachChannelCategory(
channel, [&request](llvm::StringRef name, llvm::StringRef desc) {
request.TryCompleteCurrentArg(name, desc);
});
}
}
class CommandObjectLogEnable : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogEnable(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log enable",
"Enable logging for a single log channel.",
nullptr) {
CommandArgumentEntry arg1;
CommandArgumentEntry arg2;
CommandArgumentData channel_arg;
CommandArgumentData category_arg;
// Define the first (and only) variant of this arg.
channel_arg.arg_type = eArgTypeLogChannel;
channel_arg.arg_repetition = eArgRepeatPlain;
// There is only one variant this argument could be; put it into the
// argument entry.
arg1.push_back(channel_arg);
category_arg.arg_type = eArgTypeLogCategory;
category_arg.arg_repetition = eArgRepeatPlus;
arg2.push_back(category_arg);
// Push the data for the first argument into the m_arguments vector.
m_arguments.push_back(arg1);
m_arguments.push_back(arg2);
}
~CommandObjectLogEnable() override = default;
Options *GetOptions() override { return &m_options; }
class CommandOptions : public Options {
public:
CommandOptions() = default;
~CommandOptions() 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 'f':
log_file.SetFile(option_arg, FileSpec::Style::native);
FileSystem::Instance().Resolve(log_file);
break;
case 'h':
handler = (LogHandlerKind)OptionArgParser::ToOptionEnum(
option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
if (!error.Success())
return Status::FromErrorStringWithFormatv(
"unrecognized value for log handler '{0}'", option_arg);
break;
case 'b':
return buffer_size.SetValueFromString(option_arg,
eVarSetOperationAssign);
case 'v':
log_options |= LLDB_LOG_OPTION_VERBOSE;
break;
case 's':
log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE;
break;
case 'T':
log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP;
break;
case 'p':
log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD;
break;
case 'n':
log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME;
break;
case 'S':
log_options |= LLDB_LOG_OPTION_BACKTRACE;
break;
case 'a':
log_options |= LLDB_LOG_OPTION_APPEND;
break;
case 'F':
log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION;
break;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
log_file.Clear();
buffer_size.Clear();
handler = eLogHandlerStream;
log_options = 0;
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_log_enable_options);
}
FileSpec log_file;
OptionValueUInt64 buffer_size;
LogHandlerKind handler = eLogHandlerStream;
uint32_t log_options = 0;
};
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
CompleteEnableDisable(request);
}
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
if (args.GetArgumentCount() < 2) {
result.AppendErrorWithFormat(
"%s takes a log channel and one or more log types.\n",
m_cmd_name.c_str());
return;
}
if (m_options.handler == eLogHandlerCircular &&
m_options.buffer_size.GetCurrentValue() == 0) {
result.AppendError(
"the circular buffer handler requires a non-zero buffer size.\n");
return;
}
if ((m_options.handler != eLogHandlerCircular &&
m_options.handler != eLogHandlerStream) &&
m_options.buffer_size.GetCurrentValue() != 0) {
result.AppendError("a buffer size can only be specified for the circular "
"and stream buffer handler.\n");
return;
}
if (m_options.handler != eLogHandlerStream && m_options.log_file) {
result.AppendError(
"a file name can only be specified for the stream handler.\n");
return;
}
// Store into a std::string since we're about to shift the channel off.
const std::string channel = std::string(args[0].ref());
args.Shift(); // Shift off the channel
char log_file[PATH_MAX];
if (m_options.log_file)
m_options.log_file.GetPath(log_file, sizeof(log_file));
else
log_file[0] = '\0';
std::string error;
llvm::raw_string_ostream error_stream(error);
bool success = GetDebugger().EnableLog(
channel, args.GetArgumentArrayRef(), log_file, m_options.log_options,
m_options.buffer_size.GetCurrentValue(), m_options.handler,
error_stream);
result.GetErrorStream() << error;
if (success)
result.SetStatus(eReturnStatusSuccessFinishNoResult);
else
result.SetStatus(eReturnStatusFailed);
}
CommandOptions m_options;
};
class CommandObjectLogDisable : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogDisable(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log disable",
"Disable one or more log channel categories.",
nullptr) {
CommandArgumentEntry arg1;
CommandArgumentEntry arg2;
CommandArgumentData channel_arg;
CommandArgumentData category_arg;
// Define the first (and only) variant of this arg.
channel_arg.arg_type = eArgTypeLogChannel;
channel_arg.arg_repetition = eArgRepeatPlain;
// There is only one variant this argument could be; put it into the
// argument entry.
arg1.push_back(channel_arg);
category_arg.arg_type = eArgTypeLogCategory;
category_arg.arg_repetition = eArgRepeatPlus;
arg2.push_back(category_arg);
// Push the data for the first argument into the m_arguments vector.
m_arguments.push_back(arg1);
m_arguments.push_back(arg2);
}
~CommandObjectLogDisable() override = default;
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
CompleteEnableDisable(request);
}
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
if (args.empty()) {
result.AppendErrorWithFormat(
"%s takes a log channel and one or more log types.\n",
m_cmd_name.c_str());
return;
}
const std::string channel = std::string(args[0].ref());
args.Shift(); // Shift off the channel
if (channel == "all") {
Log::DisableAllLogChannels();
result.SetStatus(eReturnStatusSuccessFinishNoResult);
} else {
std::string error;
llvm::raw_string_ostream error_stream(error);
if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(),
error_stream))
result.SetStatus(eReturnStatusSuccessFinishNoResult);
result.GetErrorStream() << error;
}
}
};
class CommandObjectLogList : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogList(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log list",
"List the log categories for one or more log "
"channels. If none specified, lists them all.",
nullptr) {
AddSimpleArgumentList(eArgTypeLogChannel, eArgRepeatStar);
}
~CommandObjectLogList() override = default;
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
for (llvm::StringRef channel : Log::ListChannels())
request.TryCompleteCurrentArg(channel);
}
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
std::string output;
llvm::raw_string_ostream output_stream(output);
if (args.empty()) {
Log::ListAllLogChannels(output_stream);
result.SetStatus(eReturnStatusSuccessFinishResult);
} else {
bool success = true;
for (const auto &entry : args.entries())
success =
success && Log::ListChannelCategories(entry.ref(), output_stream);
if (success)
result.SetStatus(eReturnStatusSuccessFinishResult);
}
result.GetOutputStream() << output;
}
};
class CommandObjectLogDump : public CommandObjectParsed {
public:
CommandObjectLogDump(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log dump",
"dump circular buffer logs", nullptr) {
AddSimpleArgumentList(eArgTypeLogChannel);
}
~CommandObjectLogDump() override = default;
Options *GetOptions() override { return &m_options; }
class CommandOptions : public Options {
public:
CommandOptions() = default;
~CommandOptions() 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 'f':
log_file.SetFile(option_arg, FileSpec::Style::native);
FileSystem::Instance().Resolve(log_file);
break;
default:
llvm_unreachable("Unimplemented option");
}
return error;
}
void OptionParsingStarting(ExecutionContext *execution_context) override {
log_file.Clear();
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
return llvm::ArrayRef(g_log_dump_options);
}
FileSpec log_file;
};
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
CompleteEnableDisable(request);
}
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
if (args.empty()) {
result.AppendErrorWithFormat(
"%s takes a log channel and one or more log types.\n",
m_cmd_name.c_str());
return;
}
std::unique_ptr<llvm::raw_ostream> stream_up;
if (m_options.log_file) {
const File::OpenOptions flags = File::eOpenOptionWriteOnly |
File::eOpenOptionCanCreate |
File::eOpenOptionTruncate;
llvm::Expected<FileUP> file = FileSystem::Instance().Open(
m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);
if (!file) {
result.AppendErrorWithFormat("Unable to open log file '%s': %s",
[NFC] Improve FileSpec internal APIs and usage in preparation for adding caching of resolved/absolute. Resubmission of https://reviews.llvm.org/D130309 with the 2 patches that fixed the linux buildbot, and new windows fixes. The FileSpec APIs allow users to modify instance variables directly by getting a non const reference to the directory and filename instance variables. This makes it impossible to control all of the times the FileSpec object is modified so we can clear cached member variables like m_resolved and with an upcoming patch caching if the file is relative or absolute. This patch modifies the APIs of FileSpec so no one can modify the directory or filename instance variables directly by adding set accessors and by removing the get accessors that are non const. Many clients were using FileSpec::GetCString(...) which returned a unique C string from a ConstString'ified version of the result of GetPath() which returned a std::string. This caused many locations to use this convenient function incorrectly and could cause many strings to be added to the constant string pool that didn't need to. Most clients were converted to using FileSpec::GetPath().c_str() when possible. Other clients were modified to use the newly renamed version of this function which returns an actualy ConstString: ConstString FileSpec::GetPathAsConstString(bool denormalize = true) const; This avoids the issue where people were getting an already uniqued "const char *" that came from a ConstString only to put the "const char *" back into a "ConstString" object. By returning the ConstString instead of a "const char *" clients can be more efficient with the result. The patch: - Removes the non const GetDirectory() and GetFilename() get accessors - Adds set accessors to replace the above functions: SetDirectory() and SetFilename(). - Adds ClearDirectory() and ClearFilename() to replace usage of the FileSpec::GetDirectory().Clear()/FileSpec::GetFilename().Clear() call sites - Fixed all incorrect usage of FileSpec::GetCString() to use FileSpec::GetPath().c_str() where appropriate, and updated other call sites that wanted a ConstString to use the newly returned ConstString appropriately and efficiently. Differential Revision: https://reviews.llvm.org/D130549
2022-07-25 23:29:30 -07:00
m_options.log_file.GetPath().c_str(),
llvm::toString(file.takeError()).c_str());
return;
}
stream_up = std::make_unique<llvm::raw_fd_ostream>(
(*file)->GetDescriptor(), /*shouldClose=*/true);
} else {
stream_up = std::make_unique<llvm::raw_fd_ostream>(
GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/false);
}
const std::string channel = std::string(args[0].ref());
std::string error;
llvm::raw_string_ostream error_stream(error);
if (Log::DumpLogChannel(channel, *stream_up, error_stream)) {
result.SetStatus(eReturnStatusSuccessFinishNoResult);
} else {
result.SetStatus(eReturnStatusFailed);
result.GetErrorStream() << error;
}
}
CommandOptions m_options;
};
class CommandObjectLogTimerEnable : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogTimerEnable(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log timers enable",
"enable LLDB internal performance timers",
"log timers enable <depth>") {
AddSimpleArgumentList(eArgTypeCount, eArgRepeatOptional);
}
~CommandObjectLogTimerEnable() override = default;
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
result.SetStatus(eReturnStatusFailed);
if (args.GetArgumentCount() == 0) {
Timer::SetDisplayDepth(UINT32_MAX);
result.SetStatus(eReturnStatusSuccessFinishNoResult);
} else if (args.GetArgumentCount() == 1) {
uint32_t depth;
if (args[0].ref().consumeInteger(0, depth)) {
result.AppendError(
"Could not convert enable depth to an unsigned integer.");
} else {
Timer::SetDisplayDepth(depth);
result.SetStatus(eReturnStatusSuccessFinishNoResult);
}
}
if (!result.Succeeded()) {
result.AppendError("Missing subcommand");
result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
}
}
};
class CommandObjectLogTimerDisable : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogTimerDisable(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log timers disable",
"disable LLDB internal performance timers",
nullptr) {}
~CommandObjectLogTimerDisable() override = default;
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
Timer::DumpCategoryTimes(result.GetOutputStream());
Timer::SetDisplayDepth(0);
result.SetStatus(eReturnStatusSuccessFinishResult);
if (!result.Succeeded()) {
result.AppendError("Missing subcommand");
result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
}
}
};
class CommandObjectLogTimerDump : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogTimerDump(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log timers dump",
"dump LLDB internal performance timers", nullptr) {}
~CommandObjectLogTimerDump() override = default;
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
Timer::DumpCategoryTimes(result.GetOutputStream());
result.SetStatus(eReturnStatusSuccessFinishResult);
if (!result.Succeeded()) {
result.AppendError("Missing subcommand");
result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
}
}
};
class CommandObjectLogTimerReset : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogTimerReset(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log timers reset",
"reset LLDB internal performance timers", nullptr) {
}
~CommandObjectLogTimerReset() override = default;
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
Timer::ResetCategoryTimes();
result.SetStatus(eReturnStatusSuccessFinishResult);
if (!result.Succeeded()) {
result.AppendError("Missing subcommand");
result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
}
}
};
class CommandObjectLogTimerIncrement : public CommandObjectParsed {
public:
// Constructors and Destructors
CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "log timers increment",
"increment LLDB internal performance timers",
"log timers increment <bool>") {
AddSimpleArgumentList(eArgTypeBoolean);
}
~CommandObjectLogTimerIncrement() override = default;
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
request.TryCompleteCurrentArg("true");
request.TryCompleteCurrentArg("false");
}
protected:
void DoExecute(Args &args, CommandReturnObject &result) override {
result.SetStatus(eReturnStatusFailed);
if (args.GetArgumentCount() == 1) {
bool success;
bool increment =
OptionArgParser::ToBoolean(args[0].ref(), false, &success);
if (success) {
Timer::SetQuiet(!increment);
result.SetStatus(eReturnStatusSuccessFinishNoResult);
} else
result.AppendError("Could not convert increment value to boolean.");
}
if (!result.Succeeded()) {
result.AppendError("Missing subcommand");
result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
}
}
};
class CommandObjectLogTimer : public CommandObjectMultiword {
public:
CommandObjectLogTimer(CommandInterpreter &interpreter)
: CommandObjectMultiword(interpreter, "log timers",
"Enable, disable, dump, and reset LLDB internal "
"performance timers.",
"log timers < enable <depth> | disable | dump | "
"increment <bool> | reset >") {
LoadSubCommand("enable", CommandObjectSP(
new CommandObjectLogTimerEnable(interpreter)));
LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable(
interpreter)));
LoadSubCommand("dump",
CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));
LoadSubCommand(
"reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));
LoadSubCommand(
"increment",
CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));
}
~CommandObjectLogTimer() override = default;
};
CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
: CommandObjectMultiword(interpreter, "log",
"Commands controlling LLDB internal logging.",
"log <subcommand> [<command-options>]") {
LoadSubCommand("enable",
CommandObjectSP(new CommandObjectLogEnable(interpreter)));
LoadSubCommand("disable",
CommandObjectSP(new CommandObjectLogDisable(interpreter)));
LoadSubCommand("list",
CommandObjectSP(new CommandObjectLogList(interpreter)));
LoadSubCommand("dump",
CommandObjectSP(new CommandObjectLogDump(interpreter)));
LoadSubCommand("timers",
CommandObjectSP(new CommandObjectLogTimer(interpreter)));
}
CommandObjectLog::~CommandObjectLog() = default;