diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -485,9 +485,11 @@ bool GetExpandRegexAliases() const; bool GetPromptOnQuit() const; - void SetPromptOnQuit(bool enable); + bool GetSaveSessionOnQuit() const; + void SetSaveSessionOnQuit(bool enable); + bool GetEchoCommands() const; void SetEchoCommands(bool enable); @@ -526,6 +528,8 @@ bool GetSpaceReplPrompts() const; + bool SaveTranscripts(llvm::StringRef file_path); + protected: friend class Debugger; @@ -621,6 +625,8 @@ llvm::Optional m_quit_exit_code; // If the driver is accepts custom exit codes for the 'quit' command. bool m_allow_exit_code = false; + + lldb::StreamSP m_session_transcripts; }; } // namespace lldb_private diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -13,6 +13,7 @@ CommandObjectFrame.cpp CommandObjectGUI.cpp CommandObjectHelp.cpp + CommandObjectLanguage.cpp CommandObjectLog.cpp CommandObjectMemory.cpp CommandObjectMultiword.cpp @@ -22,6 +23,7 @@ CommandObjectQuit.cpp CommandObjectRegister.cpp CommandObjectReproducer.cpp + CommandObjectSession.cpp CommandObjectSettings.cpp CommandObjectSource.cpp CommandObjectStats.cpp @@ -31,7 +33,6 @@ CommandObjectVersion.cpp CommandObjectWatchpoint.cpp CommandObjectWatchpointCommand.cpp - CommandObjectLanguage.cpp LINK_LIBS lldbBase diff --git a/lldb/source/Commands/CommandObjectQuit.cpp b/lldb/source/Commands/CommandObjectQuit.cpp --- a/lldb/source/Commands/CommandObjectQuit.cpp +++ b/lldb/source/Commands/CommandObjectQuit.cpp @@ -72,6 +72,13 @@ } } + if (m_interpreter.GetSaveSessionOnQuit()) { + if (!m_interpreter.SaveTranscripts("")) { + result.SetStatus(eReturnStatusFailed); + return false; + } + } + if (command.GetArgumentCount() > 1) { result.AppendError("Too many arguments for 'quit'. Only an optional exit " "code is allowed"); diff --git a/lldb/source/Commands/CommandObjectSession.h b/lldb/source/Commands/CommandObjectSession.h new file mode 100644 --- /dev/null +++ b/lldb/source/Commands/CommandObjectSession.h @@ -0,0 +1,33 @@ +//===-- CommandObjectSession.h ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H +#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H + +#include "lldb/Interpreter/CommandObjectMultiword.h" + +namespace lldb_private { + +// CommandObjectSession + +class CommandObjectSession : public CommandObjectMultiword { +public: + // Constructors and Destructors + CommandObjectSession(CommandInterpreter &interpreter); + + ~CommandObjectSession() override; + +private: + // For CommandObjectSession only + CommandObjectSession(const CommandObjectSession &) = delete; + const CommandObjectSession &operator=(const CommandObjectSession &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H diff --git a/lldb/source/Commands/CommandObjectSession.cpp b/lldb/source/Commands/CommandObjectSession.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Commands/CommandObjectSession.cpp @@ -0,0 +1,67 @@ +#include "CommandObjectSession.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" + +using namespace lldb; +using namespace lldb_private; + +class CommandObjectSessionSave : public CommandObjectParsed { +public: + // Constructors and Destructors + CommandObjectSessionSave(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "session save", + "Save the current session transcripts to a file.\n" + "If no file if specified, transcripts will be " + "saved to a temporary file.", + "session save [file]") { + CommandArgumentEntry arg1; + CommandArgumentData channel_arg; + + // Define the first (and only) variant of this arg. + channel_arg.arg_type = eArgTypePath; + channel_arg.arg_repetition = eArgRepeatOptional; + + // There is only one variant this argument could be; put it into the + // argument entry. + arg1.push_back(channel_arg); + + m_arguments.push_back(arg1); + } + + ~CommandObjectSessionSave() override = default; + + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, + request, nullptr); + } + +protected: + bool DoExecute(Args &args, CommandReturnObject &result) override { + llvm::StringRef file_path = ""; + + if (!args.empty()) + file_path = args[0].ref(); + + bool success = m_interpreter.SaveTranscripts(file_path); + + if (success) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } +}; + +CommandObjectSession::CommandObjectSession(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "session", + "Commands controlling LLDB session.", + "session []") { + LoadSubCommand("save", + CommandObjectSP(new CommandObjectSessionSave(interpreter))); + // TODO: Move 'history' subcommand from CommandObjectCommands +} + +CommandObjectSession::~CommandObjectSession() = default; diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -31,6 +31,7 @@ #include "Commands/CommandObjectQuit.h" #include "Commands/CommandObjectRegister.h" #include "Commands/CommandObjectReproducer.h" +#include "Commands/CommandObjectSession.h" #include "Commands/CommandObjectSettings.h" #include "Commands/CommandObjectSource.h" #include "Commands/CommandObjectStats.h" @@ -52,6 +53,8 @@ #if LLDB_ENABLE_LIBEDIT #include "lldb/Host/Editline.h" #endif +#include "lldb/Host/File.h" +#include "lldb/Host/FileCache.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" @@ -74,6 +77,7 @@ #include "llvm/Support/FormatAdapters.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/ScopedPrinter.h" using namespace lldb; using namespace lldb_private; @@ -116,7 +120,8 @@ m_skip_lldbinit_files(false), m_skip_app_init_files(false), m_command_io_handler_sp(), m_comment_char('#'), m_batch_command_mode(false), m_truncation_warning(eNoTruncation), - m_command_source_depth(0), m_result() { + m_command_source_depth(0), m_result(), + m_session_transcripts(new StreamString()) { SetEventName(eBroadcastBitThreadShouldExit, "thread-should-exit"); SetEventName(eBroadcastBitResetPrompt, "reset-prompt"); SetEventName(eBroadcastBitQuitCommandReceived, "quit"); @@ -142,6 +147,17 @@ m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, enable); } +bool CommandInterpreter::GetSaveSessionOnQuit() const { + const uint32_t idx = ePropertySaveSessionOnQuit; + return m_collection_sp->GetPropertyAtIndexAsBoolean( + nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0); +} + +void CommandInterpreter::SetSaveSessionOnQuit(bool enable) { + const uint32_t idx = ePropertySaveSessionOnQuit; + m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, enable); +} + bool CommandInterpreter::GetEchoCommands() const { const uint32_t idx = ePropertyEchoCommands; return m_collection_sp->GetPropertyAtIndexAsBoolean( @@ -493,6 +509,7 @@ CommandObjectSP(new CommandObjectReproducer(*this)); m_command_dict["script"] = CommandObjectSP(new CommandObjectScript(*this, script_language)); + m_command_dict["session"] = CommandObjectSP(new CommandObjectSession(*this)); m_command_dict["settings"] = CommandObjectSP(new CommandObjectMultiwordSettings(*this)); m_command_dict["source"] = @@ -2795,6 +2812,21 @@ lldb_private::CommandReturnObject result(m_debugger.GetUseColor()); HandleCommand(line.c_str(), eLazyBoolCalculate, result); + if (line.find("session save") == line.npos) { + *m_session_transcripts << io_handler.GetPrompt() << ' ' << line.c_str() + << '\n'; + + llvm::StringRef result_output = result.GetOutputData(); + if (!result_output.empty()) + *m_session_transcripts << result_output; + + llvm::StringRef result_error = result.GetErrorData(); + if (!result_error.empty()) + *m_session_transcripts << result_error; + + m_session_transcripts->Flush(); + } + // Now emit the command output text from the command we just executed if ((result.Succeeded() && io_handler.GetFlags().Test(eHandleCommandFlagPrintResult)) || @@ -2877,6 +2909,53 @@ return false; } +bool CommandInterpreter::SaveTranscripts(llvm::StringRef file_path) { + std::string output_file = file_path.str(); + + if (file_path.empty()) { + std::string now = llvm::to_string(std::chrono::system_clock::now()); + std::replace(now.begin(), now.end(), ' ', '_'); + const std::string file_name = "lldb_session_" + now + ".log"; + FileSpec tmp = HostInfo::GetGlobalTempDir(); + tmp.AppendPathComponent(file_name); + output_file = tmp.GetPath(); + } + + File::OpenOptions flags = File::eOpenOptionWrite | + File::eOpenOptionCanCreate | + File::eOpenOptionTruncate; + + Status error; + user_id_t fd_dst = FileCache::GetInstance().OpenFile( + FileSpec(output_file), flags, lldb::eFilePermissionsFileDefault, error); + + auto error_out = [&](llvm::StringRef error_message) { + LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_COMMANDS), "{0} ({1})", + error_message, output_file); + StreamSP error_stream = GetDebugger().GetErrorStreamSP(); + *error_stream << "Failed to save session's transcripts to " << output_file + << "!" << '\n'; + return false; + }; + + if (fd_dst == UINT64_MAX || error.Fail()) + return error_out("Unable to create file"); + + auto string_stream = + std::static_pointer_cast(m_session_transcripts); + size_t stream_size = string_stream->GetSize(); + + size_t bytes_written = FileCache::GetInstance().WriteFile( + fd_dst, 0, string_stream->GetData(), stream_size, error); + if (bytes_written != stream_size || error.Fail()) + return error_out("Unable to write to destination file"); + + GetDebugger().GetOutputStreamSP()->Printf( + "Session's transcripts saved to %s\n", output_file.c_str()); + + return true; +} + void CommandInterpreter::GetLLDBCommandsFromIOHandler( const char *prompt, IOHandlerDelegate &delegate, void *baton) { Debugger &debugger = GetDebugger(); diff --git a/lldb/source/Interpreter/InterpreterProperties.td b/lldb/source/Interpreter/InterpreterProperties.td --- a/lldb/source/Interpreter/InterpreterProperties.td +++ b/lldb/source/Interpreter/InterpreterProperties.td @@ -9,6 +9,10 @@ Global, DefaultTrue, Desc<"If true, LLDB will prompt you before quitting if there are any live processes being debugged. If false, LLDB will quit without asking in any case.">; + def SaveSessionOnQuit: Property<"save-session-on-quit", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, LLDB will save the session's transcripts before quitting.">; def StopCmdSourceOnError: Property<"stop-command-source-on-error", "Boolean">, Global, DefaultTrue,