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