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