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