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 
ReproducerProviderType()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 
ReproducerSignalType()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 #define LLDB_OPTIONS_reproducer_verify
120 #include "CommandOptions.inc"
121 
122 template <typename T>
ReadFromYAML(StringRef filename)123 llvm::Expected<T> static ReadFromYAML(StringRef filename) {
124   auto error_or_file = MemoryBuffer::getFile(filename);
125   if (auto err = error_or_file.getError()) {
126     return errorCodeToError(err);
127   }
128 
129   T t;
130   yaml::Input yin((*error_or_file)->getBuffer());
131   yin >> t;
132 
133   if (auto err = yin.error()) {
134     return errorCodeToError(err);
135   }
136 
137   return t;
138 }
139 
SetError(CommandReturnObject & result,Error err)140 static void SetError(CommandReturnObject &result, Error err) {
141   result.GetErrorStream().Printf("error: %s\n",
142                                  toString(std::move(err)).c_str());
143   result.SetStatus(eReturnStatusFailed);
144 }
145 
146 /// Create a loader from the given path if specified. Otherwise use the current
147 /// loader used for replay.
148 static Loader *
GetLoaderFromPathOrCurrent(llvm::Optional<Loader> & loader_storage,CommandReturnObject & result,FileSpec reproducer_path)149 GetLoaderFromPathOrCurrent(llvm::Optional<Loader> &loader_storage,
150                            CommandReturnObject &result,
151                            FileSpec reproducer_path) {
152   if (reproducer_path) {
153     loader_storage.emplace(reproducer_path);
154     Loader *loader = &(*loader_storage);
155     if (Error err = loader->LoadIndex()) {
156       // This is a hard error and will set the result to eReturnStatusFailed.
157       SetError(result, std::move(err));
158       return nullptr;
159     }
160     return loader;
161   }
162 
163   if (Loader *loader = Reproducer::Instance().GetLoader())
164     return loader;
165 
166   // This is a soft error because this is expected to fail during capture.
167   result.SetError("Not specifying a reproducer is only support during replay.");
168   result.SetStatus(eReturnStatusSuccessFinishNoResult);
169   return nullptr;
170 }
171 
172 class CommandObjectReproducerGenerate : public CommandObjectParsed {
173 public:
CommandObjectReproducerGenerate(CommandInterpreter & interpreter)174   CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
175       : CommandObjectParsed(
176             interpreter, "reproducer generate",
177             "Generate reproducer on disk. When the debugger is in capture "
178             "mode, this command will output the reproducer to a directory on "
179             "disk and quit. In replay mode this command in a no-op.",
180             nullptr) {}
181 
182   ~CommandObjectReproducerGenerate() override = default;
183 
184 protected:
DoExecute(Args & command,CommandReturnObject & result)185   bool DoExecute(Args &command, CommandReturnObject &result) override {
186     if (!command.empty()) {
187       result.AppendErrorWithFormat("'%s' takes no arguments",
188                                    m_cmd_name.c_str());
189       return false;
190     }
191 
192     auto &r = Reproducer::Instance();
193     if (auto generator = r.GetGenerator()) {
194       generator->Keep();
195       if (llvm::Error e = repro::Finalize(r.GetReproducerPath())) {
196         SetError(result, std::move(e));
197         return result.Succeeded();
198       }
199     } else if (r.IsReplaying()) {
200       // Make this operation a NO-OP in replay mode.
201       result.SetStatus(eReturnStatusSuccessFinishNoResult);
202       return result.Succeeded();
203     } else {
204       result.AppendErrorWithFormat("Unable to get the reproducer generator");
205       result.SetStatus(eReturnStatusFailed);
206       return false;
207     }
208 
209     result.GetOutputStream()
210         << "Reproducer written to '" << r.GetReproducerPath() << "'\n";
211     result.GetOutputStream()
212         << "Please have a look at the directory to assess if you're willing to "
213            "share the contained information.\n";
214 
215     m_interpreter.BroadcastEvent(
216         CommandInterpreter::eBroadcastBitQuitCommandReceived);
217     result.SetStatus(eReturnStatusQuit);
218     return result.Succeeded();
219   }
220 };
221 
222 class CommandObjectReproducerXCrash : public CommandObjectParsed {
223 public:
CommandObjectReproducerXCrash(CommandInterpreter & interpreter)224   CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
225       : CommandObjectParsed(interpreter, "reproducer xcrash",
226                             "Intentionally force  the debugger to crash in "
227                             "order to trigger and test reproducer generation.",
228                             nullptr) {}
229 
230   ~CommandObjectReproducerXCrash() override = default;
231 
GetOptions()232   Options *GetOptions() override { return &m_options; }
233 
234   class CommandOptions : public Options {
235   public:
CommandOptions()236     CommandOptions() : Options() {}
237 
238     ~CommandOptions() override = default;
239 
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)240     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
241                           ExecutionContext *execution_context) override {
242       Status error;
243       const int short_option = m_getopt_table[option_idx].val;
244 
245       switch (short_option) {
246       case 's':
247         signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
248             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
249         if (!error.Success())
250           error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
251                                          option_arg.str().c_str());
252         break;
253       default:
254         llvm_unreachable("Unimplemented option");
255       }
256 
257       return error;
258     }
259 
OptionParsingStarting(ExecutionContext * execution_context)260     void OptionParsingStarting(ExecutionContext *execution_context) override {
261       signal = eReproducerCrashSigsegv;
262     }
263 
GetDefinitions()264     ArrayRef<OptionDefinition> GetDefinitions() override {
265       return makeArrayRef(g_reproducer_xcrash_options);
266     }
267 
268     ReproducerCrashSignal signal = eReproducerCrashSigsegv;
269   };
270 
271 protected:
DoExecute(Args & command,CommandReturnObject & result)272   bool DoExecute(Args &command, CommandReturnObject &result) override {
273     if (!command.empty()) {
274       result.AppendErrorWithFormat("'%s' takes no arguments",
275                                    m_cmd_name.c_str());
276       return false;
277     }
278 
279     auto &r = Reproducer::Instance();
280 
281     if (!r.IsCapturing() && !r.IsReplaying()) {
282       result.SetError(
283           "forcing a crash is only supported when capturing a reproducer.");
284       result.SetStatus(eReturnStatusSuccessFinishNoResult);
285       return false;
286     }
287 
288     switch (m_options.signal) {
289     case eReproducerCrashSigill:
290       std::raise(SIGILL);
291       break;
292     case eReproducerCrashSigsegv:
293       std::raise(SIGSEGV);
294       break;
295     }
296 
297     result.SetStatus(eReturnStatusQuit);
298     return result.Succeeded();
299   }
300 
301 private:
302   CommandOptions m_options;
303 };
304 
305 class CommandObjectReproducerStatus : public CommandObjectParsed {
306 public:
CommandObjectReproducerStatus(CommandInterpreter & interpreter)307   CommandObjectReproducerStatus(CommandInterpreter &interpreter)
308       : CommandObjectParsed(
309             interpreter, "reproducer status",
310             "Show the current reproducer status. In capture mode the "
311             "debugger "
312             "is collecting all the information it needs to create a "
313             "reproducer.  In replay mode the reproducer is replaying a "
314             "reproducer. When the reproducers are off, no data is collected "
315             "and no reproducer can be generated.",
316             nullptr) {}
317 
318   ~CommandObjectReproducerStatus() override = default;
319 
320 protected:
DoExecute(Args & command,CommandReturnObject & result)321   bool DoExecute(Args &command, CommandReturnObject &result) override {
322     if (!command.empty()) {
323       result.AppendErrorWithFormat("'%s' takes no arguments",
324                                    m_cmd_name.c_str());
325       return false;
326     }
327 
328     auto &r = Reproducer::Instance();
329     if (r.IsCapturing()) {
330       result.GetOutputStream() << "Reproducer is in capture mode.\n";
331     } else if (r.IsReplaying()) {
332       result.GetOutputStream() << "Reproducer is in replay mode.\n";
333     } else {
334       result.GetOutputStream() << "Reproducer is off.\n";
335     }
336 
337     if (r.IsCapturing() || r.IsReplaying()) {
338       result.GetOutputStream()
339           << "Path: " << r.GetReproducerPath().GetPath() << '\n';
340     }
341 
342     // Auto generate is hidden unless enabled because this is mostly for
343     // development and testing.
344     if (Generator *g = r.GetGenerator()) {
345       if (g->IsAutoGenerate())
346         result.GetOutputStream() << "Auto generate: on\n";
347     }
348 
349     result.SetStatus(eReturnStatusSuccessFinishResult);
350     return result.Succeeded();
351   }
352 };
353 
354 class CommandObjectReproducerDump : public CommandObjectParsed {
355 public:
CommandObjectReproducerDump(CommandInterpreter & interpreter)356   CommandObjectReproducerDump(CommandInterpreter &interpreter)
357       : CommandObjectParsed(interpreter, "reproducer dump",
358                             "Dump the information contained in a reproducer. "
359                             "If no reproducer is specified during replay, it "
360                             "dumps the content of the current reproducer.",
361                             nullptr) {}
362 
363   ~CommandObjectReproducerDump() override = default;
364 
GetOptions()365   Options *GetOptions() override { return &m_options; }
366 
367   class CommandOptions : public Options {
368   public:
CommandOptions()369     CommandOptions() : Options(), file() {}
370 
371     ~CommandOptions() override = default;
372 
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)373     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
374                           ExecutionContext *execution_context) override {
375       Status error;
376       const int short_option = m_getopt_table[option_idx].val;
377 
378       switch (short_option) {
379       case 'f':
380         file.SetFile(option_arg, FileSpec::Style::native);
381         FileSystem::Instance().Resolve(file);
382         break;
383       case 'p':
384         provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
385             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
386         if (!error.Success())
387           error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
388                                          option_arg.str().c_str());
389         break;
390       default:
391         llvm_unreachable("Unimplemented option");
392       }
393 
394       return error;
395     }
396 
OptionParsingStarting(ExecutionContext * execution_context)397     void OptionParsingStarting(ExecutionContext *execution_context) override {
398       file.Clear();
399       provider = eReproducerProviderNone;
400     }
401 
GetDefinitions()402     ArrayRef<OptionDefinition> GetDefinitions() override {
403       return makeArrayRef(g_reproducer_dump_options);
404     }
405 
406     FileSpec file;
407     ReproducerProvider provider = eReproducerProviderNone;
408   };
409 
410 protected:
DoExecute(Args & command,CommandReturnObject & result)411   bool DoExecute(Args &command, CommandReturnObject &result) override {
412     if (!command.empty()) {
413       result.AppendErrorWithFormat("'%s' takes no arguments",
414                                    m_cmd_name.c_str());
415       return false;
416     }
417 
418     llvm::Optional<Loader> loader_storage;
419     Loader *loader =
420         GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
421     if (!loader)
422       return false;
423 
424     switch (m_options.provider) {
425     case eReproducerProviderFiles: {
426       FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
427 
428       // Read the VFS mapping.
429       ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
430           vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
431       if (!buffer) {
432         SetError(result, errorCodeToError(buffer.getError()));
433         return false;
434       }
435 
436       // Initialize a VFS from the given mapping.
437       IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
438           std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
439 
440       // Dump the VFS to a buffer.
441       std::string str;
442       raw_string_ostream os(str);
443       static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
444       os.flush();
445 
446       // Return the string.
447       result.AppendMessage(str);
448       result.SetStatus(eReturnStatusSuccessFinishResult);
449       return true;
450     }
451     case eReproducerProviderSymbolFiles: {
452       Expected<std::string> symbol_files =
453           loader->LoadBuffer<SymbolFileProvider>();
454       if (!symbol_files) {
455         SetError(result, symbol_files.takeError());
456         return false;
457       }
458 
459       std::vector<SymbolFileProvider::Entry> entries;
460       llvm::yaml::Input yin(*symbol_files);
461       yin >> entries;
462 
463       for (const auto &entry : entries) {
464         result.AppendMessageWithFormat("- uuid:        %s\n",
465                                        entry.uuid.c_str());
466         result.AppendMessageWithFormat("  module path: %s\n",
467                                        entry.module_path.c_str());
468         result.AppendMessageWithFormat("  symbol path: %s\n",
469                                        entry.symbol_path.c_str());
470       }
471       result.SetStatus(eReturnStatusSuccessFinishResult);
472       return true;
473     }
474     case eReproducerProviderVersion: {
475       Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
476       if (!version) {
477         SetError(result, version.takeError());
478         return false;
479       }
480       result.AppendMessage(*version);
481       result.SetStatus(eReturnStatusSuccessFinishResult);
482       return true;
483     }
484     case eReproducerProviderWorkingDirectory: {
485       Expected<std::string> cwd =
486           repro::GetDirectoryFrom<WorkingDirectoryProvider>(loader);
487       if (!cwd) {
488         SetError(result, cwd.takeError());
489         return false;
490       }
491       result.AppendMessage(*cwd);
492       result.SetStatus(eReturnStatusSuccessFinishResult);
493       return true;
494     }
495     case eReproducerProviderHomeDirectory: {
496       Expected<std::string> home =
497           repro::GetDirectoryFrom<HomeDirectoryProvider>(loader);
498       if (!home) {
499         SetError(result, home.takeError());
500         return false;
501       }
502       result.AppendMessage(*home);
503       result.SetStatus(eReturnStatusSuccessFinishResult);
504       return true;
505     }
506     case eReproducerProviderCommands: {
507       std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
508           repro::MultiLoader<repro::CommandProvider>::Create(loader);
509       if (!multi_loader) {
510         SetError(result,
511                  make_error<StringError>("Unable to create command loader.",
512                                          llvm::inconvertibleErrorCode()));
513         return false;
514       }
515 
516       // Iterate over the command files and dump them.
517       llvm::Optional<std::string> command_file;
518       while ((command_file = multi_loader->GetNextFile())) {
519         if (!command_file)
520           break;
521 
522         auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
523         if (auto err = command_buffer.getError()) {
524           SetError(result, errorCodeToError(err));
525           return false;
526         }
527         result.AppendMessage((*command_buffer)->getBuffer());
528       }
529 
530       result.SetStatus(eReturnStatusSuccessFinishResult);
531       return true;
532     }
533     case eReproducerProviderGDB: {
534       std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
535           multi_loader =
536               repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader);
537 
538       if (!multi_loader) {
539         SetError(result,
540                  make_error<StringError>("Unable to create GDB loader.",
541                                          llvm::inconvertibleErrorCode()));
542         return false;
543       }
544 
545       llvm::Optional<std::string> gdb_file;
546       while ((gdb_file = multi_loader->GetNextFile())) {
547         if (llvm::Expected<std::vector<GDBRemotePacket>> packets =
548                 ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) {
549           for (GDBRemotePacket &packet : *packets) {
550             packet.Dump(result.GetOutputStream());
551           }
552         } else {
553           SetError(result, packets.takeError());
554           return false;
555         }
556       }
557 
558       result.SetStatus(eReturnStatusSuccessFinishResult);
559       return true;
560     }
561     case eReproducerProviderProcessInfo: {
562       std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
563           multi_loader =
564               repro::MultiLoader<repro::ProcessInfoProvider>::Create(loader);
565 
566       if (!multi_loader) {
567         SetError(result, make_error<StringError>(
568                              llvm::inconvertibleErrorCode(),
569                              "Unable to create process info loader."));
570         return false;
571       }
572 
573       llvm::Optional<std::string> process_file;
574       while ((process_file = multi_loader->GetNextFile())) {
575         if (llvm::Expected<ProcessInstanceInfoList> infos =
576                 ReadFromYAML<ProcessInstanceInfoList>(*process_file)) {
577           for (ProcessInstanceInfo info : *infos)
578             info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver());
579         } else {
580           SetError(result, infos.takeError());
581           return false;
582         }
583       }
584 
585       result.SetStatus(eReturnStatusSuccessFinishResult);
586       return true;
587     }
588     case eReproducerProviderNone:
589       result.SetError("No valid provider specified.");
590       return false;
591     }
592 
593     result.SetStatus(eReturnStatusSuccessFinishNoResult);
594     return result.Succeeded();
595   }
596 
597 private:
598   CommandOptions m_options;
599 };
600 
601 class CommandObjectReproducerVerify : public CommandObjectParsed {
602 public:
CommandObjectReproducerVerify(CommandInterpreter & interpreter)603   CommandObjectReproducerVerify(CommandInterpreter &interpreter)
604       : CommandObjectParsed(interpreter, "reproducer verify",
605                             "Verify the contents of a reproducer. "
606                             "If no reproducer is specified during replay, it "
607                             "verifies the content of the current reproducer.",
608                             nullptr) {}
609 
610   ~CommandObjectReproducerVerify() override = default;
611 
GetOptions()612   Options *GetOptions() override { return &m_options; }
613 
614   class CommandOptions : public Options {
615   public:
CommandOptions()616     CommandOptions() : Options(), file() {}
617 
618     ~CommandOptions() override = default;
619 
SetOptionValue(uint32_t option_idx,StringRef option_arg,ExecutionContext * execution_context)620     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
621                           ExecutionContext *execution_context) override {
622       Status error;
623       const int short_option = m_getopt_table[option_idx].val;
624 
625       switch (short_option) {
626       case 'f':
627         file.SetFile(option_arg, FileSpec::Style::native);
628         FileSystem::Instance().Resolve(file);
629         break;
630       default:
631         llvm_unreachable("Unimplemented option");
632       }
633 
634       return error;
635     }
636 
OptionParsingStarting(ExecutionContext * execution_context)637     void OptionParsingStarting(ExecutionContext *execution_context) override {
638       file.Clear();
639     }
640 
GetDefinitions()641     ArrayRef<OptionDefinition> GetDefinitions() override {
642       return makeArrayRef(g_reproducer_verify_options);
643     }
644 
645     FileSpec file;
646   };
647 
648 protected:
DoExecute(Args & command,CommandReturnObject & result)649   bool DoExecute(Args &command, CommandReturnObject &result) override {
650     if (!command.empty()) {
651       result.AppendErrorWithFormat("'%s' takes no arguments",
652                                    m_cmd_name.c_str());
653       return false;
654     }
655 
656     llvm::Optional<Loader> loader_storage;
657     Loader *loader =
658         GetLoaderFromPathOrCurrent(loader_storage, result, m_options.file);
659     if (!loader)
660       return false;
661 
662     bool errors = false;
663     auto error_callback = [&](llvm::StringRef error) {
664       errors = true;
665       result.AppendError(error);
666     };
667 
668     bool warnings = false;
669     auto warning_callback = [&](llvm::StringRef warning) {
670       warnings = true;
671       result.AppendWarning(warning);
672     };
673 
674     auto note_callback = [&](llvm::StringRef warning) {
675       result.AppendMessage(warning);
676     };
677 
678     Verifier verifier(loader);
679     verifier.Verify(error_callback, warning_callback, note_callback);
680 
681     if (warnings || errors) {
682       result.AppendMessage("reproducer verification failed");
683       result.SetStatus(eReturnStatusFailed);
684     } else {
685       result.AppendMessage("reproducer verification succeeded");
686       result.SetStatus(eReturnStatusSuccessFinishResult);
687     }
688 
689     return result.Succeeded();
690   }
691 
692 private:
693   CommandOptions m_options;
694 };
695 
CommandObjectReproducer(CommandInterpreter & interpreter)696 CommandObjectReproducer::CommandObjectReproducer(
697     CommandInterpreter &interpreter)
698     : CommandObjectMultiword(
699           interpreter, "reproducer",
700           "Commands for manipulating reproducers. Reproducers make it "
701           "possible "
702           "to capture full debug sessions with all its dependencies. The "
703           "resulting reproducer is used to replay the debug session while "
704           "debugging the debugger.\n"
705           "Because reproducers need the whole the debug session from "
706           "beginning to end, you need to launch the debugger in capture or "
707           "replay mode, commonly though the command line driver.\n"
708           "Reproducers are unrelated record-replay debugging, as you cannot "
709           "interact with the debugger during replay.\n",
710           "reproducer <subcommand> [<subcommand-options>]") {
711   LoadSubCommand(
712       "generate",
713       CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
714   LoadSubCommand("status", CommandObjectSP(
715                                new CommandObjectReproducerStatus(interpreter)));
716   LoadSubCommand("dump",
717                  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
718   LoadSubCommand("verify", CommandObjectSP(
719                                new CommandObjectReproducerVerify(interpreter)));
720   LoadSubCommand("xcrash", CommandObjectSP(
721                                new CommandObjectReproducerXCrash(interpreter)));
722 }
723 
724 CommandObjectReproducer::~CommandObjectReproducer() = default;
725