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