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                             "If no reproducer is specified during replay, it "
166                             "dumps the content of the current reproducer.",
167                             nullptr) {}
168 
169   ~CommandObjectReproducerDump() override = default;
170 
171   Options *GetOptions() override { return &m_options; }
172 
173   class CommandOptions : public Options {
174   public:
175     CommandOptions() : Options(), file() {}
176 
177     ~CommandOptions() override = default;
178 
179     Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
180                           ExecutionContext *execution_context) override {
181       Status error;
182       const int short_option = m_getopt_table[option_idx].val;
183 
184       switch (short_option) {
185       case 'f':
186         file.SetFile(option_arg, FileSpec::Style::native);
187         FileSystem::Instance().Resolve(file);
188         break;
189       case 'p':
190         provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
191             option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
192         if (!error.Success())
193           error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
194                                          option_arg.str().c_str());
195         break;
196       default:
197         llvm_unreachable("Unimplemented option");
198       }
199 
200       return error;
201     }
202 
203     void OptionParsingStarting(ExecutionContext *execution_context) override {
204       file.Clear();
205       provider = eReproducerProviderNone;
206     }
207 
208     ArrayRef<OptionDefinition> GetDefinitions() override {
209       return makeArrayRef(g_reproducer_options);
210     }
211 
212     FileSpec file;
213     ReproducerProvider provider = eReproducerProviderNone;
214   };
215 
216 protected:
217   bool DoExecute(Args &command, CommandReturnObject &result) override {
218     if (!command.empty()) {
219       result.AppendErrorWithFormat("'%s' takes no arguments",
220                                    m_cmd_name.c_str());
221       return false;
222     }
223 
224     // If no reproducer path is specified, use the loader currently used for
225     // replay. Otherwise create a new loader just for dumping.
226     llvm::Optional<Loader> loader_storage;
227     Loader *loader = nullptr;
228     if (!m_options.file) {
229       loader = Reproducer::Instance().GetLoader();
230       if (loader == nullptr) {
231         result.SetError(
232             "Not specifying a reproducer is only support during replay.");
233         result.SetStatus(eReturnStatusSuccessFinishNoResult);
234         return false;
235       }
236     } else {
237       loader_storage.emplace(m_options.file);
238       loader = &(*loader_storage);
239       if (Error err = loader->LoadIndex()) {
240         SetError(result, std::move(err));
241         return false;
242       }
243     }
244 
245     // If we get here we should have a valid loader.
246     assert(loader);
247 
248     switch (m_options.provider) {
249     case eReproducerProviderFiles: {
250       FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
251 
252       // Read the VFS mapping.
253       ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
254           vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
255       if (!buffer) {
256         SetError(result, errorCodeToError(buffer.getError()));
257         return false;
258       }
259 
260       // Initialize a VFS from the given mapping.
261       IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
262           std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
263 
264       // Dump the VFS to a buffer.
265       std::string str;
266       raw_string_ostream os(str);
267       static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
268       os.flush();
269 
270       // Return the string.
271       result.AppendMessage(str);
272       result.SetStatus(eReturnStatusSuccessFinishResult);
273       return true;
274     }
275     case eReproducerProviderVersion: {
276       Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
277       if (!version) {
278         SetError(result, version.takeError());
279         return false;
280       }
281       result.AppendMessage(*version);
282       result.SetStatus(eReturnStatusSuccessFinishResult);
283       return true;
284     }
285     case eReproducerProviderWorkingDirectory: {
286       Expected<std::string> cwd =
287           loader->LoadBuffer<WorkingDirectoryProvider>();
288       if (!cwd) {
289         SetError(result, cwd.takeError());
290         return false;
291       }
292       result.AppendMessage(*cwd);
293       result.SetStatus(eReturnStatusSuccessFinishResult);
294       return true;
295     }
296     case eReproducerProviderCommands: {
297       // Create a new command loader.
298       std::unique_ptr<repro::CommandLoader> command_loader =
299           repro::CommandLoader::Create(loader);
300       if (!command_loader) {
301         SetError(result,
302                  make_error<StringError>(llvm::inconvertibleErrorCode(),
303                                          "Unable to create command loader."));
304         return false;
305       }
306 
307       // Iterate over the command files and dump them.
308       while (true) {
309         llvm::Optional<std::string> command_file =
310             command_loader->GetNextFile();
311         if (!command_file)
312           break;
313 
314         auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
315         if (auto err = command_buffer.getError()) {
316           SetError(result, errorCodeToError(err));
317           return false;
318         }
319         result.AppendMessage((*command_buffer)->getBuffer());
320       }
321 
322       result.SetStatus(eReturnStatusSuccessFinishResult);
323       return true;
324     }
325     case eReproducerProviderGDB: {
326       FileSpec gdb_file = loader->GetFile<ProcessGDBRemoteProvider::Info>();
327       auto error_or_file = MemoryBuffer::getFile(gdb_file.GetPath());
328       if (auto err = error_or_file.getError()) {
329         SetError(result, errorCodeToError(err));
330         return false;
331       }
332 
333       std::vector<GDBRemotePacket> packets;
334       yaml::Input yin((*error_or_file)->getBuffer());
335       yin >> packets;
336 
337       if (auto err = yin.error()) {
338         SetError(result, errorCodeToError(err));
339         return false;
340       }
341 
342       for (GDBRemotePacket &packet : packets) {
343         packet.Dump(result.GetOutputStream());
344       }
345 
346       result.SetStatus(eReturnStatusSuccessFinishResult);
347       return true;
348     }
349     case eReproducerProviderNone:
350       result.SetError("No valid provider specified.");
351       return false;
352     }
353 
354     result.SetStatus(eReturnStatusSuccessFinishNoResult);
355     return result.Succeeded();
356   }
357 
358 private:
359   CommandOptions m_options;
360 };
361 
362 CommandObjectReproducer::CommandObjectReproducer(
363     CommandInterpreter &interpreter)
364     : CommandObjectMultiword(
365           interpreter, "reproducer",
366           "Commands for manipulating reproducers. Reproducers make it possible "
367           "to capture full debug sessions with all its dependencies. The "
368           "resulting reproducer is used to replay the debug session while "
369           "debugging the debugger.\n"
370           "Because reproducers need the whole the debug session from "
371           "beginning to end, you need to launch the debugger in capture or "
372           "replay mode, commonly though the command line driver.\n"
373           "Reproducers are unrelated record-replay debugging, as you cannot "
374           "interact with the debugger during replay.\n",
375           "reproducer <subcommand> [<subcommand-options>]") {
376   LoadSubCommand(
377       "generate",
378       CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
379   LoadSubCommand("status", CommandObjectSP(
380                                new CommandObjectReproducerStatus(interpreter)));
381   LoadSubCommand("dump",
382                  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
383 }
384 
385 CommandObjectReproducer::~CommandObjectReproducer() = default;
386