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