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/OptionParser.h"
12 #include "lldb/Utility/GDBRemote.h"
13 #include "lldb/Utility/Reproducer.h"
14 
15 #include "lldb/Interpreter/CommandInterpreter.h"
16 #include "lldb/Interpreter/CommandReturnObject.h"
17 #include "lldb/Interpreter/OptionArgParser.h"
18 
19 #include <csignal>
20 
21 using namespace lldb;
22 using namespace llvm;
23 using namespace lldb_private;
24 using namespace lldb_private::repro;
25 
26 enum ReproducerProvider {
27   eReproducerProviderCommands,
28   eReproducerProviderFiles,
29   eReproducerProviderGDB,
30   eReproducerProviderVersion,
31   eReproducerProviderWorkingDirectory,
32   eReproducerProviderNone
33 };
34 
35 static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
36     {
37         eReproducerProviderCommands,
38         "commands",
39         "Command Interpreter Commands",
40     },
41     {
42         eReproducerProviderFiles,
43         "files",
44         "Files",
45     },
46     {
47         eReproducerProviderGDB,
48         "gdb",
49         "GDB Remote Packets",
50     },
51     {
52         eReproducerProviderVersion,
53         "version",
54         "Version",
55     },
56     {
57         eReproducerProviderWorkingDirectory,
58         "cwd",
59         "Working Directory",
60     },
61     {
62         eReproducerProviderNone,
63         "none",
64         "None",
65     },
66 };
67 
68 static constexpr OptionEnumValues ReproducerProviderType() {
69   return OptionEnumValues(g_reproducer_provider_type);
70 }
71 
72 #define LLDB_OPTIONS_reproducer_dump
73 #include "CommandOptions.inc"
74 
75 enum ReproducerCrashSignal {
76   eReproducerCrashSigill,
77   eReproducerCrashSigsegv,
78 };
79 
80 static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
81     {
82         eReproducerCrashSigill,
83         "SIGILL",
84         "Illegal instruction",
85     },
86     {
87         eReproducerCrashSigsegv,
88         "SIGSEGV",
89         "Segmentation fault",
90     },
91 };
92 
93 static constexpr OptionEnumValues ReproducerSignalType() {
94   return OptionEnumValues(g_reproducer_signaltype);
95 }
96 
97 #define LLDB_OPTIONS_reproducer_xcrash
98 #include "CommandOptions.inc"
99 
100 class CommandObjectReproducerGenerate : public CommandObjectParsed {
101 public:
102   CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
103       : CommandObjectParsed(
104             interpreter, "reproducer generate",
105             "Generate reproducer on disk. When the debugger is in capture "
106             "mode, this command will output the reproducer to a directory on "
107             "disk and quit. In replay mode this command in a no-op.",
108             nullptr) {}
109 
110   ~CommandObjectReproducerGenerate() override = default;
111 
112 protected:
113   bool DoExecute(Args &command, CommandReturnObject &result) override {
114     if (!command.empty()) {
115       result.AppendErrorWithFormat("'%s' takes no arguments",
116                                    m_cmd_name.c_str());
117       return false;
118     }
119 
120     auto &r = Reproducer::Instance();
121     if (auto generator = r.GetGenerator()) {
122       generator->Keep();
123     } else if (r.IsReplaying()) {
124       // Make this operation a NO-OP in replay mode.
125       result.SetStatus(eReturnStatusSuccessFinishNoResult);
126       return result.Succeeded();
127     } else {
128       result.AppendErrorWithFormat("Unable to get the reproducer generator");
129       result.SetStatus(eReturnStatusFailed);
130       return false;
131     }
132 
133     result.GetOutputStream()
134         << "Reproducer written to '" << r.GetReproducerPath() << "'\n";
135     result.GetOutputStream()
136         << "Please have a look at the directory to assess if you're willing to "
137            "share the contained information.\n";
138 
139     m_interpreter.BroadcastEvent(
140         CommandInterpreter::eBroadcastBitQuitCommandReceived);
141     result.SetStatus(eReturnStatusQuit);
142     return result.Succeeded();
143   }
144 };
145 
146 class CommandObjectReproducerXCrash : public CommandObjectParsed {
147 public:
148   CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
149       : CommandObjectParsed(interpreter, "reproducer xcrash",
150                             "Intentionally force  the debugger to crash in "
151                             "order to trigger and test reproducer generation.",
152                             nullptr) {}
153 
154   ~CommandObjectReproducerXCrash() override = default;
155 
156   Options *GetOptions() override { return &m_options; }
157 
158   class CommandOptions : public Options {
159   public:
160     CommandOptions() : Options() {}
161 
162     ~CommandOptions() override = default;
163 
164     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
165                           ExecutionContext *execution_context) override {
166       Status error;
167       const int short_option = m_getopt_table[option_idx].val;
168 
169       switch (short_option) {
170       case 's':
171         signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
172             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
173         if (!error.Success())
174           error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
175                                          option_arg.str().c_str());
176         break;
177       default:
178         llvm_unreachable("Unimplemented option");
179       }
180 
181       return error;
182     }
183 
184     void OptionParsingStarting(ExecutionContext *execution_context) override {
185       signal = eReproducerCrashSigsegv;
186     }
187 
188     ArrayRef<OptionDefinition> GetDefinitions() override {
189       return makeArrayRef(g_reproducer_xcrash_options);
190     }
191 
192     ReproducerCrashSignal signal = eReproducerCrashSigsegv;
193   };
194 
195 protected:
196   bool DoExecute(Args &command, CommandReturnObject &result) override {
197     if (!command.empty()) {
198       result.AppendErrorWithFormat("'%s' takes no arguments",
199                                    m_cmd_name.c_str());
200       return false;
201     }
202 
203     auto &r = Reproducer::Instance();
204 
205     if (!r.IsCapturing() && !r.IsReplaying()) {
206       result.SetError(
207           "forcing a crash is only supported when capturing a reproducer.");
208       result.SetStatus(eReturnStatusSuccessFinishNoResult);
209       return false;
210     }
211 
212     switch (m_options.signal) {
213     case eReproducerCrashSigill:
214       std::raise(SIGILL);
215       break;
216     case eReproducerCrashSigsegv:
217       std::raise(SIGSEGV);
218       break;
219     }
220 
221     result.SetStatus(eReturnStatusQuit);
222     return result.Succeeded();
223   }
224 
225 private:
226   CommandOptions m_options;
227 };
228 
229 class CommandObjectReproducerStatus : public CommandObjectParsed {
230 public:
231   CommandObjectReproducerStatus(CommandInterpreter &interpreter)
232       : CommandObjectParsed(
233             interpreter, "reproducer status",
234             "Show the current reproducer status. In capture mode the "
235             "debugger "
236             "is collecting all the information it needs to create a "
237             "reproducer.  In replay mode the reproducer is replaying a "
238             "reproducer. When the reproducers are off, no data is collected "
239             "and no reproducer can be generated.",
240             nullptr) {}
241 
242   ~CommandObjectReproducerStatus() override = default;
243 
244 protected:
245   bool DoExecute(Args &command, CommandReturnObject &result) override {
246     if (!command.empty()) {
247       result.AppendErrorWithFormat("'%s' takes no arguments",
248                                    m_cmd_name.c_str());
249       return false;
250     }
251 
252     auto &r = Reproducer::Instance();
253     if (r.IsCapturing()) {
254       result.GetOutputStream() << "Reproducer is in capture mode.\n";
255     } else if (r.IsReplaying()) {
256       result.GetOutputStream() << "Reproducer is in replay mode.\n";
257     } else {
258       result.GetOutputStream() << "Reproducer is off.\n";
259     }
260 
261     if (r.IsCapturing() || r.IsReplaying()) {
262       result.GetOutputStream()
263           << "Path: " << r.GetReproducerPath().GetPath() << '\n';
264     }
265 
266     // Auto generate is hidden unless enabled because this is mostly for
267     // development and testing.
268     if (Generator *g = r.GetGenerator()) {
269       if (g->IsAutoGenerate())
270         result.GetOutputStream() << "Auto generate: on\n";
271     }
272 
273     result.SetStatus(eReturnStatusSuccessFinishResult);
274     return result.Succeeded();
275   }
276 };
277 
278 static void SetError(CommandReturnObject &result, Error err) {
279   result.GetErrorStream().Printf("error: %s\n",
280                                  toString(std::move(err)).c_str());
281   result.SetStatus(eReturnStatusFailed);
282 }
283 
284 class CommandObjectReproducerDump : public CommandObjectParsed {
285 public:
286   CommandObjectReproducerDump(CommandInterpreter &interpreter)
287       : CommandObjectParsed(interpreter, "reproducer dump",
288                             "Dump the information contained in a reproducer. "
289                             "If no reproducer is specified during replay, it "
290                             "dumps the content of the current reproducer.",
291                             nullptr) {}
292 
293   ~CommandObjectReproducerDump() override = default;
294 
295   Options *GetOptions() override { return &m_options; }
296 
297   class CommandOptions : public Options {
298   public:
299     CommandOptions() : Options(), file() {}
300 
301     ~CommandOptions() override = default;
302 
303     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
304                           ExecutionContext *execution_context) override {
305       Status error;
306       const int short_option = m_getopt_table[option_idx].val;
307 
308       switch (short_option) {
309       case 'f':
310         file.SetFile(option_arg, FileSpec::Style::native);
311         FileSystem::Instance().Resolve(file);
312         break;
313       case 'p':
314         provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
315             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
316         if (!error.Success())
317           error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
318                                          option_arg.str().c_str());
319         break;
320       default:
321         llvm_unreachable("Unimplemented option");
322       }
323 
324       return error;
325     }
326 
327     void OptionParsingStarting(ExecutionContext *execution_context) override {
328       file.Clear();
329       provider = eReproducerProviderNone;
330     }
331 
332     ArrayRef<OptionDefinition> GetDefinitions() override {
333       return makeArrayRef(g_reproducer_dump_options);
334     }
335 
336     FileSpec file;
337     ReproducerProvider provider = eReproducerProviderNone;
338   };
339 
340 protected:
341   bool DoExecute(Args &command, CommandReturnObject &result) override {
342     if (!command.empty()) {
343       result.AppendErrorWithFormat("'%s' takes no arguments",
344                                    m_cmd_name.c_str());
345       return false;
346     }
347 
348     // If no reproducer path is specified, use the loader currently used for
349     // replay. Otherwise create a new loader just for dumping.
350     llvm::Optional<Loader> loader_storage;
351     Loader *loader = nullptr;
352     if (!m_options.file) {
353       loader = Reproducer::Instance().GetLoader();
354       if (loader == nullptr) {
355         result.SetError(
356             "Not specifying a reproducer is only support during replay.");
357         result.SetStatus(eReturnStatusSuccessFinishNoResult);
358         return false;
359       }
360     } else {
361       loader_storage.emplace(m_options.file);
362       loader = &(*loader_storage);
363       if (Error err = loader->LoadIndex()) {
364         SetError(result, std::move(err));
365         return false;
366       }
367     }
368 
369     // If we get here we should have a valid loader.
370     assert(loader);
371 
372     switch (m_options.provider) {
373     case eReproducerProviderFiles: {
374       FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
375 
376       // Read the VFS mapping.
377       ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
378           vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
379       if (!buffer) {
380         SetError(result, errorCodeToError(buffer.getError()));
381         return false;
382       }
383 
384       // Initialize a VFS from the given mapping.
385       IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
386           std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
387 
388       // Dump the VFS to a buffer.
389       std::string str;
390       raw_string_ostream os(str);
391       static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
392       os.flush();
393 
394       // Return the string.
395       result.AppendMessage(str);
396       result.SetStatus(eReturnStatusSuccessFinishResult);
397       return true;
398     }
399     case eReproducerProviderVersion: {
400       Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
401       if (!version) {
402         SetError(result, version.takeError());
403         return false;
404       }
405       result.AppendMessage(*version);
406       result.SetStatus(eReturnStatusSuccessFinishResult);
407       return true;
408     }
409     case eReproducerProviderWorkingDirectory: {
410       Expected<std::string> cwd =
411           loader->LoadBuffer<WorkingDirectoryProvider>();
412       if (!cwd) {
413         SetError(result, cwd.takeError());
414         return false;
415       }
416       result.AppendMessage(*cwd);
417       result.SetStatus(eReturnStatusSuccessFinishResult);
418       return true;
419     }
420     case eReproducerProviderCommands: {
421       std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
422           repro::MultiLoader<repro::CommandProvider>::Create(loader);
423       if (!multi_loader) {
424         SetError(result,
425                  make_error<StringError>("Unable to create command loader.",
426                                          llvm::inconvertibleErrorCode()));
427         return false;
428       }
429 
430       // Iterate over the command files and dump them.
431       llvm::Optional<std::string> command_file;
432       while ((command_file = multi_loader->GetNextFile())) {
433         if (!command_file)
434           break;
435 
436         auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
437         if (auto err = command_buffer.getError()) {
438           SetError(result, errorCodeToError(err));
439           return false;
440         }
441         result.AppendMessage((*command_buffer)->getBuffer());
442       }
443 
444       result.SetStatus(eReturnStatusSuccessFinishResult);
445       return true;
446     }
447     case eReproducerProviderGDB: {
448       std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
449           multi_loader =
450               repro::MultiLoader<repro::GDBRemoteProvider>::Create(loader);
451 
452       if (!multi_loader) {
453         SetError(result,
454                  make_error<StringError>("Unable to create GDB loader.",
455                                          llvm::inconvertibleErrorCode()));
456         return false;
457       }
458 
459       llvm::Optional<std::string> gdb_file;
460       while ((gdb_file = multi_loader->GetNextFile())) {
461         auto error_or_file = MemoryBuffer::getFile(*gdb_file);
462         if (auto err = error_or_file.getError()) {
463           SetError(result, errorCodeToError(err));
464           return false;
465         }
466 
467         std::vector<GDBRemotePacket> packets;
468         yaml::Input yin((*error_or_file)->getBuffer());
469         yin >> packets;
470 
471         if (auto err = yin.error()) {
472           SetError(result, errorCodeToError(err));
473           return false;
474         }
475 
476         for (GDBRemotePacket &packet : packets) {
477           packet.Dump(result.GetOutputStream());
478         }
479       }
480 
481       result.SetStatus(eReturnStatusSuccessFinishResult);
482       return true;
483     }
484     case eReproducerProviderNone:
485       result.SetError("No valid provider specified.");
486       return false;
487     }
488 
489     result.SetStatus(eReturnStatusSuccessFinishNoResult);
490     return result.Succeeded();
491   }
492 
493 private:
494   CommandOptions m_options;
495 };
496 
497 CommandObjectReproducer::CommandObjectReproducer(
498     CommandInterpreter &interpreter)
499     : CommandObjectMultiword(
500           interpreter, "reproducer",
501           "Commands for manipulating reproducers. Reproducers make it "
502           "possible "
503           "to capture full debug sessions with all its dependencies. The "
504           "resulting reproducer is used to replay the debug session while "
505           "debugging the debugger.\n"
506           "Because reproducers need the whole the debug session from "
507           "beginning to end, you need to launch the debugger in capture or "
508           "replay mode, commonly though the command line driver.\n"
509           "Reproducers are unrelated record-replay debugging, as you cannot "
510           "interact with the debugger during replay.\n",
511           "reproducer <subcommand> [<subcommand-options>]") {
512   LoadSubCommand(
513       "generate",
514       CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
515   LoadSubCommand("status", CommandObjectSP(
516                                new CommandObjectReproducerStatus(interpreter)));
517   LoadSubCommand("dump",
518                  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
519   LoadSubCommand("xcrash", CommandObjectSP(
520                                new CommandObjectReproducerXCrash(interpreter)));
521 }
522 
523 CommandObjectReproducer::~CommandObjectReproducer() = default;
524