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. 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     result.SetStatus(eReturnStatusSuccessFinishResult);
114     return result.Succeeded();
115   }
116 };
117 
118 class CommandObjectReproducerStatus : public CommandObjectParsed {
119 public:
120   CommandObjectReproducerStatus(CommandInterpreter &interpreter)
121       : CommandObjectParsed(
122             interpreter, "reproducer status",
123             "Show the current reproducer status. In capture mode the debugger "
124             "is collecting all the information it needs to create a "
125             "reproducer.  In replay mode the reproducer is replaying a "
126             "reproducer. When the reproducers are off, no data is collected "
127             "and no reproducer can be generated.",
128             nullptr) {}
129 
130   ~CommandObjectReproducerStatus() override = default;
131 
132 protected:
133   bool DoExecute(Args &command, CommandReturnObject &result) override {
134     if (!command.empty()) {
135       result.AppendErrorWithFormat("'%s' takes no arguments",
136                                    m_cmd_name.c_str());
137       return false;
138     }
139 
140     auto &r = Reproducer::Instance();
141     if (r.IsCapturing()) {
142       result.GetOutputStream() << "Reproducer is in capture mode.\n";
143     } else if (r.IsReplaying()) {
144       result.GetOutputStream() << "Reproducer is in replay mode.\n";
145     } else {
146       result.GetOutputStream() << "Reproducer is off.\n";
147     }
148 
149     result.SetStatus(eReturnStatusSuccessFinishResult);
150     return result.Succeeded();
151   }
152 };
153 
154 static void SetError(CommandReturnObject &result, Error err) {
155   result.GetErrorStream().Printf("error: %s\n",
156                                  toString(std::move(err)).c_str());
157   result.SetStatus(eReturnStatusFailed);
158 }
159 
160 class CommandObjectReproducerDump : public CommandObjectParsed {
161 public:
162   CommandObjectReproducerDump(CommandInterpreter &interpreter)
163       : CommandObjectParsed(interpreter, "reproducer dump",
164                             "Dump the information contained in a reproducer.",
165                             nullptr) {}
166 
167   ~CommandObjectReproducerDump() override = default;
168 
169   Options *GetOptions() override { return &m_options; }
170 
171   class CommandOptions : public Options {
172   public:
173     CommandOptions() : Options(), file() {}
174 
175     ~CommandOptions() override = default;
176 
177     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
178                           ExecutionContext *execution_context) override {
179       Status error;
180       const int short_option = m_getopt_table[option_idx].val;
181 
182       switch (short_option) {
183       case 'f':
184         file.SetFile(option_arg, FileSpec::Style::native);
185         FileSystem::Instance().Resolve(file);
186         break;
187       case 'p':
188         provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
189             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
190         if (!error.Success())
191           error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
192                                          option_arg.str().c_str());
193         break;
194       default:
195         llvm_unreachable("Unimplemented option");
196       }
197 
198       return error;
199     }
200 
201     void OptionParsingStarting(ExecutionContext *execution_context) override {
202       file.Clear();
203       provider = eReproducerProviderNone;
204     }
205 
206     ArrayRef<OptionDefinition> GetDefinitions() override {
207       return makeArrayRef(g_reproducer_options);
208     }
209 
210     FileSpec file;
211     ReproducerProvider provider = eReproducerProviderNone;
212   };
213 
214 protected:
215   bool DoExecute(Args &command, CommandReturnObject &result) override {
216     if (!command.empty()) {
217       result.AppendErrorWithFormat("'%s' takes no arguments",
218                                    m_cmd_name.c_str());
219       return false;
220     }
221 
222     // If no reproducer path is specified, use the loader currently used for
223     // replay. Otherwise create a new loader just for dumping.
224     llvm::Optional<Loader> loader_storage;
225     Loader *loader = nullptr;
226     if (!m_options.file) {
227       loader = Reproducer::Instance().GetLoader();
228       if (loader == nullptr) {
229         result.SetError(
230             "Not specifying a reproducer is only support during replay.");
231         result.SetStatus(eReturnStatusSuccessFinishNoResult);
232         return false;
233       }
234     } else {
235       loader_storage.emplace(m_options.file);
236       loader = &(*loader_storage);
237       if (Error err = loader->LoadIndex()) {
238         SetError(result, std::move(err));
239         return false;
240       }
241     }
242 
243     // If we get here we should have a valid loader.
244     assert(loader);
245 
246     switch (m_options.provider) {
247     case eReproducerProviderFiles: {
248       FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
249 
250       // Read the VFS mapping.
251       ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
252           vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
253       if (!buffer) {
254         SetError(result, errorCodeToError(buffer.getError()));
255         return false;
256       }
257 
258       // Initialize a VFS from the given mapping.
259       IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
260           std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
261 
262       // Dump the VFS to a buffer.
263       std::string str;
264       raw_string_ostream os(str);
265       static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
266       os.flush();
267 
268       // Return the string.
269       result.AppendMessage(str);
270       result.SetStatus(eReturnStatusSuccessFinishResult);
271       return true;
272     }
273     case eReproducerProviderVersion: {
274       Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
275       if (!version) {
276         SetError(result, version.takeError());
277         return false;
278       }
279       result.AppendMessage(*version);
280       result.SetStatus(eReturnStatusSuccessFinishResult);
281       return true;
282     }
283     case eReproducerProviderWorkingDirectory: {
284       Expected<std::string> cwd =
285           loader->LoadBuffer<WorkingDirectoryProvider>();
286       if (!cwd) {
287         SetError(result, cwd.takeError());
288         return false;
289       }
290       result.AppendMessage(*cwd);
291       result.SetStatus(eReturnStatusSuccessFinishResult);
292       return true;
293     }
294     case eReproducerProviderCommands: {
295       // Create a new command loader.
296       std::unique_ptr<repro::CommandLoader> command_loader =
297           repro::CommandLoader::Create(loader);
298       if (!command_loader) {
299         SetError(result,
300                  make_error<StringError>(llvm::inconvertibleErrorCode(),
301                                          "Unable to create command loader."));
302         return false;
303       }
304 
305       // Iterate over the command files and dump them.
306       while (true) {
307         llvm::Optional<std::string> command_file =
308             command_loader->GetNextFile();
309         if (!command_file)
310           break;
311 
312         auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
313         if (auto err = command_buffer.getError()) {
314           SetError(result, errorCodeToError(err));
315           return false;
316         }
317         result.AppendMessage((*command_buffer)->getBuffer());
318       }
319 
320       result.SetStatus(eReturnStatusSuccessFinishResult);
321       return true;
322     }
323     case eReproducerProviderGDB: {
324       FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>();
325       auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath());
326       if (auto err = error_or_file.getError()) {
327         SetError(result, errorCodeToError(err));
328         return false;
329       }
330 
331       std::vector<GDBRemotePacket> packets;
332       yaml::Input yin((*error_or_file)->getBuffer());
333       yin >> packets;
334 
335       if (auto err = yin.error()) {
336         SetError(result, errorCodeToError(err));
337         return false;
338       }
339 
340       for (GDBRemotePacket &packet : packets) {
341         packet.Dump(result.GetOutputStream());
342       }
343 
344       result.SetStatus(eReturnStatusSuccessFinishResult);
345       return true;
346     }
347     case eReproducerProviderNone:
348       result.SetError("No valid provider specified.");
349       return false;
350     }
351 
352     result.SetStatus(eReturnStatusSuccessFinishNoResult);
353     return result.Succeeded();
354   }
355 
356 private:
357   CommandOptions m_options;
358 };
359 
360 CommandObjectReproducer::CommandObjectReproducer(
361     CommandInterpreter &interpreter)
362     : CommandObjectMultiword(
363           interpreter, "reproducer",
364           "Commands for manipulate the reproducer functionality.",
365           "reproducer <subcommand> [<subcommand-options>]") {
366   LoadSubCommand(
367       "generate",
368       CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
369   LoadSubCommand("status", CommandObjectSP(
370                                new CommandObjectReproducerStatus(interpreter)));
371   LoadSubCommand("dump",
372                  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
373 }
374 
375 CommandObjectReproducer::~CommandObjectReproducer() = default;
376