# Motivation Currently, the user can already get the "transcript" (for "what is the transcript", see `CommandInterpreter::SaveTranscript`). However, the only way to obtain the transcript data as a user is to first destroy the debugger, then read the save directory. Note that destroy-callbacks cannot be used, because 1\ transcript data is private to the command interpreter (see `CommandInterpreter.h`), and 2\ the writing of the transcript is *after* the invocation of destory-callbacks (see `Debugger::Destroy`). So basically, there is no way to obtain the transcript: * during the lifetime of a debugger (including the destroy-callbacks, which often performs logging tasks, where the transcript can be useful) * without relying on external storage In theory, there are other ways for user to obtain transcript data during a debugger's life cycle: * Use Python API and intercept commands and results. * Use CLI and record console input/output. However, such ways rely on the client's setup and are not supported natively by LLDB. # Proposal Add a new Python API `SBCommandInterpreter::GetTranscript()`. Goals: * It can be called at any time during the debugger's life cycle, including in destroy-callbacks. * It returns data in-memory. Structured data: * To make data processing easier, the return type is `SBStructuredData`. See comments in code for how the data is organized. * In the future, `SaveTranscript` can be updated to write different formats using such data (e.g. JSON). This is probably accompanied by a new setting (e.g. `interpreter.save-session-format`). # Alternatives The return type can also be `std::vector<std::pair<std::string, SBCommandReturnObject>>`. This will make implementation easier, without having to translate it to `SBStructuredData`. On the other hand, `SBStructuredData` can convert to JSON easily, so it's more convenient for user to process. # Privacy Both user commands and output/error in the transcript can contain privacy data. However, as mentioned, the transcript is already available to the user. The addition of the new API doesn't increase the level of risk. In fact, it _lowers_ the risk of privacy data being leaked later on, by avoiding writing such data to external storage. Once the user (or their code) gets the transcript, it will be their responsibility to make sure that any required privacy policies are guaranteed. # Tests ``` bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py ``` ``` bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/commands/session/save/TestSessionSave.py ``` --------- Co-authored-by: Roy Shi <royshi@meta.com> Co-authored-by: Med Ismail Bennani <ismail@bennani.ma>
134 lines
4.7 KiB
Python
134 lines
4.7 KiB
Python
"""
|
|
Test the session save feature
|
|
"""
|
|
import os
|
|
import tempfile
|
|
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
|
|
|
|
class SessionSaveTestCase(TestBase):
|
|
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
|
|
@no_debug_info_test
|
|
def test_session_save(self):
|
|
raw = ""
|
|
interpreter = self.dbg.GetCommandInterpreter()
|
|
|
|
# Make sure "save-transcript" is on, so that all the following setings
|
|
# and commands are saved into the trasncript. Note that this cannot be
|
|
# a part of the `settings`, because this command itself won't be saved
|
|
# into the transcript.
|
|
self.runCmd("settings set interpreter.save-transcript true")
|
|
|
|
settings = [
|
|
"settings set interpreter.echo-commands true",
|
|
"settings set interpreter.echo-comment-commands true",
|
|
"settings set interpreter.stop-command-source-on-error false",
|
|
"settings set interpreter.open-transcript-in-editor 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.assertNotEqual(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)
|
|
|
|
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)
|
|
|
|
td = tempfile.TemporaryDirectory()
|
|
res = lldb.SBCommandReturnObject()
|
|
interpreter.HandleCommand(
|
|
"settings set interpreter.save-session-directory " + td.name, res
|
|
)
|
|
self.assertTrue(res.Succeeded())
|
|
|
|
res = lldb.SBCommandReturnObject()
|
|
interpreter.HandleCommand("session save", res)
|
|
self.assertTrue(res.Succeeded())
|
|
raw += self.raw_transcript_builder(cmd, res)
|
|
|
|
with open(os.path.join(td.name, os.listdir(td.name)[0]), "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)
|
|
|
|
@skipIfWindows
|
|
@no_debug_info_test
|
|
def test_session_save_on_quit(self):
|
|
raw = ""
|
|
interpreter = self.dbg.GetCommandInterpreter()
|
|
|
|
# Make sure "save-transcript" is on, so that all the following setings
|
|
# and commands are saved into the trasncript. Note that this cannot be
|
|
# a part of the `settings`, because this command itself won't be saved
|
|
# into the transcript.
|
|
self.runCmd("settings set interpreter.save-transcript true")
|
|
|
|
td = tempfile.TemporaryDirectory()
|
|
|
|
settings = [
|
|
"settings set interpreter.echo-commands true",
|
|
"settings set interpreter.echo-comment-commands true",
|
|
"settings set interpreter.stop-command-source-on-error false",
|
|
"settings set interpreter.save-session-on-quit true",
|
|
"settings set interpreter.save-session-directory " + td.name,
|
|
"settings set interpreter.open-transcript-in-editor false",
|
|
]
|
|
|
|
for setting in settings:
|
|
res = lldb.SBCommandReturnObject()
|
|
interpreter.HandleCommand(setting, res)
|
|
raw += self.raw_transcript_builder(setting, res)
|
|
|
|
self.dbg.Destroy(self.dbg)
|
|
|
|
with open(os.path.join(td.name, os.listdir(td.name)[0]), "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)
|