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 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 void 157 HandleArgumentCompletion(CompletionRequest &request, 158 OptionElementVector &opt_element_vector) override { 159 CompleteEnableDisable(request); 160 } 161 162 protected: 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 channel 195 char log_file[PATH_MAX]; 196 if (m_options.log_file) 197 m_options.log_file.GetPath(log_file, sizeof(log_file)); 198 else 199 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 else 212 result.SetStatus(eReturnStatusFailed); 213 } 214 215 CommandOptions m_options; 216 }; 217 218 class CommandObjectLogDisable : public CommandObjectParsed { 219 public: 220 // Constructors and Destructors 221 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 the 235 // 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 void 251 HandleArgumentCompletion(CompletionRequest &request, 252 OptionElementVector &opt_element_vector) override { 253 CompleteEnableDisable(request); 254 } 255 256 protected: 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 channel 267 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 281 class CommandObjectLogList : public CommandObjectParsed { 282 public: 283 // Constructors and Destructors 284 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 void 295 HandleArgumentCompletion(CompletionRequest &request, 296 OptionElementVector &opt_element_vector) override { 297 for (llvm::StringRef channel : Log::ListChannels()) 298 request.TryCompleteCurrentArg(channel); 299 } 300 301 protected: 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 }; 319 class CommandObjectLogDump : public CommandObjectParsed { 320 public: 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 void 366 HandleArgumentCompletion(CompletionRequest &request, 367 OptionElementVector &opt_element_vector) override { 368 CompleteEnableDisable(request); 369 } 370 371 protected: 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().GetOutputFile().GetDescriptor(), /*shouldClose=*/false); 398 } 399 400 const std::string channel = std::string(args[0].ref()); 401 std::string error; 402 llvm::raw_string_ostream error_stream(error); 403 if (Log::DumpLogChannel(channel, *stream_up, error_stream)) { 404 result.SetStatus(eReturnStatusSuccessFinishNoResult); 405 } else { 406 result.SetStatus(eReturnStatusFailed); 407 result.GetErrorStream() << error; 408 } 409 } 410 411 CommandOptions m_options; 412 }; 413 414 class CommandObjectLogTimerEnable : public CommandObjectParsed { 415 public: 416 // Constructors and Destructors 417 CommandObjectLogTimerEnable(CommandInterpreter &interpreter) 418 : CommandObjectParsed(interpreter, "log timers enable", 419 "enable LLDB internal performance timers", 420 "log timers enable <depth>") { 421 AddSimpleArgumentList(eArgTypeCount, eArgRepeatOptional); 422 } 423 424 ~CommandObjectLogTimerEnable() override = default; 425 426 protected: 427 void DoExecute(Args &args, CommandReturnObject &result) override { 428 result.SetStatus(eReturnStatusFailed); 429 430 if (args.GetArgumentCount() == 0) { 431 Timer::SetDisplayDepth(UINT32_MAX); 432 result.SetStatus(eReturnStatusSuccessFinishNoResult); 433 } else if (args.GetArgumentCount() == 1) { 434 uint32_t depth; 435 if (args[0].ref().consumeInteger(0, depth)) { 436 result.AppendError( 437 "Could not convert enable depth to an unsigned integer."); 438 } else { 439 Timer::SetDisplayDepth(depth); 440 result.SetStatus(eReturnStatusSuccessFinishNoResult); 441 } 442 } 443 444 if (!result.Succeeded()) { 445 result.AppendError("Missing subcommand"); 446 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); 447 } 448 } 449 }; 450 451 class CommandObjectLogTimerDisable : public CommandObjectParsed { 452 public: 453 // Constructors and Destructors 454 CommandObjectLogTimerDisable(CommandInterpreter &interpreter) 455 : CommandObjectParsed(interpreter, "log timers disable", 456 "disable LLDB internal performance timers", 457 nullptr) {} 458 459 ~CommandObjectLogTimerDisable() override = default; 460 461 protected: 462 void DoExecute(Args &args, CommandReturnObject &result) override { 463 Timer::DumpCategoryTimes(result.GetOutputStream()); 464 Timer::SetDisplayDepth(0); 465 result.SetStatus(eReturnStatusSuccessFinishResult); 466 467 if (!result.Succeeded()) { 468 result.AppendError("Missing subcommand"); 469 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); 470 } 471 } 472 }; 473 474 class CommandObjectLogTimerDump : public CommandObjectParsed { 475 public: 476 // Constructors and Destructors 477 CommandObjectLogTimerDump(CommandInterpreter &interpreter) 478 : CommandObjectParsed(interpreter, "log timers dump", 479 "dump LLDB internal performance timers", nullptr) {} 480 481 ~CommandObjectLogTimerDump() override = default; 482 483 protected: 484 void DoExecute(Args &args, CommandReturnObject &result) override { 485 Timer::DumpCategoryTimes(result.GetOutputStream()); 486 result.SetStatus(eReturnStatusSuccessFinishResult); 487 488 if (!result.Succeeded()) { 489 result.AppendError("Missing subcommand"); 490 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); 491 } 492 } 493 }; 494 495 class CommandObjectLogTimerReset : public CommandObjectParsed { 496 public: 497 // Constructors and Destructors 498 CommandObjectLogTimerReset(CommandInterpreter &interpreter) 499 : CommandObjectParsed(interpreter, "log timers reset", 500 "reset LLDB internal performance timers", nullptr) { 501 } 502 503 ~CommandObjectLogTimerReset() override = default; 504 505 protected: 506 void DoExecute(Args &args, CommandReturnObject &result) override { 507 Timer::ResetCategoryTimes(); 508 result.SetStatus(eReturnStatusSuccessFinishResult); 509 510 if (!result.Succeeded()) { 511 result.AppendError("Missing subcommand"); 512 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); 513 } 514 } 515 }; 516 517 class CommandObjectLogTimerIncrement : public CommandObjectParsed { 518 public: 519 // Constructors and Destructors 520 CommandObjectLogTimerIncrement(CommandInterpreter &interpreter) 521 : CommandObjectParsed(interpreter, "log timers increment", 522 "increment LLDB internal performance timers", 523 "log timers increment <bool>") { 524 AddSimpleArgumentList(eArgTypeBoolean); 525 } 526 527 ~CommandObjectLogTimerIncrement() override = default; 528 529 void 530 HandleArgumentCompletion(CompletionRequest &request, 531 OptionElementVector &opt_element_vector) override { 532 request.TryCompleteCurrentArg("true"); 533 request.TryCompleteCurrentArg("false"); 534 } 535 536 protected: 537 void DoExecute(Args &args, CommandReturnObject &result) override { 538 result.SetStatus(eReturnStatusFailed); 539 540 if (args.GetArgumentCount() == 1) { 541 bool success; 542 bool increment = 543 OptionArgParser::ToBoolean(args[0].ref(), false, &success); 544 545 if (success) { 546 Timer::SetQuiet(!increment); 547 result.SetStatus(eReturnStatusSuccessFinishNoResult); 548 } else 549 result.AppendError("Could not convert increment value to boolean."); 550 } 551 552 if (!result.Succeeded()) { 553 result.AppendError("Missing subcommand"); 554 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); 555 } 556 } 557 }; 558 559 class CommandObjectLogTimer : public CommandObjectMultiword { 560 public: 561 CommandObjectLogTimer(CommandInterpreter &interpreter) 562 : CommandObjectMultiword(interpreter, "log timers", 563 "Enable, disable, dump, and reset LLDB internal " 564 "performance timers.", 565 "log timers < enable <depth> | disable | dump | " 566 "increment <bool> | reset >") { 567 LoadSubCommand("enable", CommandObjectSP( 568 new CommandObjectLogTimerEnable(interpreter))); 569 LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable( 570 interpreter))); 571 LoadSubCommand("dump", 572 CommandObjectSP(new CommandObjectLogTimerDump(interpreter))); 573 LoadSubCommand( 574 "reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter))); 575 LoadSubCommand( 576 "increment", 577 CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter))); 578 } 579 580 ~CommandObjectLogTimer() override = default; 581 }; 582 583 CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter) 584 : CommandObjectMultiword(interpreter, "log", 585 "Commands controlling LLDB internal logging.", 586 "log <subcommand> [<command-options>]") { 587 LoadSubCommand("enable", 588 CommandObjectSP(new CommandObjectLogEnable(interpreter))); 589 LoadSubCommand("disable", 590 CommandObjectSP(new CommandObjectLogDisable(interpreter))); 591 LoadSubCommand("list", 592 CommandObjectSP(new CommandObjectLogList(interpreter))); 593 LoadSubCommand("dump", 594 CommandObjectSP(new CommandObjectLogDump(interpreter))); 595 LoadSubCommand("timers", 596 CommandObjectSP(new CommandObjectLogTimer(interpreter))); 597 } 598 599 CommandObjectLog::~CommandObjectLog() = default; 600