JDevlieghere created this revision.
JDevlieghere added reviewers: labath, vsk, jingham, friss.
Herald added a reviewer: jfb.
Herald added a project: LLDB.
JDevlieghere edited the summary of this revision.
Herald added a subscriber: dexonsmith.

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.


Repository:
  rLLDB LLDB

https://reviews.llvm.org/D70474

Files:
  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
  lldb/test/Shell/Reproducer/TestCrash.test
  lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
  lldb/tools/driver/Driver.cpp

Index: lldb/tools/driver/Driver.cpp
===================================================================
--- lldb/tools/driver/Driver.cpp
+++ lldb/tools/driver/Driver.cpp
@@ -732,6 +732,20 @@
   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 @@
     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()
Index: lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
===================================================================
--- lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test
+++ 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.
+# 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 an actual crash.
+# 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: lldb/test/Shell/Reproducer/TestCrash.test
===================================================================
--- /dev/null
+++ 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: ********************
Index: lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in
===================================================================
--- /dev/null
+++ 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
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -438,6 +438,12 @@
     "provided, that reproducer is dumped.">;
 }
 
+let Command = "reproducer crash" 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.">;
Index: lldb/source/Commands/CommandObjectReproducer.cpp
===================================================================
--- lldb/source/Commands/CommandObjectReproducer.cpp
+++ 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,43 @@
 #define LLDB_OPTIONS_reproducer_dump
 #include "CommandOptions.inc"
 
+enum ReproducerCrashSignal {
+  eReproducerCrashSigbus,
+  eReproducerCrashSigill,
+  eReproducerCrashSigsegv,
+  eReproducerCrashNone,
+};
+
+static constexpr OptionEnumValueElement g_reproducer_signaltype[] = {
+    {
+        eReproducerCrashSigbus,
+        "sigbus",
+        "Bus error",
+    },
+    {
+        eReproducerCrashSigill,
+        "sigill",
+        "Illegal instruction",
+    },
+    {
+        eReproducerCrashSigsegv,
+        "sigsegv",
+        "Segmentation fault",
+    },
+    {
+        eReproducerCrashNone,
+        "none",
+        "None",
+    },
+};
+
+static constexpr OptionEnumValues ReproducerSignalType() {
+  return OptionEnumValues(g_reproducer_signaltype);
+}
+
+#define LLDB_OPTIONS_reproducer_crash
+#include "CommandOptions.inc"
+
 class CommandObjectReproducerGenerate : public CommandObjectParsed {
 public:
   CommandObjectReproducerGenerate(CommandInterpreter &interpreter)
@@ -117,12 +156,99 @@
   }
 };
 
+class CommandObjectReproducerCrash : public CommandObjectParsed {
+public:
+  CommandObjectReproducerCrash(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "reproducer xcrash",
+                            "Intentionally force  the debugger to crash in "
+                            "order to trigger and test reproducer generation.",
+                            nullptr) {}
+
+  ~CommandObjectReproducerCrash() 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 = eReproducerCrashNone;
+    }
+
+    ArrayRef<OptionDefinition> GetDefinitions() override {
+      return makeArrayRef(g_reproducer_crash_options);
+    }
+
+    ReproducerCrashSignal signal = eReproducerCrashNone;
+  };
+
+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;
+    case eReproducerCrashNone:
+      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 +491,8 @@
     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 +509,8 @@
                                new CommandObjectReproducerStatus(interpreter)));
   LoadSubCommand("dump",
                  CommandObjectSP(new CommandObjectReproducerDump(interpreter)));
+  LoadSubCommand(
+      "xcrash", CommandObjectSP(new CommandObjectReproducerCrash(interpreter)));
 }
 
 CommandObjectReproducer::~CommandObjectReproducer() = default;
Index: lldb/source/API/SBReproducer.cpp
===================================================================
--- lldb/source/API/SBReproducer.cpp
+++ lldb/source/API/SBReproducer.cpp
@@ -30,7 +30,7 @@
 using namespace lldb_private::repro;
 
 SBRegistry::SBRegistry() {
-  Registry& R = *this;
+  Registry &R = *this;
 
   RegisterMethods<SBAddress>(R);
   RegisterMethods<SBAttachInfo>(R);
@@ -149,6 +149,22 @@
   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: lldb/include/lldb/API/SBReproducer.h
===================================================================
--- lldb/include/lldb/API/SBReproducer.h
+++ lldb/include/lldb/API/SBReproducer.h
@@ -21,6 +21,8 @@
   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
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to