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