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: //------------------------------------------------------------------ @@ -136,6 +137,12 @@ m_add_to_history = add_to_history ? eLazyBoolYes : eLazyBoolNo; } + bool GetAddToReproducer() const { return DefaultToYes(m_add_to_reproducer); } + + void SetAddToReproducer(bool add_to_reproducer) { + m_add_to_reproducer = add_to_reproducer ? eLazyBoolYes : eLazyBoolNo; + } + LazyBool m_stop_on_continue; LazyBool m_stop_on_error; LazyBool m_stop_on_crash; @@ -143,6 +150,7 @@ LazyBool m_echo_comment_commands; LazyBool m_print_results; LazyBool m_add_to_history; + LazyBool m_add_to_reproducer; private: static bool DefaultToYes(LazyBool flag) { @@ -204,6 +212,8 @@ return GetStaticBroadcasterClass(); } + void ReplayCommands(CommandReturnObject &result); + void SourceInitFile(bool in_cwd, CommandReturnObject &result); bool AddCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp, @@ -251,7 +261,8 @@ CommandReturnObject &result, ExecutionContext *override_context = nullptr, bool repeat_on_empty_command = true, - bool no_context_switching = false); + bool no_context_switching = false, + bool add_to_reproducer = false); bool WasInterrupted() const; @@ -607,6 +618,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/Inputs/CommandCapture.in =================================================================== --- /dev/null +++ lit/Reproducer/Inputs/CommandCapture.in @@ -0,0 +1,6 @@ +breakpoint set -f simple.c -l 13 +run +bt +cont +reproducer status +reproducer generate Index: lit/Reproducer/TestCommandRepro.test =================================================================== --- /dev/null +++ lit/Reproducer/TestCommandRepro.test @@ -0,0 +1,39 @@ +# UNSUPPORTED: system-windows, system-freebsd + +# RUN: rm -rf %T/commands +# RUN: %clang %S/Inputs/simple.c -g -o %t.out + +# This tests the replaying of commands. +# +# 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/commands -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE +# RUN: %lldb -x -b --replay %T/commands -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix REPLAY +# RUN: %lldb -x -b --replay %T/commands | FileCheck %s --check-prefix CHECK --check-prefix REPLAY + +# RUN: rm -rf %T/commands + +# Test that sourced files are not captured double. +# +# RUN: %lldb -x -b -s %S/Inputs/CommandCapture.in --capture %T/commands -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix CAPTURE +# RUN: %lldb -x -b --replay %T/commands -- %t.out | FileCheck %s --check-prefix CHECK --check-prefix REPLAY + +# This tests that errors are printed. +# +# RUN: echo "bogus" >> %T/commands/command-interpreter.txt +# RUN: %lldb -x -b --replay %T/commands 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" @@ -929,8 +930,13 @@ void SBDebugger::RunCommandInterpreter(bool auto_handle_events, bool spawn_thread) { if (m_opaque_sp) { - CommandInterpreterRunOptions options; + // If there's a loader we're in replay mode. + if (repro::Reproducer::Instance().GetLoader()) { + m_opaque_sp->RunReplay(); + return; + } + CommandInterpreterRunOptions options; m_opaque_sp->GetCommandInterpreter().RunCommandInterpreter( auto_handle_events, spawn_thread, options); } @@ -945,6 +951,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/CommandObjectCommands.cpp =================================================================== --- source/Commands/CommandObjectCommands.cpp +++ source/Commands/CommandObjectCommands.cpp @@ -316,6 +316,7 @@ CommandInterpreterRunOptions options; options.SetStopOnContinue(m_options.m_stop_on_continue.GetCurrentValue()); options.SetStopOnError(m_options.m_stop_on_error.GetCurrentValue()); + options.SetAddToReproducer(false); // Individual silent setting is override for global command echo settings. if (m_options.m_silent_run.GetCurrentValue()) { @@ -331,6 +332,7 @@ // No options were set, inherit any settings from nested "command source" // commands, or set to sane default settings... CommandInterpreterRunOptions options; + options.SetAddToReproducer(false); m_interpreter.HandleCommandsFromFile(cmd_file, exe_ctx, options, result); } return result.Succeeded(); 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 { @@ -1589,7 +1630,8 @@ CommandReturnObject &result, ExecutionContext *override_context, bool repeat_on_empty_command, - bool no_context_switching) + bool no_context_switching, + bool add_to_reproducer) { @@ -1679,6 +1721,9 @@ Status error(PreprocessCommand(command_string)); + if (m_provider && add_to_reproducer) + m_provider->CaptureCommand(original_command_string); + if (error.Fail()) { result.AppendError(error.AsCString()); result.SetStatus(eReturnStatusFailed); @@ -2074,6 +2119,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; @@ -2160,6 +2244,7 @@ options.SetSilent(true); options.SetStopOnError(false); options.SetStopOnContinue(true); + options.SetAddToReproducer(false); HandleCommandsFromFile(init_file, nullptr, // Execution context @@ -2237,7 +2322,8 @@ HandleCommand(cmd, options.m_add_to_history, tmp_result, nullptr, /* override_context */ true, /* repeat_on_empty_command */ - override_context != nullptr /* no_context_switching */); + override_context != nullptr /* no_context_switching */, + options.GetAddToReproducer()); if (!options.GetAddToHistory()) m_command_source_depth--; @@ -2349,7 +2435,8 @@ eHandleCommandFlagEchoCommand = (1u << 2), eHandleCommandFlagEchoCommentCommand = (1u << 3), eHandleCommandFlagPrintResult = (1u << 4), - eHandleCommandFlagStopOnCrash = (1u << 5) + eHandleCommandFlagStopOnCrash = (1u << 5), + eHandleCommandFlagAddToReproducer = (1u << 6) }; void CommandInterpreter::HandleCommandsFromFile( @@ -2438,6 +2525,10 @@ flags |= eHandleCommandFlagPrintResult; } + if (options.GetAddToReproducer()) { + flags |= eHandleCommandFlagAddToReproducer; + } + if (flags & eHandleCommandFlagPrintResult) { debugger.GetOutputFile()->Printf("Executing commands in '%s'.\n", cmd_file_path.c_str()); @@ -2788,7 +2879,8 @@ StartHandlingCommand(); lldb_private::CommandReturnObject result; - HandleCommand(line.c_str(), eLazyBoolCalculate, result); + HandleCommand(line.c_str(), eLazyBoolCalculate, result, nullptr, true, false, + io_handler.GetFlags().Test(eHandleCommandFlagAddToReproducer)); // Now emit the command output text from the command we just executed if (io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) { @@ -2958,6 +3050,8 @@ flags |= eHandleCommandFlagEchoCommentCommand; if (options->m_print_results != eLazyBoolNo) flags |= eHandleCommandFlagPrintResult; + if (options->m_add_to_reproducer != eLazyBoolNo) + flags |= eHandleCommandFlagAddToReproducer; } else { flags = eHandleCommandFlagEchoCommand | eHandleCommandFlagPrintResult; } Index: tools/driver/Driver.h =================================================================== --- tools/driver/Driver.h +++ tools/driver/Driver.h @@ -100,6 +100,7 @@ bool m_wait_for = false; bool m_repl = false; bool m_batch = false; + bool m_replay = false; // FIXME: When we have set/show variables we can remove this from here. bool m_use_external_editor = false; Index: tools/driver/Driver.cpp =================================================================== --- tools/driver/Driver.cpp +++ tools/driver/Driver.cpp @@ -286,6 +286,10 @@ m_option_data.m_core_file = arg_value; } + if (args.hasArg(OPT_replay)) { + m_option_data.m_replay = true; + } + if (args.hasArg(OPT_editor)) { m_option_data.m_use_external_editor = true; } @@ -677,6 +681,8 @@ else WithColor::error() << error.GetError() << '\n'; } + } else if (m_option_data.m_replay) { + m_debugger.RunCommandInterpreter(handle_events, spawn_thread); } else { // Check if we have any data in the commands stream, and if so, save it to a // temp file