LLDB  mainline
CommandObjectReproducer.cpp
Go to the documentation of this file.
1 //===-- CommandObjectReproducer.cpp ---------------------------------------===//
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 
10 
11 #include "lldb/Host/HostInfo.h"
12 #include "lldb/Host/OptionParser.h"
16 #include "lldb/Utility/GDBRemote.h"
19 
20 #include <csignal>
21 
22 using namespace lldb;
23 using namespace llvm;
24 using namespace lldb_private;
25 using namespace lldb_private::repro;
26 
35 };
36 
37 static constexpr OptionEnumValueElement g_reproducer_provider_type[] = {
38  {
40  "commands",
41  "Command Interpreter Commands",
42  },
43  {
45  "files",
46  "Files",
47  },
48  {
50  "gdb",
51  "GDB Remote Packets",
52  },
53  {
55  "processes",
56  "Process Info",
57  },
58  {
60  "version",
61  "Version",
62  },
63  {
65  "cwd",
66  "Working Directory",
67  },
68  {
70  "none",
71  "None",
72  },
73 };
74 
75 static constexpr OptionEnumValues ReproducerProviderType() {
76  return OptionEnumValues(g_reproducer_provider_type);
77 }
78 
79 #define LLDB_OPTIONS_reproducer_dump
80 #include "CommandOptions.inc"
81 
85 };
86 
87 static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
88  {
90  "SIGILL",
91  "Illegal instruction",
92  },
93  {
95  "SIGSEGV",
96  "Segmentation fault",
97  },
98 };
99 
100 static constexpr OptionEnumValues ReproducerSignalType() {
101  return OptionEnumValues(g_reproducer_signaltype);
102 }
103 
104 #define LLDB_OPTIONS_reproducer_xcrash
105 #include "CommandOptions.inc"
106 
107 template <typename T>
108 llvm::Expected<T> static ReadFromYAML(StringRef filename) {
109  auto error_or_file = MemoryBuffer::getFile(filename);
110  if (auto err = error_or_file.getError()) {
111  return errorCodeToError(err);
112  }
113 
114  T t;
115  yaml::Input yin((*error_or_file)->getBuffer());
116  yin >> t;
117 
118  if (auto err = yin.error()) {
119  return errorCodeToError(err);
120  }
121 
122  return t;
123 }
124 
126 public:
129  interpreter, "reproducer generate",
130  "Generate reproducer on disk. When the debugger is in capture "
131  "mode, this command will output the reproducer to a directory on "
132  "disk and quit. In replay mode this command in a no-op.",
133  nullptr) {}
134 
135  ~CommandObjectReproducerGenerate() override = default;
136 
137 protected:
138  bool DoExecute(Args &command, CommandReturnObject &result) override {
139  if (!command.empty()) {
140  result.AppendErrorWithFormat("'%s' takes no arguments",
141  m_cmd_name.c_str());
142  return false;
143  }
144 
145  auto &r = Reproducer::Instance();
146  if (auto generator = r.GetGenerator()) {
147  generator->Keep();
148  } else if (r.IsReplaying()) {
149  // Make this operation a NO-OP in replay mode.
151  return result.Succeeded();
152  } else {
153  result.AppendErrorWithFormat("Unable to get the reproducer generator");
155  return false;
156  }
157 
158  result.GetOutputStream()
159  << "Reproducer written to '" << r.GetReproducerPath() << "'\n";
160  result.GetOutputStream()
161  << "Please have a look at the directory to assess if you're willing to "
162  "share the contained information.\n";
163 
164  m_interpreter.BroadcastEvent(
165  CommandInterpreter::eBroadcastBitQuitCommandReceived);
167  return result.Succeeded();
168  }
169 };
170 
172 public:
174  : CommandObjectParsed(interpreter, "reproducer xcrash",
175  "Intentionally force the debugger to crash in "
176  "order to trigger and test reproducer generation.",
177  nullptr) {}
178 
179  ~CommandObjectReproducerXCrash() override = default;
180 
181  Options *GetOptions() override { return &m_options; }
182 
183  class CommandOptions : public Options {
184  public:
186 
187  ~CommandOptions() override = default;
188 
189  Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
190  ExecutionContext *execution_context) override {
191  Status error;
192  const int short_option = m_getopt_table[option_idx].val;
193 
194  switch (short_option) {
195  case 's':
196  signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
197  option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
198  if (!error.Success())
199  error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
200  option_arg.str().c_str());
201  break;
202  default:
203  llvm_unreachable("Unimplemented option");
204  }
205 
206  return error;
207  }
208 
209  void OptionParsingStarting(ExecutionContext *execution_context) override {
210  signal = eReproducerCrashSigsegv;
211  }
212 
213  ArrayRef<OptionDefinition> GetDefinitions() override {
214  return makeArrayRef(g_reproducer_xcrash_options);
215  }
216 
218  };
219 
220 protected:
221  bool DoExecute(Args &command, CommandReturnObject &result) override {
222  if (!command.empty()) {
223  result.AppendErrorWithFormat("'%s' takes no arguments",
224  m_cmd_name.c_str());
225  return false;
226  }
227 
228  auto &r = Reproducer::Instance();
229 
230  if (!r.IsCapturing() && !r.IsReplaying()) {
231  result.SetError(
232  "forcing a crash is only supported when capturing a reproducer.");
234  return false;
235  }
236 
237  switch (m_options.signal) {
239  std::raise(SIGILL);
240  break;
242  std::raise(SIGSEGV);
243  break;
244  }
245 
247  return result.Succeeded();
248  }
249 
250 private:
252 };
253 
255 public:
258  interpreter, "reproducer status",
259  "Show the current reproducer status. In capture mode the "
260  "debugger "
261  "is collecting all the information it needs to create a "
262  "reproducer. In replay mode the reproducer is replaying a "
263  "reproducer. When the reproducers are off, no data is collected "
264  "and no reproducer can be generated.",
265  nullptr) {}
266 
267  ~CommandObjectReproducerStatus() override = default;
268 
269 protected:
270  bool DoExecute(Args &command, CommandReturnObject &result) override {
271  if (!command.empty()) {
272  result.AppendErrorWithFormat("'%s' takes no arguments",
273  m_cmd_name.c_str());
274  return false;
275  }
276 
277  auto &r = Reproducer::Instance();
278  if (r.IsCapturing()) {
279  result.GetOutputStream() << "Reproducer is in capture mode.\n";
280  } else if (r.IsReplaying()) {
281  result.GetOutputStream() << "Reproducer is in replay mode.\n";
282  } else {
283  result.GetOutputStream() << "Reproducer is off.\n";
284  }
285 
286  if (r.IsCapturing() || r.IsReplaying()) {
287  result.GetOutputStream()
288  << "Path: " << r.GetReproducerPath().GetPath() << '\n';
289  }
290 
291  // Auto generate is hidden unless enabled because this is mostly for
292  // development and testing.
293  if (Generator *g = r.GetGenerator()) {
294  if (g->IsAutoGenerate())
295  result.GetOutputStream() << "Auto generate: on\n";
296  }
297 
299  return result.Succeeded();
300  }
301 };
302 
303 static void SetError(CommandReturnObject &result, Error err) {
304  result.GetErrorStream().Printf("error: %s\n",
305  toString(std::move(err)).c_str());
307 }
308 
310 public:
312  : CommandObjectParsed(interpreter, "reproducer dump",
313  "Dump the information contained in a reproducer. "
314  "If no reproducer is specified during replay, it "
315  "dumps the content of the current reproducer.",
316  nullptr) {}
317 
318  ~CommandObjectReproducerDump() override = default;
319 
320  Options *GetOptions() override { return &m_options; }
321 
322  class CommandOptions : public Options {
323  public:
324  CommandOptions() : Options(), file() {}
325 
326  ~CommandOptions() override = default;
327 
328  Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
329  ExecutionContext *execution_context) override {
330  Status error;
331  const int short_option = m_getopt_table[option_idx].val;
332 
333  switch (short_option) {
334  case 'f':
335  file.SetFile(option_arg, FileSpec::Style::native);
336  FileSystem::Instance().Resolve(file);
337  break;
338  case 'p':
339  provider = (ReproducerProvider)OptionArgParser::ToOptionEnum(
340  option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
341  if (!error.Success())
342  error.SetErrorStringWithFormat("unrecognized value for provider '%s'",
343  option_arg.str().c_str());
344  break;
345  default:
346  llvm_unreachable("Unimplemented option");
347  }
348 
349  return error;
350  }
351 
352  void OptionParsingStarting(ExecutionContext *execution_context) override {
353  file.Clear();
354  provider = eReproducerProviderNone;
355  }
356 
357  ArrayRef<OptionDefinition> GetDefinitions() override {
358  return makeArrayRef(g_reproducer_dump_options);
359  }
360 
363  };
364 
365 protected:
366  bool DoExecute(Args &command, CommandReturnObject &result) override {
367  if (!command.empty()) {
368  result.AppendErrorWithFormat("'%s' takes no arguments",
369  m_cmd_name.c_str());
370  return false;
371  }
372 
373  // If no reproducer path is specified, use the loader currently used for
374  // replay. Otherwise create a new loader just for dumping.
375  llvm::Optional<Loader> loader_storage;
376  Loader *loader = nullptr;
377  if (!m_options.file) {
378  loader = Reproducer::Instance().GetLoader();
379  if (loader == nullptr) {
380  result.SetError(
381  "Not specifying a reproducer is only support during replay.");
383  return false;
384  }
385  } else {
386  loader_storage.emplace(m_options.file);
387  loader = &(*loader_storage);
388  if (Error err = loader->LoadIndex()) {
389  SetError(result, std::move(err));
390  return false;
391  }
392  }
393 
394  // If we get here we should have a valid loader.
395  assert(loader);
396 
397  switch (m_options.provider) {
399  FileSpec vfs_mapping = loader->GetFile<FileProvider::Info>();
400 
401  // Read the VFS mapping.
402  ErrorOr<std::unique_ptr<MemoryBuffer>> buffer =
403  vfs::getRealFileSystem()->getBufferForFile(vfs_mapping.GetPath());
404  if (!buffer) {
405  SetError(result, errorCodeToError(buffer.getError()));
406  return false;
407  }
408 
409  // Initialize a VFS from the given mapping.
410  IntrusiveRefCntPtr<vfs::FileSystem> vfs = vfs::getVFSFromYAML(
411  std::move(buffer.get()), nullptr, vfs_mapping.GetPath());
412 
413  // Dump the VFS to a buffer.
414  std::string str;
415  raw_string_ostream os(str);
416  static_cast<vfs::RedirectingFileSystem &>(*vfs).dump(os);
417  os.flush();
418 
419  // Return the string.
420  result.AppendMessage(str);
422  return true;
423  }
425  Expected<std::string> version = loader->LoadBuffer<VersionProvider>();
426  if (!version) {
427  SetError(result, version.takeError());
428  return false;
429  }
430  result.AppendMessage(*version);
432  return true;
433  }
435  Expected<std::string> cwd =
437  if (!cwd) {
438  SetError(result, cwd.takeError());
439  return false;
440  }
441  result.AppendMessage(*cwd);
443  return true;
444  }
446  std::unique_ptr<repro::MultiLoader<repro::CommandProvider>> multi_loader =
448  if (!multi_loader) {
449  SetError(result,
450  make_error<StringError>("Unable to create command loader.",
451  llvm::inconvertibleErrorCode()));
452  return false;
453  }
454 
455  // Iterate over the command files and dump them.
456  llvm::Optional<std::string> command_file;
457  while ((command_file = multi_loader->GetNextFile())) {
458  if (!command_file)
459  break;
460 
461  auto command_buffer = llvm::MemoryBuffer::getFile(*command_file);
462  if (auto err = command_buffer.getError()) {
463  SetError(result, errorCodeToError(err));
464  return false;
465  }
466  result.AppendMessage((*command_buffer)->getBuffer());
467  }
468 
470  return true;
471  }
472  case eReproducerProviderGDB: {
473  std::unique_ptr<repro::MultiLoader<repro::GDBRemoteProvider>>
474  multi_loader =
476 
477  if (!multi_loader) {
478  SetError(result,
479  make_error<StringError>("Unable to create GDB loader.",
480  llvm::inconvertibleErrorCode()));
481  return false;
482  }
483 
484  llvm::Optional<std::string> gdb_file;
485  while ((gdb_file = multi_loader->GetNextFile())) {
486  if (llvm::Expected<std::vector<GDBRemotePacket>> packets =
487  ReadFromYAML<std::vector<GDBRemotePacket>>(*gdb_file)) {
488  for (GDBRemotePacket &packet : *packets) {
489  packet.Dump(result.GetOutputStream());
490  }
491  } else {
492  SetError(result, packets.takeError());
493  return false;
494  }
495  }
496 
498  return true;
499  }
501  std::unique_ptr<repro::MultiLoader<repro::ProcessInfoProvider>>
502  multi_loader =
504 
505  if (!multi_loader) {
506  SetError(result, make_error<StringError>(
507  llvm::inconvertibleErrorCode(),
508  "Unable to create process info loader."));
509  return false;
510  }
511 
512  llvm::Optional<std::string> process_file;
513  while ((process_file = multi_loader->GetNextFile())) {
514  if (llvm::Expected<ProcessInstanceInfoList> infos =
515  ReadFromYAML<ProcessInstanceInfoList>(*process_file)) {
516  for (ProcessInstanceInfo info : *infos)
517  info.Dump(result.GetOutputStream(), HostInfo::GetUserIDResolver());
518  } else {
519  SetError(result, infos.takeError());
520  return false;
521  }
522  }
523 
525  return true;
526  }
528  result.SetError("No valid provider specified.");
529  return false;
530  }
531 
533  return result.Succeeded();
534  }
535 
536 private:
538 };
539 
540 CommandObjectReproducer::CommandObjectReproducer(
541  CommandInterpreter &interpreter)
543  interpreter, "reproducer",
544  "Commands for manipulating reproducers. Reproducers make it "
545  "possible "
546  "to capture full debug sessions with all its dependencies. The "
547  "resulting reproducer is used to replay the debug session while "
548  "debugging the debugger.\n"
549  "Because reproducers need the whole the debug session from "
550  "beginning to end, you need to launch the debugger in capture or "
551  "replay mode, commonly though the command line driver.\n"
552  "Reproducers are unrelated record-replay debugging, as you cannot "
553  "interact with the debugger during replay.\n",
554  "reproducer <subcommand> [<subcommand-options>]") {
556  "generate",
557  CommandObjectSP(new CommandObjectReproducerGenerate(interpreter)));
558  LoadSubCommand("status", CommandObjectSP(
559  new CommandObjectReproducerStatus(interpreter)));
560  LoadSubCommand("dump",
561  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
562  LoadSubCommand("xcrash", CommandObjectSP(
563  new CommandObjectReproducerXCrash(interpreter)));
564 }
565 
static void SetError(CommandReturnObject &result, Error err)
A command line argument class.
Definition: Args.h:33
bool empty() const
Definition: Args.h:118
A class that represents a running process on the host machine.
Definition: Debugger.h:49
bool DoExecute(Args &command, CommandReturnObject &result) override
Provider for the LLDB version number.
Definition: Reproducer.h:122
bool LoadSubCommand(llvm::StringRef cmd_name, const lldb::CommandObjectSP &command_obj) override
void AppendErrorWithFormat(const char *format,...) __attribute__((format(printf
void OptionParsingStarting(ExecutionContext *execution_context) override
llvm::Error Error
llvm::Expected< std::string > LoadBuffer()
Definition: Reproducer.h:303
bool DoExecute(Args &command, CommandReturnObject &result) override
A file utility class.
Definition: FileSpec.h:56
"lldb/Target/ExecutionContext.h" A class that contains an execution context.
static constexpr OptionEnumValues ReproducerProviderType()
static constexpr OptionEnumValues ReproducerSignalType()
CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
llvm::StringRef toString(Record::Kind K)
Status SetOptionValue(uint32_t option_idx, StringRef option_arg, ExecutionContext *execution_context) override
static llvm::Expected< T > ReadFromYAML(StringRef filename)
void AppendMessage(llvm::StringRef in_string)
static constexpr OptionEnumValueElement g_reproducer_signaltype[]
void SetError(const Status &error, const char *fallback_error_cstr=nullptr)
void OptionParsingStarting(ExecutionContext *execution_context) override
size_t Printf(const char *format,...) __attribute__((format(printf
Output printf formatted output to the stream.
Definition: Stream.cpp:106
CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
bool Success() const
Test for success condition.
Definition: Status.cpp:288
static constexpr OptionEnumValueElement g_reproducer_provider_type[]
A command line option parsing protocol class.
Definition: Options.h:62
GDB remote packet as used by the reproducer and the GDB remote communication history.
Definition: GDBRemote.h:52
ArrayRef< OptionDefinition > GetDefinitions() override
bool DoExecute(Args &command, CommandReturnObject &result) override
The generator is responsible for the logic needed to generate a reproducer.
Definition: Reproducer.h:225
CommandObjectReproducerDump(CommandInterpreter &interpreter)
Definition: SBAddress.h:15
bool DoExecute(Args &command, CommandReturnObject &result) override
Status SetOptionValue(uint32_t option_idx, StringRef option_arg, ExecutionContext *execution_context) override
int SetErrorStringWithFormat(const char *format,...) __attribute__((format(printf
Set the current error string to a formatted error string.
Definition: Status.cpp:256
ArrayRef< OptionDefinition > GetDefinitions() override
CommandObjectReproducerStatus(CommandInterpreter &interpreter)
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition: FileSpec.cpp:348
void SetStatus(lldb::ReturnStatus status)
Provider for the LLDB current working directory.
Definition: Reproducer.h:142
An error handling class.
Definition: Status.h:44