Index: include/lldb/Core/Debugger.h =================================================================== --- include/lldb/Core/Debugger.h +++ include/lldb/Core/Debugger.h @@ -317,6 +317,8 @@ bool IsHandlingEvents() const { return m_event_handler_thread.IsJoinable(); } + void RunReplay(); + Status RunREPL(lldb::LanguageType language, const char *repl_options); // This is for use in the command interpreter, when you either want the Index: include/lldb/Interpreter/CommandInterpreter.h =================================================================== --- include/lldb/Interpreter/CommandInterpreter.h +++ include/lldb/Interpreter/CommandInterpreter.h @@ -4,7 +4,6 @@ // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. -// //===----------------------------------------------------------------------===// #ifndef liblldb_CommandInterpreter_h_ @@ -28,6 +27,8 @@ namespace lldb_private { +class CommandProvider; + class CommandInterpreterRunOptions { public: //------------------------------------------------------------------ @@ -204,6 +205,8 @@ return GetStaticBroadcasterClass(); } + void ReplayCommands(CommandReturnObject &result); + void SourceInitFile(bool in_cwd, CommandReturnObject &result); bool AddCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp, @@ -607,6 +610,9 @@ bool m_quit_requested; bool m_stopped_for_crash; + // Reproducer provider. + CommandProvider *m_provider = nullptr; + // The exit code the user has requested when calling the 'quit' command. // No value means the user hasn't set a custom exit code so far. llvm::Optional m_quit_exit_code; Index: lit/Reproducer/TestCommandRepro.test =================================================================== --- /dev/null +++ lit/Reproducer/TestCommandRepro.test @@ -0,0 +1,30 @@ +# UNSUPPORTED: system-windows, system-freebsd + +# This tests the replaying of commands. +# +# RUN: %clang %S/Inputs/simple.c -g -o %t.out +# RUN: %lldb -x -b -o 'breakpoint set -f simple.c -l 13' -o 'run' -o 'bt' -o 'cont' -o 'reproducer status' -o 'reproducer generate' --capture %T/reproducer -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE +# RUN: %lldb -x -b --replay %T/reproducer -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix REPLAY +# RUN: %lldb -x -b --replay %T/reproducer | FileCheck %s --check-prefix CHECK --check-prefix REPLAY + +# This tests that errors are printed. +# +# RUN: echo "bogus" >> %T/reproducer/command-interpreter.txt +# RUN: %lldb -x -b --replay %T/reproducer 2>&1 | FileCheck %s --check-prefix CHECK --check-prefix REPLAY --check-prefix BOGUS + +# CHECK: Breakpoint 1 +# CHECK: Process {{.*}} stopped +# CHECK: Process {{.*}} launched +# CHECK: thread {{.*}} stop reason = breakpoint +# CHECK: frame {{.*}} simple.c + +# CAPTURE: testing +# REPLAY-NOT: testing + +# CHECK: Process {{.*}} resuming +# CHECK: Process {{.*}} exited + +# CAPTURE: Reproducer is in capture mode. +# CAPTURE: Reproducer written + +# BOGUS: error: 'bogus' is not a valid command. Index: source/API/SBDebugger.cpp =================================================================== --- source/API/SBDebugger.cpp +++ source/API/SBDebugger.cpp @@ -48,6 +48,7 @@ #include "lldb/Target/Process.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/Args.h" +#include "lldb/Utility/Reproducer.h" #include "lldb/Utility/State.h" #include "llvm/ADT/STLExtras.h" @@ -945,6 +946,13 @@ { if (m_opaque_sp) { CommandInterpreter &interp = m_opaque_sp->GetCommandInterpreter(); + + // If there's a loader we're in replay mode. + if (repro::Reproducer::Instance().GetLoader()) { + m_opaque_sp->RunReplay(); + return; + } + interp.RunCommandInterpreter(auto_handle_events, spawn_thread, options.ref()); num_errors = interp.GetNumErrors(); Index: source/Commands/CommandObjectReproducer.cpp =================================================================== --- source/Commands/CommandObjectReproducer.cpp +++ source/Commands/CommandObjectReproducer.cpp @@ -38,8 +38,13 @@ auto &r = repro::Reproducer::Instance(); if (auto generator = r.GetGenerator()) { generator->Keep(); + } else if (r.GetLoader()) { + // Make this operation a NOP in replay mode. + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return result.Succeeded(); } else { result.AppendErrorWithFormat("Unable to get the reproducer generator"); + result.SetStatus(eReturnStatusFailed); return false; } Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -26,6 +26,7 @@ #include "lldb/Host/Terminal.h" #include "lldb/Host/ThreadLauncher.h" #include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionValue.h" #include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Interpreter/OptionValueSInt64.h" @@ -1692,6 +1693,16 @@ return GetDummyTarget(); } +void Debugger::RunReplay() { + // Create a command return object and hook up its streams. + CommandReturnObject result; + result.SetImmediateOutputStream(GetOutputFile()); + result.SetImmediateErrorStream(GetErrorFile()); + + // Replay commands from reproducer. + GetCommandInterpreter().ReplayCommands(result); +} + Status Debugger::RunREPL(LanguageType language, const char *repl_options) { Status err; FileSpec repl_executable; Index: source/Interpreter/CommandInterpreter.cpp =================================================================== --- source/Interpreter/CommandInterpreter.cpp +++ source/Interpreter/CommandInterpreter.cpp @@ -45,6 +45,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/StreamFile.h" #include "lldb/Utility/Log.h" +#include "lldb/Utility/Reproducer.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/Timer.h" @@ -74,6 +75,7 @@ using namespace lldb; using namespace lldb_private; +using namespace llvm; static const char *k_white_space = " \t\v"; @@ -116,6 +118,42 @@ eEchoCommentCommands = 5 }; +class lldb_private::CommandProvider + : public repro::Provider { +public: + CommandProvider(const FileSpec &directory) : Provider(directory) { + m_info.name = "command-interpreter"; + m_info.files.push_back("command-interpreter.txt"); + } + + void CaptureCommand(std::string command) { + m_commands.push_back(std::move(command)); + } + + void Keep() override { + FileSpec file = + GetRoot().CopyByAppendingPathComponent("command-interpreter.txt"); + + std::error_code ec; + llvm::raw_fd_ostream os(file.GetPath(), ec, llvm::sys::fs::F_Text); + + if (ec) + return; + + for (auto &command : m_commands) + os << command << '\n'; + } + + void Discard() override {} + + static char ID; + +private: + std::vector m_commands; +}; + +char CommandProvider::ID = 0; + ConstString &CommandInterpreter::GetStaticBroadcasterClass() { static ConstString class_name("lldb.commandInterpreter"); return class_name; @@ -141,6 +179,9 @@ SetEventName(eBroadcastBitQuitCommandReceived, "quit"); CheckInWithManager(); m_collection_sp->Initialize(g_properties); + + if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) + m_provider = &g->GetOrCreate(); } bool CommandInterpreter::GetExpandRegexAliases() const { @@ -1679,6 +1720,9 @@ Status error(PreprocessCommand(command_string)); + if (m_provider) + m_provider->CaptureCommand(original_command_string); + if (error.Fail()) { result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); @@ -2074,6 +2118,45 @@ return position; } +void CommandInterpreter::ReplayCommands(CommandReturnObject &result) { + repro::Loader *loader = repro::Reproducer::Instance().GetLoader(); + if (!loader) { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return; + } + + auto provider_info = loader->GetProviderInfo("command-interpreter"); + if (!provider_info) { + result.AppendErrorWithFormat("no provider for command interpreter."); + result.SetStatus(eReturnStatusFailed); + return; + } + + if (provider_info->files.empty()) { + result.AppendErrorWithFormat( + "provider for command interpreter contains no files."); + result.SetStatus(eReturnStatusFailed); + return; + } + + FileSpec command_file = loader->GetRoot().CopyByAppendingPathComponent( + provider_info->files.front()); + + // Remember current batch mode. + const bool saved_batch = SetBatchCommandMode(true); + + CommandInterpreterRunOptions options; + options.SetSilent(false); + options.SetStopOnError(false); + options.SetStopOnContinue(false); + + // Run the commands in a new execution context. + HandleCommandsFromFile(command_file, nullptr, options, result); + + // Reset current batch mode. + SetBatchCommandMode(saved_batch); +} + void CommandInterpreter::SourceInitFile(bool in_cwd, CommandReturnObject &result) { FileSpec init_file;