mib updated this revision to Diff 279638. mib marked 6 inline comments as done. mib added a comment.
Address @JDevlieghere comments. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D82155/new/ https://reviews.llvm.org/D82155 Files: lldb/include/lldb/Interpreter/CommandInterpreter.h lldb/source/Commands/CMakeLists.txt lldb/source/Commands/CommandObjectLog.cpp lldb/source/Commands/CommandObjectQuit.cpp lldb/source/Commands/CommandObjectSession.cpp lldb/source/Commands/CommandObjectSession.h lldb/source/Interpreter/CommandInterpreter.cpp lldb/source/Interpreter/InterpreterProperties.td lldb/test/API/commands/session/save/TestSessionSave.py
Index: lldb/test/API/commands/session/save/TestSessionSave.py =================================================================== --- /dev/null +++ lldb/test/API/commands/session/save/TestSessionSave.py @@ -0,0 +1,70 @@ +""" +Test the session save feature +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class SessionSaveTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + configuration.settings.append(("interpreter.echo-commands", "true")) + configuration.settings.append(("interpreter.echo-comment-commands", "true")) + configuration.settings.append(("interpreter.stop-command-source-on-error", "false")) + TestBase.setUp(self) + + + def raw_transcript_builder(self, cmd, res): + raw = "(lldb) " + cmd + "\n" + if res.GetOutputSize(): + raw += res.GetOutput() + if res.GetErrorSize(): + raw += res.GetError() + return raw + + + @skipIfWindows + @skipIfReproducer + @no_debug_info_test + def test_session_save(self): + raw = "" + inputs = [ + '# This is a comment', # Comment + 'help session', # Valid command + 'Lorem ipsum' # Invalid command + ] + + import tempfile + tf = tempfile.NamedTemporaryFile() + + interpreter = self.dbg.GetCommandInterpreter() + for cmd in inputs: + res = lldb.SBCommandReturnObject() + interpreter.HandleCommand(cmd, res) + raw += self.raw_transcript_builder(cmd, res) + + self.assertTrue(interpreter.HasCommands()) + self.assertTrue(len(raw) != 0) + + # Check for error + cmd = 'session save /root/file' + interpreter.HandleCommand(cmd, res) + self.assertFalse(res.Succeeded()) + raw += self.raw_transcript_builder(cmd, res) + + output_file = tf.name + + res = lldb.SBCommandReturnObject() + interpreter.HandleCommand('session save ' + output_file, res) + self.assertTrue(res.Succeeded()) + raw += self.raw_transcript_builder(cmd, res) + + with open(output_file, "r") as file: + content = file.read() + for line in raw.splitlines(): + self.assertIn(line, content) Index: lldb/source/Interpreter/InterpreterProperties.td =================================================================== --- lldb/source/Interpreter/InterpreterProperties.td +++ 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, Index: lldb/source/Interpreter/CommandInterpreter.cpp =================================================================== --- lldb/source/Interpreter/CommandInterpreter.cpp +++ lldb/source/Interpreter/CommandInterpreter.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include <limits> #include <memory> #include <stdlib.h> #include <string> @@ -31,6 +32,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 +54,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 +78,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 +121,7 @@ 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_transcript_stream() { 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"] = @@ -1667,6 +1684,8 @@ else add_to_history = (lazy_add_to_history == eLazyBoolYes); + m_transcript_stream << "(lldb) " << command_line << '\n'; + bool empty_command = false; bool comment_command = false; if (command_string.empty()) @@ -1799,6 +1818,9 @@ LLDB_LOGF(log, "HandleCommand, command %s", (result.Succeeded() ? "succeeded" : "did not succeed")); + m_transcript_stream << result.GetOutputData(); + m_transcript_stream << result.GetErrorData(); + return result.Succeeded(); } @@ -2877,6 +2899,51 @@ return false; } +bool CommandInterpreter::SaveTranscript( + CommandReturnObject &result, llvm::Optional<std::string> output_file) { + if (output_file == llvm::None || output_file->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(); + } + + auto error_out = [&](llvm::StringRef error_message, std::string description) { + LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_COMMANDS), "{0} ({1}:{2})", + error_message, output_file, description); + result.AppendErrorWithFormatv( + "Failed to save session's transcripts to {0}!", *output_file); + return false; + }; + + File::OpenOptions flags = File::eOpenOptionWrite | + File::eOpenOptionCanCreate | + File::eOpenOptionTruncate; + + auto opened_file = FileSystem::Instance().Open(FileSpec(*output_file), flags); + + if (!opened_file) + return error_out("Unable to create file", + llvm::toString(opened_file.takeError())); + + FileUP file = std::move(opened_file.get()); + + size_t byte_size = m_transcript_stream.GetSize(); + + Status error = file->Write(m_transcript_stream.GetData(), byte_size); + + if (error.Fail() || byte_size != m_transcript_stream.GetSize()) + return error_out("Unable to write to destination file", + "Bytes written do not match transcript size."); + + 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(); Index: lldb/source/Commands/CommandObjectSession.h =================================================================== --- /dev/null +++ lldb/source/Commands/CommandObjectSession.h @@ -0,0 +1,23 @@ +//===-- 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 { + +class CommandObjectSession : public CommandObjectMultiword { +public: + CommandObjectSession(CommandInterpreter &interpreter); +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H Index: lldb/source/Commands/CommandObjectSession.cpp =================================================================== --- /dev/null +++ lldb/source/Commands/CommandObjectSession.cpp @@ -0,0 +1,53 @@ +#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: + 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; + arg1.emplace_back(eArgTypePath, eArgRepeatOptional); + 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(); + + if (m_interpreter.SaveTranscript(result, file_path.str())) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } +}; + +CommandObjectSession::CommandObjectSession(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "session", + "Commands controlling LLDB session.", + "session <subcommand> [<command-options>]") { + LoadSubCommand("save", + CommandObjectSP(new CommandObjectSessionSave(interpreter))); + // TODO: Move 'history' subcommand from CommandObjectCommands. +} Index: lldb/source/Commands/CommandObjectQuit.cpp =================================================================== --- lldb/source/Commands/CommandObjectQuit.cpp +++ lldb/source/Commands/CommandObjectQuit.cpp @@ -72,6 +72,13 @@ } } + if (m_interpreter.GetSaveSessionOnQuit()) { + if (!m_interpreter.SaveTranscript(result)) { + result.SetStatus(eReturnStatusFailed); + return false; + } + } + if (command.GetArgumentCount() > 1) { result.AppendError("Too many arguments for 'quit'. Only an optional exit " "code is allowed"); Index: lldb/source/Commands/CommandObjectLog.cpp =================================================================== --- lldb/source/Commands/CommandObjectLog.cpp +++ lldb/source/Commands/CommandObjectLog.cpp @@ -87,6 +87,10 @@ switch (short_option) { case 'f': + if (log_file.IsResolved()) { + llvm::Twine error_message = "Warning: logging output file " + log_file.GetPath() + " will be replaced by " + option_arg; + error.SetErrorString(error_message.str()); + } log_file.SetFile(option_arg, FileSpec::Style::native); FileSystem::Instance().Resolve(log_file); break; Index: lldb/source/Commands/CMakeLists.txt =================================================================== --- lldb/source/Commands/CMakeLists.txt +++ 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 Index: lldb/include/lldb/Interpreter/CommandInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/CommandInterpreter.h +++ lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -20,6 +20,7 @@ #include "lldb/Utility/CompletionRequest.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" #include "lldb/Utility/StringList.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private.h" @@ -485,9 +486,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 +529,18 @@ bool GetSpaceReplPrompts() const; + /// Save the current debugger session transcript to a file on disk. + /// \param output_file + /// The file path to which the session transcript will be written. Since + /// the argument is optional, an arbitrary temporary file will be create + /// when no argument is passed. + /// \param result + /// This is used to pass function output and error messages. + /// \return \b true if the session transcript was successfully written to + /// disk, \b false otherwise. + bool SaveTranscript(CommandReturnObject &result, + llvm::Optional<std::string> output_file = llvm::None); + protected: friend class Debugger; @@ -621,6 +636,8 @@ llvm::Optional<int> m_quit_exit_code; // If the driver is accepts custom exit codes for the 'quit' command. bool m_allow_exit_code = false; + + StreamString m_transcript_stream; }; } // namespace lldb_private
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits