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