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