Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion lldb/include/lldb/Interpreter/CommandInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -485,9 +486,11 @@ class CommandInterpreter : public Broadcaster,
bool GetExpandRegexAliases() const;

bool GetPromptOnQuit() const;

void SetPromptOnQuit(bool enable);

bool GetSaveSessionOnQuit() const;
void SetSaveSessionOnQuit(bool enable);

bool GetEchoCommands() const;
void SetEchoCommands(bool enable);

Expand Down Expand Up @@ -526,6 +529,18 @@ class CommandInterpreter : public Broadcaster,

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;

Expand Down Expand Up @@ -621,6 +636,8 @@ class CommandInterpreter : public Broadcaster,
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
Expand Down
3 changes: 2 additions & 1 deletion lldb/source/Commands/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_lldb_library(lldbCommands
CommandObjectFrame.cpp
CommandObjectGUI.cpp
CommandObjectHelp.cpp
CommandObjectLanguage.cpp
CommandObjectLog.cpp
CommandObjectMemory.cpp
CommandObjectMultiword.cpp
Expand All @@ -22,6 +23,7 @@ add_lldb_library(lldbCommands
CommandObjectQuit.cpp
CommandObjectRegister.cpp
CommandObjectReproducer.cpp
CommandObjectSession.cpp
CommandObjectSettings.cpp
CommandObjectSource.cpp
CommandObjectStats.cpp
Expand All @@ -31,7 +33,6 @@ add_lldb_library(lldbCommands
CommandObjectVersion.cpp
CommandObjectWatchpoint.cpp
CommandObjectWatchpointCommand.cpp
CommandObjectLanguage.cpp

LINK_LIBS
lldbBase
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Commands/CommandObjectQuit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,9 @@ bool CommandObjectQuit::DoExecute(Args &command, CommandReturnObject &result) {
CommandInterpreter::eBroadcastBitQuitCommandReceived;
m_interpreter.BroadcastEvent(event_type);
result.SetStatus(eReturnStatusQuit);

if (m_interpreter.GetSaveSessionOnQuit())
m_interpreter.SaveTranscript(result);

return true;
}
53 changes: 53 additions & 0 deletions lldb/source/Commands/CommandObjectSession.cpp
Original file line number Diff line number Diff line change
@@ -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.
}
23 changes: 23 additions & 0 deletions lldb/source/Commands/CommandObjectSession.h
Original file line number Diff line number Diff line change
@@ -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
69 changes: 68 additions & 1 deletion lldb/source/Interpreter/CommandInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include <limits>
#include <memory>
#include <stdlib.h>
#include <string>
Expand All @@ -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"
Expand All @@ -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"

Expand All @@ -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;
Expand Down Expand Up @@ -116,7 +121,7 @@ CommandInterpreter::CommandInterpreter(Debugger &debugger,
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");
Expand All @@ -142,6 +147,17 @@ void CommandInterpreter::SetPromptOnQuit(bool enable) {
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(
Expand Down Expand Up @@ -493,6 +509,7 @@ void CommandInterpreter::LoadCommandDictionary() {
CommandObjectSP(new CommandObjectReproducer(*this));
m_command_dict["script"] =
CommandObjectSP(new CommandObjectScript(*this, script_language));
m_command_dict["session"] = std::make_shared<CommandObjectSession>(*this);
m_command_dict["settings"] =
CommandObjectSP(new CommandObjectMultiwordSettings(*this));
m_command_dict["source"] =
Expand Down Expand Up @@ -1667,6 +1684,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
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())
Expand Down Expand Up @@ -1799,6 +1818,9 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
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();
}

Expand Down Expand Up @@ -2877,6 +2899,51 @@ bool CommandInterpreter::IOHandlerInterrupt(IOHandler &io_handler) {
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.");

result.AppendMessageWithFormat("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();
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Interpreter/InterpreterProperties.td
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ let Definition = "interpreter" in {
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,
Expand Down
74 changes: 74 additions & 0 deletions lldb/test/API/commands/session/save/TestSessionSave.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
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 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 = ""
interpreter = self.dbg.GetCommandInterpreter()

settings = [
'settings set interpreter.echo-commands true',
'settings set interpreter.echo-comment-commands true',
'settings set interpreter.stop-command-source-on-error false'
]

for setting in settings:
interpreter.HandleCommand(setting, lldb.SBCommandReturnObject())

inputs = [
'# This is a comment', # Comment
'help session', # Valid command
'Lorem ipsum' # Invalid command
]

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)

import tempfile
tf = tempfile.NamedTemporaryFile()
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()
# Exclude last line, since session won't record it's own output
lines = raw.splitlines()[:-1]
for line in lines:
self.assertIn(line, content)