Skip to content

Commit 1eac44b

Browse files
author
git apple-llvm automerger
committed
Merge commit '21ccb46f2afc' from apple/stable/20200714 into swift/master-rebranch
2 parents 8c3e1ec + 21ccb46 commit 1eac44b

File tree

8 files changed

+246
-3
lines changed

8 files changed

+246
-3
lines changed

lldb/include/lldb/Interpreter/CommandInterpreter.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "lldb/Utility/CompletionRequest.h"
2121
#include "lldb/Utility/Event.h"
2222
#include "lldb/Utility/Log.h"
23+
#include "lldb/Utility/StreamString.h"
2324
#include "lldb/Utility/StringList.h"
2425
#include "lldb/lldb-forward.h"
2526
#include "lldb/lldb-private.h"
@@ -485,9 +486,11 @@ class CommandInterpreter : public Broadcaster,
485486
bool GetExpandRegexAliases() const;
486487

487488
bool GetPromptOnQuit() const;
488-
489489
void SetPromptOnQuit(bool enable);
490490

491+
bool GetSaveSessionOnQuit() const;
492+
void SetSaveSessionOnQuit(bool enable);
493+
491494
bool GetEchoCommands() const;
492495
void SetEchoCommands(bool enable);
493496

@@ -526,6 +529,18 @@ class CommandInterpreter : public Broadcaster,
526529

527530
bool GetSpaceReplPrompts() const;
528531

532+
/// Save the current debugger session transcript to a file on disk.
533+
/// \param output_file
534+
/// The file path to which the session transcript will be written. Since
535+
/// the argument is optional, an arbitrary temporary file will be create
536+
/// when no argument is passed.
537+
/// \param result
538+
/// This is used to pass function output and error messages.
539+
/// \return \b true if the session transcript was successfully written to
540+
/// disk, \b false otherwise.
541+
bool SaveTranscript(CommandReturnObject &result,
542+
llvm::Optional<std::string> output_file = llvm::None);
543+
529544
protected:
530545
friend class Debugger;
531546

@@ -621,6 +636,8 @@ class CommandInterpreter : public Broadcaster,
621636
llvm::Optional<int> m_quit_exit_code;
622637
// If the driver is accepts custom exit codes for the 'quit' command.
623638
bool m_allow_exit_code = false;
639+
640+
StreamString m_transcript_stream;
624641
};
625642

626643
} // namespace lldb_private

lldb/source/Commands/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_lldb_library(lldbCommands
1313
CommandObjectFrame.cpp
1414
CommandObjectGUI.cpp
1515
CommandObjectHelp.cpp
16+
CommandObjectLanguage.cpp
1617
CommandObjectLog.cpp
1718
CommandObjectMemory.cpp
1819
CommandObjectMultiword.cpp
@@ -22,6 +23,7 @@ add_lldb_library(lldbCommands
2223
CommandObjectQuit.cpp
2324
CommandObjectRegister.cpp
2425
CommandObjectReproducer.cpp
26+
CommandObjectSession.cpp
2527
CommandObjectSettings.cpp
2628
CommandObjectSource.cpp
2729
CommandObjectStats.cpp
@@ -31,7 +33,6 @@ add_lldb_library(lldbCommands
3133
CommandObjectVersion.cpp
3234
CommandObjectWatchpoint.cpp
3335
CommandObjectWatchpointCommand.cpp
34-
CommandObjectLanguage.cpp
3536

3637
LINK_LIBS
3738
lldbBase

lldb/source/Commands/CommandObjectQuit.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,9 @@ bool CommandObjectQuit::DoExecute(Args &command, CommandReturnObject &result) {
103103
CommandInterpreter::eBroadcastBitQuitCommandReceived;
104104
m_interpreter.BroadcastEvent(event_type);
105105
result.SetStatus(eReturnStatusQuit);
106+
107+
if (m_interpreter.GetSaveSessionOnQuit())
108+
m_interpreter.SaveTranscript(result);
109+
106110
return true;
107111
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "CommandObjectSession.h"
2+
#include "lldb/Interpreter/CommandInterpreter.h"
3+
#include "lldb/Interpreter/CommandReturnObject.h"
4+
5+
using namespace lldb;
6+
using namespace lldb_private;
7+
8+
class CommandObjectSessionSave : public CommandObjectParsed {
9+
public:
10+
CommandObjectSessionSave(CommandInterpreter &interpreter)
11+
: CommandObjectParsed(interpreter, "session save",
12+
"Save the current session transcripts to a file.\n"
13+
"If no file if specified, transcripts will be "
14+
"saved to a temporary file.",
15+
"session save [file]") {
16+
CommandArgumentEntry arg1;
17+
arg1.emplace_back(eArgTypePath, eArgRepeatOptional);
18+
m_arguments.push_back(arg1);
19+
}
20+
21+
~CommandObjectSessionSave() override = default;
22+
23+
void
24+
HandleArgumentCompletion(CompletionRequest &request,
25+
OptionElementVector &opt_element_vector) override {
26+
CommandCompletions::InvokeCommonCompletionCallbacks(
27+
GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion,
28+
request, nullptr);
29+
}
30+
31+
protected:
32+
bool DoExecute(Args &args, CommandReturnObject &result) override {
33+
llvm::StringRef file_path;
34+
35+
if (!args.empty())
36+
file_path = args[0].ref();
37+
38+
if (m_interpreter.SaveTranscript(result, file_path.str()))
39+
result.SetStatus(eReturnStatusSuccessFinishNoResult);
40+
else
41+
result.SetStatus(eReturnStatusFailed);
42+
return result.Succeeded();
43+
}
44+
};
45+
46+
CommandObjectSession::CommandObjectSession(CommandInterpreter &interpreter)
47+
: CommandObjectMultiword(interpreter, "session",
48+
"Commands controlling LLDB session.",
49+
"session <subcommand> [<command-options>]") {
50+
LoadSubCommand("save",
51+
CommandObjectSP(new CommandObjectSessionSave(interpreter)));
52+
// TODO: Move 'history' subcommand from CommandObjectCommands.
53+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===-- CommandObjectSession.h ----------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H
10+
#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H
11+
12+
#include "lldb/Interpreter/CommandObjectMultiword.h"
13+
14+
namespace lldb_private {
15+
16+
class CommandObjectSession : public CommandObjectMultiword {
17+
public:
18+
CommandObjectSession(CommandInterpreter &interpreter);
19+
};
20+
21+
} // namespace lldb_private
22+
23+
#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTSESSION_H

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include <limits>
910
#include <memory>
1011
#include <stdlib.h>
1112
#include <string>
@@ -31,6 +32,7 @@
3132
#include "Commands/CommandObjectQuit.h"
3233
#include "Commands/CommandObjectRegister.h"
3334
#include "Commands/CommandObjectReproducer.h"
35+
#include "Commands/CommandObjectSession.h"
3436
#include "Commands/CommandObjectSettings.h"
3537
#include "Commands/CommandObjectSource.h"
3638
#include "Commands/CommandObjectStats.h"
@@ -52,6 +54,8 @@
5254
#if LLDB_ENABLE_LIBEDIT
5355
#include "lldb/Host/Editline.h"
5456
#endif
57+
#include "lldb/Host/File.h"
58+
#include "lldb/Host/FileCache.h"
5559
#include "lldb/Host/Host.h"
5660
#include "lldb/Host/HostInfo.h"
5761

@@ -74,6 +78,7 @@
7478
#include "llvm/Support/FormatAdapters.h"
7579
#include "llvm/Support/Path.h"
7680
#include "llvm/Support/PrettyStackTrace.h"
81+
#include "llvm/Support/ScopedPrinter.h"
7782

7883
using namespace lldb;
7984
using namespace lldb_private;
@@ -116,7 +121,7 @@ CommandInterpreter::CommandInterpreter(Debugger &debugger,
116121
m_skip_lldbinit_files(false), m_skip_app_init_files(false),
117122
m_command_io_handler_sp(), m_comment_char('#'),
118123
m_batch_command_mode(false), m_truncation_warning(eNoTruncation),
119-
m_command_source_depth(0), m_result() {
124+
m_command_source_depth(0), m_result(), m_transcript_stream() {
120125
SetEventName(eBroadcastBitThreadShouldExit, "thread-should-exit");
121126
SetEventName(eBroadcastBitResetPrompt, "reset-prompt");
122127
SetEventName(eBroadcastBitQuitCommandReceived, "quit");
@@ -142,6 +147,17 @@ void CommandInterpreter::SetPromptOnQuit(bool enable) {
142147
m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, enable);
143148
}
144149

150+
bool CommandInterpreter::GetSaveSessionOnQuit() const {
151+
const uint32_t idx = ePropertySaveSessionOnQuit;
152+
return m_collection_sp->GetPropertyAtIndexAsBoolean(
153+
nullptr, idx, g_interpreter_properties[idx].default_uint_value != 0);
154+
}
155+
156+
void CommandInterpreter::SetSaveSessionOnQuit(bool enable) {
157+
const uint32_t idx = ePropertySaveSessionOnQuit;
158+
m_collection_sp->SetPropertyAtIndexAsBoolean(nullptr, idx, enable);
159+
}
160+
145161
bool CommandInterpreter::GetEchoCommands() const {
146162
const uint32_t idx = ePropertyEchoCommands;
147163
return m_collection_sp->GetPropertyAtIndexAsBoolean(
@@ -496,6 +512,7 @@ void CommandInterpreter::LoadCommandDictionary() {
496512
CommandObjectSP(new CommandObjectReproducer(*this));
497513
m_command_dict["script"] =
498514
CommandObjectSP(new CommandObjectScript(*this, script_language));
515+
m_command_dict["session"] = std::make_shared<CommandObjectSession>(*this);
499516
m_command_dict["settings"] =
500517
CommandObjectSP(new CommandObjectMultiwordSettings(*this));
501518
m_command_dict["source"] =
@@ -1672,6 +1689,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
16721689
else
16731690
add_to_history = (lazy_add_to_history == eLazyBoolYes);
16741691

1692+
m_transcript_stream << "(lldb) " << command_line << '\n';
1693+
16751694
bool empty_command = false;
16761695
bool comment_command = false;
16771696
if (command_string.empty())
@@ -1804,6 +1823,9 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
18041823
LLDB_LOGF(log, "HandleCommand, command %s",
18051824
(result.Succeeded() ? "succeeded" : "did not succeed"));
18061825

1826+
m_transcript_stream << result.GetOutputData();
1827+
m_transcript_stream << result.GetErrorData();
1828+
18071829
return result.Succeeded();
18081830
}
18091831

@@ -2882,6 +2904,51 @@ bool CommandInterpreter::IOHandlerInterrupt(IOHandler &io_handler) {
28822904
return false;
28832905
}
28842906

2907+
bool CommandInterpreter::SaveTranscript(
2908+
CommandReturnObject &result, llvm::Optional<std::string> output_file) {
2909+
if (output_file == llvm::None || output_file->empty()) {
2910+
std::string now = llvm::to_string(std::chrono::system_clock::now());
2911+
std::replace(now.begin(), now.end(), ' ', '_');
2912+
const std::string file_name = "lldb_session_" + now + ".log";
2913+
FileSpec tmp = HostInfo::GetGlobalTempDir();
2914+
tmp.AppendPathComponent(file_name);
2915+
output_file = tmp.GetPath();
2916+
}
2917+
2918+
auto error_out = [&](llvm::StringRef error_message, std::string description) {
2919+
LLDB_LOG(GetLogIfAllCategoriesSet(LIBLLDB_LOG_COMMANDS), "{0} ({1}:{2})",
2920+
error_message, output_file, description);
2921+
result.AppendErrorWithFormatv(
2922+
"Failed to save session's transcripts to {0}!", *output_file);
2923+
return false;
2924+
};
2925+
2926+
File::OpenOptions flags = File::eOpenOptionWrite |
2927+
File::eOpenOptionCanCreate |
2928+
File::eOpenOptionTruncate;
2929+
2930+
auto opened_file = FileSystem::Instance().Open(FileSpec(*output_file), flags);
2931+
2932+
if (!opened_file)
2933+
return error_out("Unable to create file",
2934+
llvm::toString(opened_file.takeError()));
2935+
2936+
FileUP file = std::move(opened_file.get());
2937+
2938+
size_t byte_size = m_transcript_stream.GetSize();
2939+
2940+
Status error = file->Write(m_transcript_stream.GetData(), byte_size);
2941+
2942+
if (error.Fail() || byte_size != m_transcript_stream.GetSize())
2943+
return error_out("Unable to write to destination file",
2944+
"Bytes written do not match transcript size.");
2945+
2946+
result.AppendMessageWithFormat("Session's transcripts saved to %s\n",
2947+
output_file->c_str());
2948+
2949+
return true;
2950+
}
2951+
28852952
void CommandInterpreter::GetLLDBCommandsFromIOHandler(
28862953
const char *prompt, IOHandlerDelegate &delegate, void *baton) {
28872954
Debugger &debugger = GetDebugger();

lldb/source/Interpreter/InterpreterProperties.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ let Definition = "interpreter" in {
99
Global,
1010
DefaultTrue,
1111
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.">;
12+
def SaveSessionOnQuit: Property<"save-session-on-quit", "Boolean">,
13+
Global,
14+
DefaultFalse,
15+
Desc<"If true, LLDB will save the session's transcripts before quitting.">;
1216
def StopCmdSourceOnError: Property<"stop-command-source-on-error", "Boolean">,
1317
Global,
1418
DefaultTrue,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
Test the session save feature
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
class SessionSaveTestCase(TestBase):
12+
13+
mydir = TestBase.compute_mydir(__file__)
14+
15+
def raw_transcript_builder(self, cmd, res):
16+
raw = "(lldb) " + cmd + "\n"
17+
if res.GetOutputSize():
18+
raw += res.GetOutput()
19+
if res.GetErrorSize():
20+
raw += res.GetError()
21+
return raw
22+
23+
24+
@skipIfWindows
25+
@skipIfReproducer
26+
@no_debug_info_test
27+
def test_session_save(self):
28+
raw = ""
29+
interpreter = self.dbg.GetCommandInterpreter()
30+
31+
settings = [
32+
'settings set interpreter.echo-commands true',
33+
'settings set interpreter.echo-comment-commands true',
34+
'settings set interpreter.stop-command-source-on-error false'
35+
]
36+
37+
for setting in settings:
38+
interpreter.HandleCommand(setting, lldb.SBCommandReturnObject())
39+
40+
inputs = [
41+
'# This is a comment', # Comment
42+
'help session', # Valid command
43+
'Lorem ipsum' # Invalid command
44+
]
45+
46+
for cmd in inputs:
47+
res = lldb.SBCommandReturnObject()
48+
interpreter.HandleCommand(cmd, res)
49+
raw += self.raw_transcript_builder(cmd, res)
50+
51+
self.assertTrue(interpreter.HasCommands())
52+
self.assertTrue(len(raw) != 0)
53+
54+
# Check for error
55+
cmd = 'session save /root/file'
56+
interpreter.HandleCommand(cmd, res)
57+
self.assertFalse(res.Succeeded())
58+
raw += self.raw_transcript_builder(cmd, res)
59+
60+
import tempfile
61+
tf = tempfile.NamedTemporaryFile()
62+
output_file = tf.name
63+
64+
res = lldb.SBCommandReturnObject()
65+
interpreter.HandleCommand('session save ' + output_file, res)
66+
self.assertTrue(res.Succeeded())
67+
raw += self.raw_transcript_builder(cmd, res)
68+
69+
with open(output_file, "r") as file:
70+
content = file.read()
71+
# Exclude last line, since session won't record it's own output
72+
lines = raw.splitlines()[:-1]
73+
for line in lines:
74+
self.assertIn(line, content)

0 commit comments

Comments
 (0)