mib updated this revision to Diff 279505.
mib retitled this revision from "[WIP][lldb/interpreter] Add ability to save 
lldb session to a file" to "[lldb/interpreter] Add ability to save lldb session 
to a file".
mib added a comment.

- Address previous comments
- Add test


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/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,73 @@
+"""
+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 pdb
+#        pdb.set_trace()
+
+        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,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() {
   SetEventName(eBroadcastBitThreadShouldExit, "thread-should-exit");
   SetEventName(eBroadcastBitResetPrompt, "reset-prompt");
   SetEventName(eBroadcastBitQuitCommandReceived, "quit");
@@ -142,6 +148,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 +510,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 +1685,8 @@
   else
     add_to_history = (lazy_add_to_history == eLazyBoolYes);
 
+  m_session_transcripts << "(lldb) " << command_line << '\n';
+
   bool empty_command = false;
   bool comment_command = false;
   if (command_string.empty())
@@ -1799,6 +1819,9 @@
   LLDB_LOGF(log, "HandleCommand, command %s",
             (result.Succeeded() ? "succeeded" : "did not succeed"));
 
+  m_session_transcripts << result.GetOutputData();
+  m_session_transcripts << result.GetErrorData();
+
   return result.Succeeded();
 }
 
@@ -2877,6 +2900,49 @@
   return false;
 }
 
+bool CommandInterpreter::SaveTranscripts(std::string output_file) {
+  if (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);
+    StreamSP error_stream = GetDebugger().GetErrorStreamSP();
+    *error_stream << "Failed to save session's transcripts to " << output_file
+                  << "!" << '\n';
+    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_session_transcripts.GetSize();
+
+    Status error = file->Write(m_session_transcripts.GetData(), byte_size);
+
+    if (error.Fail() || byte_size != m_session_transcripts.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,25 @@
+//===-- 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:
+  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,62 @@
+#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;
+    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();
+
+    if (m_interpreter.SaveTranscripts(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.SaveTranscripts("")) {
+      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/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,8 @@
 
   bool GetSpaceReplPrompts() const;
 
+  bool SaveTranscripts(std::string file_path);
+
 protected:
   friend class Debugger;
 
@@ -621,6 +626,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_session_transcripts;
 };
 
 } // namespace lldb_private
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to