601 lines · cpp
1//===-- CommandObjectLog.cpp ----------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "CommandObjectLog.h"10#include "lldb/Core/Debugger.h"11#include "lldb/Host/OptionParser.h"12#include "lldb/Interpreter/CommandOptionArgumentTable.h"13#include "lldb/Interpreter/CommandReturnObject.h"14#include "lldb/Interpreter/OptionArgParser.h"15#include "lldb/Interpreter/OptionValueEnumeration.h"16#include "lldb/Interpreter/OptionValueUInt64.h"17#include "lldb/Interpreter/Options.h"18#include "lldb/Utility/Args.h"19#include "lldb/Utility/FileSpec.h"20#include "lldb/Utility/Log.h"21#include "lldb/Utility/Stream.h"22#include "lldb/Utility/Timer.h"23 24using namespace lldb;25using namespace lldb_private;26 27#define LLDB_OPTIONS_log_enable28#include "CommandOptions.inc"29 30#define LLDB_OPTIONS_log_dump31#include "CommandOptions.inc"32 33/// Common completion logic for log enable/disable.34static void CompleteEnableDisable(CompletionRequest &request) {35 size_t arg_index = request.GetCursorIndex();36 if (arg_index == 0) { // We got: log enable/disable x[tab]37 for (llvm::StringRef channel : Log::ListChannels())38 request.TryCompleteCurrentArg(channel);39 } else if (arg_index >= 1) { // We got: log enable/disable channel x[tab]40 llvm::StringRef channel = request.GetParsedLine().GetArgumentAtIndex(0);41 Log::ForEachChannelCategory(42 channel, [&request](llvm::StringRef name, llvm::StringRef desc) {43 request.TryCompleteCurrentArg(name, desc);44 });45 }46}47 48class CommandObjectLogEnable : public CommandObjectParsed {49public:50 // Constructors and Destructors51 CommandObjectLogEnable(CommandInterpreter &interpreter)52 : CommandObjectParsed(interpreter, "log enable",53 "Enable logging for a single log channel.",54 nullptr) {55 CommandArgumentEntry arg1;56 CommandArgumentEntry arg2;57 CommandArgumentData channel_arg;58 CommandArgumentData category_arg;59 60 // Define the first (and only) variant of this arg.61 channel_arg.arg_type = eArgTypeLogChannel;62 channel_arg.arg_repetition = eArgRepeatPlain;63 64 // There is only one variant this argument could be; put it into the65 // argument entry.66 arg1.push_back(channel_arg);67 68 category_arg.arg_type = eArgTypeLogCategory;69 category_arg.arg_repetition = eArgRepeatPlus;70 71 arg2.push_back(category_arg);72 73 // Push the data for the first argument into the m_arguments vector.74 m_arguments.push_back(arg1);75 m_arguments.push_back(arg2);76 }77 78 ~CommandObjectLogEnable() override = default;79 80 Options *GetOptions() override { return &m_options; }81 82 class CommandOptions : public Options {83 public:84 CommandOptions() = default;85 86 ~CommandOptions() override = default;87 88 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,89 ExecutionContext *execution_context) override {90 Status error;91 const int short_option = m_getopt_table[option_idx].val;92 93 switch (short_option) {94 case 'f':95 log_file.SetFile(option_arg, FileSpec::Style::native);96 FileSystem::Instance().Resolve(log_file);97 break;98 case 'h':99 handler = (LogHandlerKind)OptionArgParser::ToOptionEnum(100 option_arg, GetDefinitions()[option_idx].enum_values, 0, error);101 if (!error.Success())102 return Status::FromErrorStringWithFormatv(103 "unrecognized value for log handler '{0}'", option_arg);104 break;105 case 'b':106 return buffer_size.SetValueFromString(option_arg,107 eVarSetOperationAssign);108 case 'v':109 log_options |= LLDB_LOG_OPTION_VERBOSE;110 break;111 case 's':112 log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE;113 break;114 case 'T':115 log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP;116 break;117 case 'p':118 log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD;119 break;120 case 'n':121 log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME;122 break;123 case 'S':124 log_options |= LLDB_LOG_OPTION_BACKTRACE;125 break;126 case 'a':127 log_options |= LLDB_LOG_OPTION_APPEND;128 break;129 case 'F':130 log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION;131 break;132 default:133 llvm_unreachable("Unimplemented option");134 }135 136 return error;137 }138 139 void OptionParsingStarting(ExecutionContext *execution_context) override {140 log_file.Clear();141 buffer_size.Clear();142 handler = eLogHandlerStream;143 log_options = 0;144 }145 146 llvm::ArrayRef<OptionDefinition> GetDefinitions() override {147 return llvm::ArrayRef(g_log_enable_options);148 }149 150 FileSpec log_file;151 OptionValueUInt64 buffer_size;152 LogHandlerKind handler = eLogHandlerStream;153 uint32_t log_options = 0;154 };155 156 void157 HandleArgumentCompletion(CompletionRequest &request,158 OptionElementVector &opt_element_vector) override {159 CompleteEnableDisable(request);160 }161 162protected:163 void DoExecute(Args &args, CommandReturnObject &result) override {164 if (args.GetArgumentCount() < 2) {165 result.AppendErrorWithFormat(166 "%s takes a log channel and one or more log types.\n",167 m_cmd_name.c_str());168 return;169 }170 171 if (m_options.handler == eLogHandlerCircular &&172 m_options.buffer_size.GetCurrentValue() == 0) {173 result.AppendError(174 "the circular buffer handler requires a non-zero buffer size.\n");175 return;176 }177 178 if ((m_options.handler != eLogHandlerCircular &&179 m_options.handler != eLogHandlerStream) &&180 m_options.buffer_size.GetCurrentValue() != 0) {181 result.AppendError("a buffer size can only be specified for the circular "182 "and stream buffer handler.\n");183 return;184 }185 186 if (m_options.handler != eLogHandlerStream && m_options.log_file) {187 result.AppendError(188 "a file name can only be specified for the stream handler.\n");189 return;190 }191 192 // Store into a std::string since we're about to shift the channel off.193 const std::string channel = std::string(args[0].ref());194 args.Shift(); // Shift off the channel195 char log_file[PATH_MAX];196 if (m_options.log_file)197 m_options.log_file.GetPath(log_file, sizeof(log_file));198 else199 log_file[0] = '\0';200 201 std::string error;202 llvm::raw_string_ostream error_stream(error);203 bool success = GetDebugger().EnableLog(204 channel, args.GetArgumentArrayRef(), log_file, m_options.log_options,205 m_options.buffer_size.GetCurrentValue(), m_options.handler,206 error_stream);207 result.GetErrorStream() << error;208 209 if (success)210 result.SetStatus(eReturnStatusSuccessFinishNoResult);211 else212 result.SetStatus(eReturnStatusFailed);213 }214 215 CommandOptions m_options;216};217 218class CommandObjectLogDisable : public CommandObjectParsed {219public:220 // Constructors and Destructors221 CommandObjectLogDisable(CommandInterpreter &interpreter)222 : CommandObjectParsed(interpreter, "log disable",223 "Disable one or more log channel categories.",224 nullptr) {225 CommandArgumentEntry arg1;226 CommandArgumentEntry arg2;227 CommandArgumentData channel_arg;228 CommandArgumentData category_arg;229 230 // Define the first (and only) variant of this arg.231 channel_arg.arg_type = eArgTypeLogChannel;232 channel_arg.arg_repetition = eArgRepeatPlain;233 234 // There is only one variant this argument could be; put it into the235 // argument entry.236 arg1.push_back(channel_arg);237 238 category_arg.arg_type = eArgTypeLogCategory;239 category_arg.arg_repetition = eArgRepeatPlus;240 241 arg2.push_back(category_arg);242 243 // Push the data for the first argument into the m_arguments vector.244 m_arguments.push_back(arg1);245 m_arguments.push_back(arg2);246 }247 248 ~CommandObjectLogDisable() override = default;249 250 void251 HandleArgumentCompletion(CompletionRequest &request,252 OptionElementVector &opt_element_vector) override {253 CompleteEnableDisable(request);254 }255 256protected:257 void DoExecute(Args &args, CommandReturnObject &result) override {258 if (args.empty()) {259 result.AppendErrorWithFormat(260 "%s takes a log channel and one or more log types.\n",261 m_cmd_name.c_str());262 return;263 }264 265 const std::string channel = std::string(args[0].ref());266 args.Shift(); // Shift off the channel267 if (channel == "all") {268 Log::DisableAllLogChannels();269 result.SetStatus(eReturnStatusSuccessFinishNoResult);270 } else {271 std::string error;272 llvm::raw_string_ostream error_stream(error);273 if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(),274 error_stream))275 result.SetStatus(eReturnStatusSuccessFinishNoResult);276 result.GetErrorStream() << error;277 }278 }279};280 281class CommandObjectLogList : public CommandObjectParsed {282public:283 // Constructors and Destructors284 CommandObjectLogList(CommandInterpreter &interpreter)285 : CommandObjectParsed(interpreter, "log list",286 "List the log categories for one or more log "287 "channels. If none specified, lists them all.",288 nullptr) {289 AddSimpleArgumentList(eArgTypeLogChannel, eArgRepeatStar);290 }291 292 ~CommandObjectLogList() override = default;293 294 void295 HandleArgumentCompletion(CompletionRequest &request,296 OptionElementVector &opt_element_vector) override {297 for (llvm::StringRef channel : Log::ListChannels())298 request.TryCompleteCurrentArg(channel);299 }300 301protected:302 void DoExecute(Args &args, CommandReturnObject &result) override {303 std::string output;304 llvm::raw_string_ostream output_stream(output);305 if (args.empty()) {306 Log::ListAllLogChannels(output_stream);307 result.SetStatus(eReturnStatusSuccessFinishResult);308 } else {309 bool success = true;310 for (const auto &entry : args.entries())311 success =312 success && Log::ListChannelCategories(entry.ref(), output_stream);313 if (success)314 result.SetStatus(eReturnStatusSuccessFinishResult);315 }316 result.GetOutputStream() << output;317 }318};319class CommandObjectLogDump : public CommandObjectParsed {320public:321 CommandObjectLogDump(CommandInterpreter &interpreter)322 : CommandObjectParsed(interpreter, "log dump",323 "dump circular buffer logs", nullptr) {324 AddSimpleArgumentList(eArgTypeLogChannel);325 }326 327 ~CommandObjectLogDump() override = default;328 329 Options *GetOptions() override { return &m_options; }330 331 class CommandOptions : public Options {332 public:333 CommandOptions() = default;334 335 ~CommandOptions() override = default;336 337 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,338 ExecutionContext *execution_context) override {339 Status error;340 const int short_option = m_getopt_table[option_idx].val;341 342 switch (short_option) {343 case 'f':344 log_file.SetFile(option_arg, FileSpec::Style::native);345 FileSystem::Instance().Resolve(log_file);346 break;347 default:348 llvm_unreachable("Unimplemented option");349 }350 351 return error;352 }353 354 void OptionParsingStarting(ExecutionContext *execution_context) override {355 log_file.Clear();356 }357 358 llvm::ArrayRef<OptionDefinition> GetDefinitions() override {359 return llvm::ArrayRef(g_log_dump_options);360 }361 362 FileSpec log_file;363 };364 365 void366 HandleArgumentCompletion(CompletionRequest &request,367 OptionElementVector &opt_element_vector) override {368 CompleteEnableDisable(request);369 }370 371protected:372 void DoExecute(Args &args, CommandReturnObject &result) override {373 if (args.empty()) {374 result.AppendErrorWithFormat(375 "%s takes a log channel and one or more log types.\n",376 m_cmd_name.c_str());377 return;378 }379 380 std::unique_ptr<llvm::raw_ostream> stream_up;381 if (m_options.log_file) {382 const File::OpenOptions flags = File::eOpenOptionWriteOnly |383 File::eOpenOptionCanCreate |384 File::eOpenOptionTruncate;385 llvm::Expected<FileUP> file = FileSystem::Instance().Open(386 m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);387 if (!file) {388 result.AppendErrorWithFormat("Unable to open log file '%s': %s",389 m_options.log_file.GetPath().c_str(),390 llvm::toString(file.takeError()).c_str());391 return;392 }393 stream_up = std::make_unique<llvm::raw_fd_ostream>(394 (*file)->GetDescriptor(), /*shouldClose=*/true);395 } else {396 stream_up = std::make_unique<llvm::raw_fd_ostream>(397 GetDebugger().GetOutputFileSP()->GetDescriptor(),398 /*shouldClose=*/false);399 }400 401 const std::string channel = std::string(args[0].ref());402 std::string error;403 llvm::raw_string_ostream error_stream(error);404 if (Log::DumpLogChannel(channel, *stream_up, error_stream)) {405 result.SetStatus(eReturnStatusSuccessFinishNoResult);406 } else {407 result.SetStatus(eReturnStatusFailed);408 result.GetErrorStream() << error;409 }410 }411 412 CommandOptions m_options;413};414 415class CommandObjectLogTimerEnable : public CommandObjectParsed {416public:417 // Constructors and Destructors418 CommandObjectLogTimerEnable(CommandInterpreter &interpreter)419 : CommandObjectParsed(interpreter, "log timers enable",420 "enable LLDB internal performance timers",421 "log timers enable <depth>") {422 AddSimpleArgumentList(eArgTypeCount, eArgRepeatOptional);423 }424 425 ~CommandObjectLogTimerEnable() override = default;426 427protected:428 void DoExecute(Args &args, CommandReturnObject &result) override {429 result.SetStatus(eReturnStatusFailed);430 431 if (args.GetArgumentCount() == 0) {432 Timer::SetDisplayDepth(UINT32_MAX);433 result.SetStatus(eReturnStatusSuccessFinishNoResult);434 } else if (args.GetArgumentCount() == 1) {435 uint32_t depth;436 if (args[0].ref().consumeInteger(0, depth)) {437 result.AppendError(438 "Could not convert enable depth to an unsigned integer.");439 } else {440 Timer::SetDisplayDepth(depth);441 result.SetStatus(eReturnStatusSuccessFinishNoResult);442 }443 }444 445 if (!result.Succeeded()) {446 result.AppendError("Missing subcommand");447 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());448 }449 }450};451 452class CommandObjectLogTimerDisable : public CommandObjectParsed {453public:454 // Constructors and Destructors455 CommandObjectLogTimerDisable(CommandInterpreter &interpreter)456 : CommandObjectParsed(interpreter, "log timers disable",457 "disable LLDB internal performance timers",458 nullptr) {}459 460 ~CommandObjectLogTimerDisable() override = default;461 462protected:463 void DoExecute(Args &args, CommandReturnObject &result) override {464 Timer::DumpCategoryTimes(result.GetOutputStream());465 Timer::SetDisplayDepth(0);466 result.SetStatus(eReturnStatusSuccessFinishResult);467 468 if (!result.Succeeded()) {469 result.AppendError("Missing subcommand");470 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());471 }472 }473};474 475class CommandObjectLogTimerDump : public CommandObjectParsed {476public:477 // Constructors and Destructors478 CommandObjectLogTimerDump(CommandInterpreter &interpreter)479 : CommandObjectParsed(interpreter, "log timers dump",480 "dump LLDB internal performance timers", nullptr) {}481 482 ~CommandObjectLogTimerDump() override = default;483 484protected:485 void DoExecute(Args &args, CommandReturnObject &result) override {486 Timer::DumpCategoryTimes(result.GetOutputStream());487 result.SetStatus(eReturnStatusSuccessFinishResult);488 489 if (!result.Succeeded()) {490 result.AppendError("Missing subcommand");491 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());492 }493 }494};495 496class CommandObjectLogTimerReset : public CommandObjectParsed {497public:498 // Constructors and Destructors499 CommandObjectLogTimerReset(CommandInterpreter &interpreter)500 : CommandObjectParsed(interpreter, "log timers reset",501 "reset LLDB internal performance timers", nullptr) {502 }503 504 ~CommandObjectLogTimerReset() override = default;505 506protected:507 void DoExecute(Args &args, CommandReturnObject &result) override {508 Timer::ResetCategoryTimes();509 result.SetStatus(eReturnStatusSuccessFinishResult);510 511 if (!result.Succeeded()) {512 result.AppendError("Missing subcommand");513 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());514 }515 }516};517 518class CommandObjectLogTimerIncrement : public CommandObjectParsed {519public:520 // Constructors and Destructors521 CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)522 : CommandObjectParsed(interpreter, "log timers increment",523 "increment LLDB internal performance timers",524 "log timers increment <bool>") {525 AddSimpleArgumentList(eArgTypeBoolean);526 }527 528 ~CommandObjectLogTimerIncrement() override = default;529 530 void531 HandleArgumentCompletion(CompletionRequest &request,532 OptionElementVector &opt_element_vector) override {533 request.TryCompleteCurrentArg("true");534 request.TryCompleteCurrentArg("false");535 }536 537protected:538 void DoExecute(Args &args, CommandReturnObject &result) override {539 result.SetStatus(eReturnStatusFailed);540 541 if (args.GetArgumentCount() == 1) {542 bool success;543 bool increment =544 OptionArgParser::ToBoolean(args[0].ref(), false, &success);545 546 if (success) {547 Timer::SetQuiet(!increment);548 result.SetStatus(eReturnStatusSuccessFinishNoResult);549 } else550 result.AppendError("could not convert increment value to boolean");551 }552 553 if (!result.Succeeded()) {554 result.AppendError("Missing subcommand");555 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());556 }557 }558};559 560class CommandObjectLogTimer : public CommandObjectMultiword {561public:562 CommandObjectLogTimer(CommandInterpreter &interpreter)563 : CommandObjectMultiword(interpreter, "log timers",564 "Enable, disable, dump, and reset LLDB internal "565 "performance timers.",566 "log timers < enable <depth> | disable | dump | "567 "increment <bool> | reset >") {568 LoadSubCommand("enable", CommandObjectSP(569 new CommandObjectLogTimerEnable(interpreter)));570 LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable(571 interpreter)));572 LoadSubCommand("dump",573 CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));574 LoadSubCommand(575 "reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));576 LoadSubCommand(577 "increment",578 CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));579 }580 581 ~CommandObjectLogTimer() override = default;582};583 584CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)585 : CommandObjectMultiword(interpreter, "log",586 "Commands controlling LLDB internal logging.",587 "log <subcommand> [<command-options>]") {588 LoadSubCommand("enable",589 CommandObjectSP(new CommandObjectLogEnable(interpreter)));590 LoadSubCommand("disable",591 CommandObjectSP(new CommandObjectLogDisable(interpreter)));592 LoadSubCommand("list",593 CommandObjectSP(new CommandObjectLogList(interpreter)));594 LoadSubCommand("dump",595 CommandObjectSP(new CommandObjectLogDump(interpreter)));596 LoadSubCommand("timers",597 CommandObjectSP(new CommandObjectLogTimer(interpreter)));598}599 600CommandObjectLog::~CommandObjectLog() = default;601