[Reproducer] Generate LLDB reproducer on crash
authorJonas Devlieghere <jonas@devlieghere.com>
Wed, 20 Nov 2019 01:28:46 +0000 (17:28 -0800)
committerJonas Devlieghere <jonas@devlieghere.com>
Wed, 20 Nov 2019 21:14:16 +0000 (13:14 -0800)
This patch hooks the reproducer infrastructure with the signal handlers.
When lldb crashes with reproducers capture enabled, it will now generate
the reproducer and print a short message the standard out. This doesn't
affect the pretty stack traces, which are still printed before.

This patch also introduces a new reproducer sub-command that
intentionally raises a given signal to test the reproducer signal
handling.

Currently the signal handler is doing too much work. Instead of copying
over files into the reproducers in the signal handler, we should
re-invoke ourselves with a special command line flag that looks at the
VFS mapping and performs the copy.

This is a NO-OP when reproducers are disabled.

Differential revision: https://reviews.llvm.org/D70474

lldb/include/lldb/API/SBReproducer.h
lldb/source/API/SBReproducer.cpp
lldb/source/Commands/CommandObjectReproducer.cpp
lldb/source/Commands/Options.td
lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in [new file with mode: 0644]
lldb/test/Shell/Reproducer/TestCrash.test [new file with mode: 0644]
lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
lldb/tools/driver/Driver.cpp

index 0f1739d..93e5676 100644 (file)
@@ -21,6 +21,8 @@ public:
   static const char *Capture();
   static const char *Capture(const char *path);
   static const char *Replay(const char *path);
+  static const char *GetPath();
+  static bool Generate();
 };
 
 } // namespace lldb
index 6e11b2c..d50d95e 100644 (file)
@@ -30,7 +30,7 @@ using namespace lldb_private;
 using namespace lldb_private::repro;
 
 SBRegistry::SBRegistry() {
-  RegistryR = *this;
+  Registry &R = *this;
 
   RegisterMethods<SBAddress>(R);
   RegisterMethods<SBAttachInfo>(R);
@@ -149,6 +149,22 @@ const char *SBReproducer::Replay(const char *path) {
   return nullptr;
 }
 
+bool SBReproducer::Generate() {
+  auto &r = Reproducer::Instance();
+  if (auto generator = r.GetGenerator()) {
+    generator->Keep();
+    return true;
+  }
+  return false;
+}
+
+const char *SBReproducer::GetPath() {
+  static std::string path;
+  auto &r = Reproducer::Instance();
+  path = r.GetReproducerPath().GetCString();
+  return path.c_str();
+}
+
 char lldb_private::repro::SBProvider::ID = 0;
 const char *SBProvider::Info::name = "sbapi";
 const char *SBProvider::Info::file = "sbapi.bin";
index a22c704..7f97ba2 100644 (file)
@@ -17,6 +17,8 @@
 #include "lldb/Interpreter/OptionArgParser.h"
 #include "lldb/Interpreter/OptionGroupBoolean.h"
 
+#include <csignal>
+
 using namespace lldb;
 using namespace llvm;
 using namespace lldb_private;
@@ -71,6 +73,37 @@ static constexpr OptionEnumValues ReproducerProviderType() {
 #define LLDB_OPTIONS_reproducer_dump
 #include "CommandOptions.inc"
 
+enum ReproducerCrashSignal {
+  eReproducerCrashSigbus,
+  eReproducerCrashSigill,
+  eReproducerCrashSigsegv,
+};
+
+static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
+    {
+        eReproducerCrashSigbus,
+        "SIGBUS",
+        "Bus error",
+    },
+    {
+        eReproducerCrashSigill,
+        "SIGILL",
+        "Illegal instruction",
+    },
+    {
+        eReproducerCrashSigsegv,
+        "SIGSEGV",
+        "Segmentation fault",
+    },
+};
+
+static constexpr OptionEnumValues ReproducerSignalType() {
+  return OptionEnumValues(g_reproducer_signaltype);
+}
+
+#define LLDB_OPTIONS_reproducer_xcrash
+#include "CommandOptions.inc"
+
 class CommandObjectReproducerGenerate : public CommandObjectParsed {
 public:
   CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
@@ -117,12 +150,98 @@ protected:
   }
 };
 
+class CommandObjectReproducerXCrash : public CommandObjectParsed {
+public:
+  CommandObjectReproducerXCrash(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "reproducer xcrash",
+                            "Intentionally force  the debugger to crash in "
+                            "order to trigger and test reproducer generation.",
+                            nullptr) {}
+
+  ~CommandObjectReproducerXCrash() override = default;
+
+  Options *GetOptions() override { return &m_options; }
+
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() : Options() {}
+
+    ~CommandOptions() override = default;
+
+    Status SetOptionValue(uint32_t option_idx, StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      Status error;
+      const int short_option = m_getopt_table[option_idx].val;
+
+      switch (short_option) {
+      case 's':
+        signal = (ReproducerCrashSignal)OptionArgParser::ToOptionEnum(
+            option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
+        if (!error.Success())
+          error.SetErrorStringWithFormat("unrecognized value for signal '%s'",
+                                         option_arg.str().c_str());
+        break;
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+
+      return error;
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {
+      signal = eReproducerCrashSigsegv;
+    }
+
+    ArrayRef<OptionDefinition> GetDefinitions() override {
+      return makeArrayRef(g_reproducer_xcrash_options);
+    }
+
+    ReproducerCrashSignal signal = eReproducerCrashSigsegv;
+  };
+
+protected:
+  bool DoExecute(Args &command, CommandReturnObject &result) override {
+    if (!command.empty()) {
+      result.AppendErrorWithFormat("'%s' takes no arguments",
+                                   m_cmd_name.c_str());
+      return false;
+    }
+
+    auto &r = Reproducer::Instance();
+    if (!r.IsCapturing()) {
+      result.SetError(
+          "forcing a crash is only supported when capturing a reproducer.");
+      result.SetStatus(eReturnStatusSuccessFinishNoResult);
+      return false;
+    }
+
+    switch (m_options.signal) {
+    case eReproducerCrashSigill:
+      std::raise(SIGILL);
+      break;
+    case eReproducerCrashSigbus:
+      std::raise(SIGBUS);
+      break;
+    case eReproducerCrashSigsegv:
+      std::raise(SIGSEGV);
+      break;
+    }
+
+    result.SetStatus(eReturnStatusQuit);
+    return result.Succeeded();
+  }
+
+private:
+  CommandOptions m_options;
+};
+
 class CommandObjectReproducerStatus : public CommandObjectParsed {
 public:
   CommandObjectReproducerStatus(CommandInterpreter &interpreter)
       : CommandObjectParsed(
             interpreter, "reproducer status",
-            "Show the current reproducer status. In capture mode the debugger "
+            "Show the current reproducer status. In capture mode the "
+            "debugger "
             "is collecting all the information it needs to create a "
             "reproducer.  In replay mode the reproducer is replaying a "
             "reproducer. When the reproducers are off, no data is collected "
@@ -365,7 +484,8 @@ CommandObjectReproducer::CommandObjectReproducer(
     CommandInterpreter &interpreter)
     : CommandObjectMultiword(
           interpreter, "reproducer",
-          "Commands for manipulating reproducers. Reproducers make it possible "
+          "Commands for manipulating reproducers. Reproducers make it "
+          "possible "
           "to capture full debug sessions with all its dependencies. The "
           "resulting reproducer is used to replay the debug session while "
           "debugging the debugger.\n"
@@ -382,6 +502,8 @@ CommandObjectReproducer::CommandObjectReproducer(
                                new CommandObjectReproducerStatus(interpreter)));
   LoadSubCommand("dump",
                  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
+  LoadSubCommand("xcrash", CommandObjectSP(
+                               new CommandObjectReproducerXCrash(interpreter)));
 }
 
 CommandObjectReproducer::~CommandObjectReproducer() = default;
index 501f814..f53d148 100644 (file)
@@ -438,6 +438,12 @@ let Command = "reproducer dump" in {
     "provided, that reproducer is dumped.">;
 }
 
+let Command = "reproducer xcrash" in {
+  def reproducer_signal : Option<"signal", "s">, Group<1>,
+    EnumArg<"None", "ReproducerSignalType()">,
+    Required, Desc<"The signal to crash the debugger.">;
+}
+
 let Command = "memory read" in {
   def memory_read_num_per_line : Option<"num-per-line", "l">, Group<1>,
     Arg<"NumberPerLine">, Desc<"The number of items per line to display.">;
diff --git a/lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in b/lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in
new file mode 100644 (file)
index 0000000..1a733f4
--- /dev/null
@@ -0,0 +1,6 @@
+breakpoint set -f simple.c -l 12
+run
+bt
+cont
+reproducer status
+reproducer xcrash -s SIGSEGV
diff --git a/lldb/test/Shell/Reproducer/TestCrash.test b/lldb/test/Shell/Reproducer/TestCrash.test
new file mode 100644 (file)
index 0000000..1f26a4e
--- /dev/null
@@ -0,0 +1,14 @@
+# UNSUPPORTED: system-windows
+# This tests that a reproducer is generated when LLDB crashes.
+
+# Start clean.
+# RUN: rm -rf %t.repro
+
+# RUN: %lldb -b --capture --capture-path %t.repro -o 'reproducer xcrash -s SIGSEGV' | FileCheck %s
+# RUN: %lldb -b --capture --capture-path %t.repro -o 'reproducer xcrash -s SIGBUS' | FileCheck %s
+# RUN: %lldb -b --capture --capture-path %t.repro -o 'reproducer xcrash -s SIGILL' | FileCheck %s
+
+# CHECK: ********************
+# CHECK: Crash reproducer for
+# CHECK: Reproducer written to
+# CHECK: ********************
index 04a3e54..609c839 100644 (file)
@@ -6,11 +6,18 @@
 # process. To ensure we're not actually running the original binary we check
 # that the string "testing" is not printed.
 
-# RUN: rm -rf %t.repro
 # RUN: %clang_host %S/Inputs/simple.c -g -o %t.out
+
+# Test reproducer generate command.
+# RUN: rm -rf %t.repro
 # RUN: %lldb -x -b -s %S/Inputs/GDBRemoteCapture.in --capture --capture-path %t.repro %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE
 # RUN: env FOO=BAR %lldb --replay %t.repro | FileCheck %s --check-prefix CHECK --check-prefix REPLAY
 
+# Test crash reproducer.
+# RUN: rm -rf %t.repro
+# RUN: %lldb -x -b -s %S/Inputs/GDBRemoteCrashCapture.in --capture --capture-path %t.repro %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE
+# RUN: %lldb --replay %t.repro | FileCheck %s --check-prefix CHECK --check-prefix REPLAY
+
 # CHECK: Breakpoint 1
 # CHECK: Process {{.*}} stopped
 # CHECK: Process {{.*}} launched
index 3a22d7a..9d685f2 100644 (file)
@@ -732,6 +732,20 @@ void sigcont_handler(int signo) {
   signal(signo, sigcont_handler);
 }
 
+void reproducer_handler(void *) {
+  if (SBReproducer::Generate()) {
+    llvm::outs() << "********************\n";
+    llvm::outs() << "Crash reproducer for ";
+    llvm::outs() << lldb::SBDebugger::GetVersionString() << '\n';
+    llvm::outs() << "Reproducer written to '" << SBReproducer::GetPath()
+                 << "'\n";
+    llvm::outs()
+        << "Please have a look at the directory to assess if you're willing to "
+           "share the contained information.\n";
+    llvm::outs() << "********************\n";
+  }
+}
+
 static void printHelp(LLDBOptTable &table, llvm::StringRef tool_name) {
   std::string usage_str = tool_name.str() + "options";
   table.PrintHelp(llvm::outs(), usage_str.c_str(), "LLDB", false);
@@ -832,6 +846,9 @@ int main(int argc, char const *argv[]) {
     return *exit_code;
   }
 
+  // Register the reproducer signal handler.
+  llvm::sys::AddSignalHandler(reproducer_handler, nullptr);
+
   SBError error = SBDebugger::InitializeWithErrorHandling();
   if (error.Fail()) {
     WithColor::error() << "initialization failed: " << error.GetCString()