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