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