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