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