[Lldb-commits] [lldb] c8dfe90 - [Reproducer] Generate LLDB reproducer on crash

Jonas Devlieghere via lldb-commits lldb-commits at lists.llvm.org
Wed Nov 20 13:14:24 PST 2019


Author: Jonas Devlieghere
Date: 2019-11-20T13:14:16-08:00
New Revision: c8dfe907299e16aeb17175cb0896c17043fc7c81

URL: https://github.com/llvm/llvm-project/commit/c8dfe907299e16aeb17175cb0896c17043fc7c81
DIFF: https://github.com/llvm/llvm-project/commit/c8dfe907299e16aeb17175cb0896c17043fc7c81.diff

LOG: [Reproducer] Generate LLDB reproducer on crash

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

Added: 
    lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in
    lldb/test/Shell/Reproducer/TestCrash.test

Modified: 
    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/TestGDBRemoteRepro.test
    lldb/tools/driver/Driver.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/API/SBReproducer.h b/lldb/include/lldb/API/SBReproducer.h
index 0f1739d0c5bd..93e567607aa8 100644
--- a/lldb/include/lldb/API/SBReproducer.h
+++ b/lldb/include/lldb/API/SBReproducer.h
@@ -21,6 +21,8 @@ class LLDB_API SBReproducer {
   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

diff  --git a/lldb/source/API/SBReproducer.cpp b/lldb/source/API/SBReproducer.cpp
index 6e11b2c6366f..d50d95ebb547 100644
--- a/lldb/source/API/SBReproducer.cpp
+++ b/lldb/source/API/SBReproducer.cpp
@@ -30,7 +30,7 @@ using namespace lldb_private;
 using namespace lldb_private::repro;
 
 SBRegistry::SBRegistry() {
-  Registry& R = *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";

diff  --git a/lldb/source/Commands/CommandObjectReproducer.cpp b/lldb/source/Commands/CommandObjectReproducer.cpp
index a22c704ebd0f..7f97ba2875cb 100644
--- a/lldb/source/Commands/CommandObjectReproducer.cpp
+++ b/lldb/source/Commands/CommandObjectReproducer.cpp
@@ -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 @@ class CommandObjectReproducerGenerate : public CommandObjectParsed {
   }
 };
 
+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;

diff  --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 501f81470a2e..f53d1481f4e8 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -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
index 000000000000..1a733f4dd94f
--- /dev/null
+++ b/lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in
@@ -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
index 000000000000..1f26a4ebf03d
--- /dev/null
+++ b/lldb/test/Shell/Reproducer/TestCrash.test
@@ -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: ********************

diff  --git a/lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test b/lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
index 04a3e5465bb5..609c83929292 100644
--- a/lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
+++ b/lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
@@ -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

diff  --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp
index 3a22d7aabc15..9d685f2e35ba 100644
--- a/lldb/tools/driver/Driver.cpp
+++ b/lldb/tools/driver/Driver.cpp
@@ -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()


        


More information about the lldb-commits mailing list