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