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