1 //===-- CommandObjectReproducer.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 "CommandObjectReproducer.h" 10 11 #include "lldb/Host/HostInfo.h" 12 #include "lldb/Host/OptionParser.h" 13 #include "lldb/Interpreter/CommandInterpreter.h" 14 #include "lldb/Interpreter/CommandReturnObject.h" 15 #include "lldb/Interpreter/OptionArgParser.h" 16 #include "lldb/Utility/GDBRemote.h" 17 #include "lldb/Utility/ProcessInfo.h" 18 #include "lldb/Utility/Reproducer.h" 19 20 #include <csignal> 21 22 using namespace lldb; 23 using namespace llvm; 24 using namespace lldb_private; 25 using namespace lldb_private::repro; 26 27 enum ReproducerProvider { 28 eReproducerProviderCommands, 29 eReproducerProviderFiles, 30 eReproducerProviderSymbolFiles, 31 eReproducerProviderGDB, 32 eReproducerProviderProcessInfo, 33 eReproducerProviderVersion, 34 eReproducerProviderWorkingDirectory, 35 eReproducerProviderHomeDirectory, 36 eReproducerProviderNone 37 }; 38 39 static constexpr OptionEnumValueElement g_reproducer_provider_type[] = { 40 { 41 eReproducerProviderCommands, 42 "commands", 43 "Command Interpreter Commands", 44 }, 45 { 46 eReproducerProviderFiles, 47 "files", 48 "Files", 49 }, 50 { 51 eReproducerProviderSymbolFiles, 52 "symbol-files", 53 "Symbol Files", 54 }, 55 { 56 eReproducerProviderGDB, 57 "gdb", 58 "GDB Remote Packets", 59 }, 60 { 61 eReproducerProviderProcessInfo, 62 "processes", 63 "Process Info", 64 }, 65 { 66 eReproducerProviderVersion, 67 "version", 68 "Version", 69 }, 70 { 71 eReproducerProviderWorkingDirectory, 72 "cwd", 73 "Working Directory", 74 }, 75 { 76 eReproducerProviderHomeDirectory, 77 "home", 78 "Home Directory", 79 }, 80 { 81 eReproducerProviderNone, 82 "none", 83 "None", 84 }, 85 }; 86 87 static constexpr OptionEnumValues ReproducerProviderType() { 88 return OptionEnumValues(g_reproducer_provider_type); 89 } 90 91 #define LLDB_OPTIONS_reproducer_dump 92 #include "CommandOptions.inc" 93 94 enum ReproducerCrashSignal { 95 eReproducerCrashSigill, 96 eReproducerCrashSigsegv, 97 }; 98 99 static constexpr OptionEnumValueElement g_reproducer_signaltype[] = { 100 { 101 eReproducerCrashSigill, 102 "SIGILL", 103 "Illegal instruction", 104 }, 105 { 106 eReproducerCrashSigsegv, 107 "SIGSEGV", 108 "Segmentation fault", 109 }, 110 }; 111 112 static constexpr OptionEnumValues ReproducerSignalType() { 113 return OptionEnumValues(g_reproducer_signaltype); 114 } 115 116 #define LLDB_OPTIONS_reproducer_xcrash 117 #include "CommandOptions.inc" 118 119 template <typename T> 120 llvm::Expected<T> static ReadFromYAML(StringRef filename) { 121 auto error_or_file = MemoryBuffer::getFile(filename); 122 if (auto err = error_or_file.getError()) { 123 return errorCodeToError(err); 124 } 125 126 T t; 127 yaml::Input yin((*error_or_file)->getBuffer()); 128 yin >> t; 129 130 if (auto err = yin.error()) { 131 return errorCodeToError(err); 132 } 133 134 return t; 135 } 136 137 class CommandObjectReproducerGenerate : public CommandObjectParsed { 138 public: 139 CommandObjectReproducerGenerate(CommandInterpreter &interpreter) 140 : CommandObjectParsed( 141 interpreter, "reproducer generate", 142 "Generate reproducer on disk. When the debugger is in capture " 143 "mode, this command will output the reproducer to a directory on " 144 "disk and quit. In replay mode this command in a no-op.", 145 nullptr) {} 146 147 ~CommandObjectReproducerGenerate() override = default; 148 149 protected: 150 bool DoExecute(Args &command, CommandReturnObject &result) override { 151 if (!command.empty()) { 152 result.AppendErrorWithFormat("'%s' takes no arguments", 153 m_cmd_name.c_str()); 154 return false; 155 } 156 157 auto &r = Reproducer::Instance(); 158 if (auto generator = r.GetGenerator()) { 159 generator->Keep(); 160 } else if (r.IsReplaying()) { 161 // Make this operation a NO-OP in replay mode. 162 result.SetStatus(eReturnStatusSuccessFinishNoResult); 163 return result.Succeeded(); 164 } else { 165 result.AppendErrorWithFormat("Unable to get the reproducer generator"); 166 result.SetStatus(eReturnStatusFailed); 167 return false; 168 } 169 170 result.GetOutputStream() 171 << "Reproducer written to '" << r.GetReproducerPath() << "'\n"; 172 result.GetOutputStream() 173 << "Please have a look at the directory to assess if you're willing to " 174 "share the contained information.\n"; 175 176 m_interpreter.BroadcastEvent( 177 CommandInterpreter::eBroadcastBitQuitCommandReceived); 178 result.SetStatus(eReturnStatusQuit); 179 return result.Succeeded(); 180 } 181 }; 182 183 class CommandObjectReproducerXCrash : public CommandObjectParsed { 184 public: 185 CommandObjectReproducerXCrash(CommandInterpreter &interpreter) 186 : CommandObjectParsed(interpreter, "reproducer xcrash", 187 "Intentionally force the debugger to crash in " 188 "order to trigger and test reproducer generation.", 189 nullptr) {} 190 191 ~CommandObjectReproducerXCrash() override = default; 192 193 Options *GetOptions() override { return &m_options; } 194 195 class CommandOptions : public Options { 196 public: 197 CommandOptions() : Options() {} 198 199 ~CommandOptions() override = default; 200 201 Status SetOptionValue(uint32_t option_idx, StringRef option_arg, 202 ExecutionContext *execution_context) override { 203 Status error; 204 const int short_option = m_getopt_table[option_idx].val; 205 206 switch (short_option) { 207 case 's': 208 signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum( 209 option_arg, GetDefinitions()[option_idx].enum_values, 0, error); 210 if (!error.Success()) 211 error.SetErrorStringWithFormat("unrecognized value for signal '%s'", 212 option_arg.str().c_str()); 213 break; 214 default: 215 llvm_unreachable("Unimplemented option"); 216 } 217 218 return error; 219 } 220 221 void OptionParsingStarting(ExecutionContext *execution_context) override { 222 signal = eReproducerCrashSigsegv; 223 } 224 225 ArrayRef<OptionDefinition> GetDefinitions() override { 226 return makeArrayRef(g_reproducer_xcrash_options); 227 } 228 229 ReproducerCrashSignal signal = eReproducerCrashSigsegv; 230 }; 231 232 protected: 233 bool DoExecute(Args &command, CommandReturnObject &result) override { 234 if (!command.empty()) { 235 result.AppendErrorWithFormat("'%s' takes no arguments", 236 m_cmd_name.c_str()); 237 return false; 238 } 239 240 auto &r = Reproducer::Instance(); 241 242 if (!r.IsCapturing() && !r.IsReplaying()) { 243 result.SetError( 244 "forcing a crash is only supported when capturing a reproducer."); 245 result.SetStatus(eReturnStatusSuccessFinishNoResult); 246 return false; 247 } 248 249 switch (m_options.signal) { 250 case eReproducerCrashSigill: 251 std::raise(SIGILL); 252 break; 253 case eReproducerCrashSigsegv: 254 std::raise(SIGSEGV); 255 break; 256 } 257 258 result.SetStatus(eReturnStatusQuit); 259 return result.Succeeded(); 260 } 261 262 private: 263 CommandOptions m_options; 264 }; 265 266 class CommandObjectReproducerStatus : public CommandObjectParsed { 267 public: 268 CommandObjectReproducerStatus(CommandInterpreter &interpreter) 269 : CommandObjectParsed( 270 interpreter, "reproducer status", 271 "Show the current reproducer status. In capture mode the " 272 "debugger " 273 "is collecting all the information it needs to create a " 274 "reproducer. In replay mode the reproducer is replaying a " 275 "reproducer. When the reproducers are off, no data is collected " 276 "and no reproducer can be generated.", 277 nullptr) {} 278 279 ~CommandObjectReproducerStatus() override = default; 280 281 protected: 282 bool DoExecute(Args &command, CommandReturnObject &result) override { 283 if (!command.empty()) { 284 result.AppendErrorWithFormat("'%s' takes no arguments", 285 m_cmd_name.c_str()); 286 return false; 287 } 288 289 auto &r = Reproducer::Instance(); 290 if (r.IsCapturing()) { 291 result.GetOutputStream() << "Reproducer is in capture mode.\n"; 292 } else if (r.IsReplaying()) { 293 result.GetOutputStream() << "Reproducer is in replay mode.\n"; 294 } else { 295 result.GetOutputStream() << "Reproducer is off.\n"; 296 } 297 298 if (r.IsCapturing() || r.IsReplaying()) { 299 result.GetOutputStream() 300 << "Path: " << r.GetReproducerPath().GetPath() << '\n'; 301 } 302 303 // Auto generate is hidden unless enabled because this is mostly for 304 // development and testing. 305 if (Generator *g = r.GetGenerator()) { 306 if (g->IsAutoGenerate()) 307 result.GetOutputStream() << "Auto generate: on\n"; 308 } 309 310 result.SetStatus(eReturnStatusSuccessFinishResult); 311 return result.Succeeded(); 312 } 313 }; 314 315 static void SetError(CommandReturnObject &result, Error err) { 316 result.GetErrorStream().Printf("error: %s\n", 317 toString(std::move(err)).c_str()); 318 result.SetStatus(eReturnStatusFailed); 319 } 320 321 class CommandObjectReproducerDump : public CommandObjectParsed { 322 public: 323 CommandObjectReproducerDump(CommandInterpreter &interpreter) 324 : CommandObjectParsed(interpreter, "reproducer dump", 325 "Dump the information contained in a reproducer. " 326 "If no reproducer is specified during replay, it " 327 "dumps the content of the current reproducer.", 328 nullptr) {} 329 330 ~CommandObjectReproducerDump() override = default; 331 332 Options *GetOptions() override { return &m_options; } 333 334 class CommandOptions : public Options { 335 public: 336 CommandOptions() : Options(), file() {} 337 338 ~CommandOptions() override = default; 339 340 Status SetOptionValue(uint32_t option_idx, StringRef option_arg, 341 ExecutionContext *execution_context) override { 342 Status error; 343 const int short_option = m_getopt_table[option_idx].val; 344 345 switch (short_option) { 346 case 'f': 347 file.SetFile(option_arg, FileSpec::Style::native); 348 FileSystem::Instance().Resolve(file); 349 break; 350 case 'p': 351 provider = (ReproducerProvider)OptionArgParser::ToOptionEnum( 352 option_arg, GetDefinitions()[option_idx].enum_values, 0, error); 353 if (!error.Success()) 354 error.SetErrorStringWithFormat("unrecognized value for provider '%s'", 355 option_arg.str().c_str()); 356 break; 357 default: 358 llvm_unreachable("Unimplemented option"); 359 } 360 361 return error; 362 } 363 364 void OptionParsingStarting(ExecutionContext *execution_context) override { 365 file.Clear(); 366 provider = eReproducerProviderNone; 367 } 368 369 ArrayRef<OptionDefinition> GetDefinitions() override { 370 return makeArrayRef(g_reproducer_dump_options); 371 } 372 373 FileSpec file; 374 ReproducerProvider provider = eReproducerProviderNone; 375 }; 376 377 protected: 378 bool DoExecute(Args &command, CommandReturnObject &result) override { 379 if (!command.empty()) { 380 result.AppendErrorWithFormat("'%s' takes no arguments", 381 m_cmd_name.c_str()); 382 return false; 383 } 384 385 // If no reproducer path is specified, use the loader currently used for 386 // replay. Otherwise create a new loader just for dumping. 387 llvm::Optional<Loader> loader_storage; 388 Loader *loader = nullptr; 389 if (!m_options.file) { 390 loader = Reproducer::Instance().GetLoader(); 391 if (loader == nullptr) { 392 result.SetError( 393 "Not specifying a reproducer is only support during replay."); 394 result.SetStatus(eReturnStatusSuccessFinishNoResult); 395 return false; 396 } 397 } else { 398 loader_storage.emplace(m_options.file); 399 loader = &(*loader_storage); 400 if (Error err = loader->LoadIndex()) { 401 SetError(result, std::move(err)); 402 return false; 403 } 404 } 405 406 // If we get here we should have a valid loader. 407 assert(loader); 408 409 switch (m_options.provider) { 410 case eReproducerProviderFiles: { 411 FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>(); 412 413 // Read the VFS mapping. 414 ErrorOr<std::unique_ptr<MemoryBuffer>> buffer = 415 vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath()); 416 if (!buffer) { 417 SetError(result, errorCodeToError(buffer.getError())); 418 return false; 419 } 420 421 // Initialize a VFS from the given mapping. 422 IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML( 423 std::move(buffer.get()), nullptr, vfs_mapping.GetPath()); 424 425 // Dump the VFS to a buffer. 426 std::string str; 427 raw_string_ostream os(str); 428 static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os); 429 os.flush(); 430 431 // Return the string. 432 result.AppendMessage(str); 433 result.SetStatus(eReturnStatusSuccessFinishResult); 434 return true; 435 } 436 case eReproducerProviderSymbolFiles: { 437 Expected<std::string> symbol_files = 438 loader->LoadBuffer<SymbolFileProvider>(); 439 if (!symbol_files) { 440 SetError(result, symbol_files.takeError()); 441 return false; 442 } 443 444 std::vector<SymbolFileProvider::Entry> entries; 445 llvm::yaml::Input yin(*symbol_files); 446 yin >> entries; 447 448 for (const auto &entry : entries) { 449 result.AppendMessageWithFormat("- uuid: %s\n", 450 entry.uuid.c_str()); 451 result.AppendMessageWithFormat(" module path: %s\n", 452 entry.module_path.c_str()); 453 result.AppendMessageWithFormat(" symbol path: %s\n", 454 entry.symbol_path.c_str()); 455 } 456 result.SetStatus(eReturnStatusSuccessFinishResult); 457 return true; 458 } 459 case eReproducerProviderVersion: { 460 Expected<std::string> version = loader->LoadBuffer<VersionProvider>(); 461 if (!version) { 462 SetError(result, version.takeError()); 463 return false; 464 } 465 result.AppendMessage(*version); 466 result.SetStatus(eReturnStatusSuccessFinishResult); 467 return true; 468 } 469 case eReproducerProviderWorkingDirectory: { 470 Expected<std::string> cwd = 471 repro::GetDirectoryFrom<WorkingDirectoryProvider>(loader); 472 if (!cwd) { 473 SetError(result, cwd.takeError()); 474 return false; 475 } 476 result.AppendMessage(*cwd); 477 result.SetStatus(eReturnStatusSuccessFinishResult); 478 return true; 479 } 480 case eReproducerProviderHomeDirectory: { 481 Expected<std::string> home = 482 repro::GetDirectoryFrom<HomeDirectoryProvider>(loader); 483 if (!home) { 484 SetError(result, home.takeError()); 485 return false; 486 } 487 result.AppendMessage(*home); 488 result.SetStatus(eReturnStatusSuccessFinishResult); 489 return true; 490 } 491 case eReproducerProviderCommands: { 492 std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader = 493 repro::MultiLoader<repro::CommandProvider>::Create(loader); 494 if (!multi_loader) { 495 SetError(result, 496 make_error<StringError>("Unable to create command loader.", 497 llvm::inconvertibleErrorCode())); 498 return false; 499 } 500 501 // Iterate over the command files and dump them. 502 llvm::Optional<std::string> command_file; 503 while ((command_file = multi_loader->GetNextFile())) { 504 if (!command_file) 505 break; 506 507 auto command_buffer = llvm::MemoryBuffer::getFile(*command_file); 508 if (auto err = command_buffer.getError()) { 509 SetError(result, errorCodeToError(err)); 510 return false; 511 } 512 result.AppendMessage((*command_buffer)->getBuffer()); 513 } 514 515 result.SetStatus(eReturnStatusSuccessFinishResult); 516 return true; 517 } 518 case eReproducerProviderGDB: { 519 std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>> 520 multi_loader = 521 repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader); 522 523 if (!multi_loader) { 524 SetError(result, 525 make_error<StringError>("Unable to create GDB loader.", 526 llvm::inconvertibleErrorCode())); 527 return false; 528 } 529 530 llvm::Optional<std::string> gdb_file; 531 while ((gdb_file = multi_loader->GetNextFile())) { 532 if (llvm::Expected<std::vector<GDBRemotePacket>> packets = 533 ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) { 534 for (GDBRemotePacket &packet : *packets) { 535 packet.Dump(result.GetOutputStream()); 536 } 537 } else { 538 SetError(result, packets.takeError()); 539 return false; 540 } 541 } 542 543 result.SetStatus(eReturnStatusSuccessFinishResult); 544 return true; 545 } 546 case eReproducerProviderProcessInfo: { 547 std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>> 548 multi_loader = 549 repro::MultiLoader<repro::ProcessInfoProvider>::Create(loader); 550 551 if (!multi_loader) { 552 SetError(result, make_error<StringError>( 553 llvm::inconvertibleErrorCode(), 554 "Unable to create process info loader.")); 555 return false; 556 } 557 558 llvm::Optional<std::string> process_file; 559 while ((process_file = multi_loader->GetNextFile())) { 560 if (llvm::Expected<ProcessInstanceInfoList> infos = 561 ReadFromYAML<ProcessInstanceInfoList>(*process_file)) { 562 for (ProcessInstanceInfo info : *infos) 563 info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver()); 564 } else { 565 SetError(result, infos.takeError()); 566 return false; 567 } 568 } 569 570 result.SetStatus(eReturnStatusSuccessFinishResult); 571 return true; 572 } 573 case eReproducerProviderNone: 574 result.SetError("No valid provider specified."); 575 return false; 576 } 577 578 result.SetStatus(eReturnStatusSuccessFinishNoResult); 579 return result.Succeeded(); 580 } 581 582 private: 583 CommandOptions m_options; 584 }; 585 586 CommandObjectReproducer::CommandObjectReproducer( 587 CommandInterpreter &interpreter) 588 : CommandObjectMultiword( 589 interpreter, "reproducer", 590 "Commands for manipulating reproducers. Reproducers make it " 591 "possible " 592 "to capture full debug sessions with all its dependencies. The " 593 "resulting reproducer is used to replay the debug session while " 594 "debugging the debugger.\n" 595 "Because reproducers need the whole the debug session from " 596 "beginning to end, you need to launch the debugger in capture or " 597 "replay mode, commonly though the command line driver.\n" 598 "Reproducers are unrelated record-replay debugging, as you cannot " 599 "interact with the debugger during replay.\n", 600 "reproducer <subcommand> [<subcommand-options>]") { 601 LoadSubCommand( 602 "generate", 603 CommandObjectSP(new CommandObjectReproducerGenerate(interpreter))); 604 LoadSubCommand("status", CommandObjectSP( 605 new CommandObjectReproducerStatus(interpreter))); 606 LoadSubCommand("dump", 607 CommandObjectSP(new CommandObjectReproducerDump(interpreter))); 608 LoadSubCommand("xcrash", CommandObjectSP( 609 new CommandObjectReproducerXCrash(interpreter))); 610 } 611 612 CommandObjectReproducer::~CommandObjectReproducer() = default; 613