summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Devlieghere <jonas@devlieghere.com>2019-11-19 17:28:46 -0800
committerJonas Devlieghere <jonas@devlieghere.com>2019-11-20 13:14:16 -0800
commitc8dfe907299e16aeb17175cb0896c17043fc7c81 (patch)
tree85aa1fcf033d71c0243f3efec47e593a5ba0818e
parent824b25fc02dc4544adae55e6451d355f4c6d7055 (diff)
downloadbcm5719-llvm-c8dfe907299e16aeb17175cb0896c17043fc7c81.tar.gz
bcm5719-llvm-c8dfe907299e16aeb17175cb0896c17043fc7c81.zip
[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
-rw-r--r--lldb/include/lldb/API/SBReproducer.h2
-rw-r--r--lldb/source/API/SBReproducer.cpp18
-rw-r--r--lldb/source/Commands/CommandObjectReproducer.cpp126
-rw-r--r--lldb/source/Commands/Options.td6
-rw-r--r--lldb/test/Shell/Reproducer/Inputs/GDBRemoteCrashCapture.in6
-rw-r--r--lldb/test/Shell/Reproducer/TestCrash.test14
-rw-r--r--lldb/test/Shell/Reproducer/TestGDBRemoteRepro.test9
-rw-r--r--lldb/tools/driver/Driver.cpp17
8 files changed, 194 insertions, 4 deletions
diff --git a/lldb/include/lldb/API/SBReproducer.h b/lldb/include/lldb/API/SBReproducer.h
index 0f1739d0c5b..93e567607aa 100644
--- a/lldb/include/lldb/API/SBReproducer.h
+++ b/lldb/include/lldb/API/SBReproducer.h
@@ -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
diff --git a/lldb/source/API/SBReproducer.cpp b/lldb/source/API/SBReproducer.cpp
index 6e11b2c6366..d50d95ebb54 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 a22c704ebd0..7f97ba2875c 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 @@ 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;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 501f81470a2..f53d1481f4e 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 00000000000..1a733f4dd94
--- /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 00000000000..1f26a4ebf03
--- /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 04a3e5465bb..609c8392929 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 3a22d7aabc1..9d685f2e35b 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()
OpenPOWER on IntegriCloud