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