diff --git a/lldb/include/lldb/API/SBReproducer.h b/lldb/include/lldb/API/SBReproducer.h --- a/lldb/include/lldb/API/SBReproducer.h +++ b/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 diff --git a/lldb/source/API/SBReproducer.cpp b/lldb/source/API/SBReproducer.cpp --- a/lldb/source/API/SBReproducer.cpp +++ b/lldb/source/API/SBReproducer.cpp @@ -30,7 +30,7 @@ using namespace lldb_private::repro; SBRegistry::SBRegistry() { - Registry& R = *this; + Registry &R = *this; RegisterMethods(R); RegisterMethods(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"; diff --git a/lldb/source/Commands/CommandObjectReproducer.cpp b/lldb/source/Commands/CommandObjectReproducer.cpp --- 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 + using namespace lldb; using namespace llvm; using namespace lldb_private; @@ -71,6 +73,37 @@ #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 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 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 @@ 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 @@ 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 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -438,6 +438,12 @@ "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 --- /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 --- /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 --- 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 --- a/lldb/tools/driver/Driver.cpp +++ b/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()