Page MenuHomePhabricator

[lldb/interpreter] Add ability to save lldb session to a file
ClosedPublic

Authored by mib on Jun 19 2020, 12:45 AM.

Details

Summary

This patch introduce a new feature that allows the users to save their
debugging session's transcript (commands + outputs) to a file.

It differs from the reproducers since it doesn't require to capture a
session preemptively and replay the reproducer file in lldb.
The user can choose the save its session manually using the session save
command or automatically by setting the interpreter.save-session-on-quit
on their init file.

To do so, the patch adds a Stream object to the CommandInterpreter that
will hold the input command from the IOHandler and the CommandReturnObject
output and error. This way, that stream object accumulates passively all
the interactions throughout the session and will save them to disk on demand.

The user can specify a file path where the session's transcript will be
saved. However, it is optional, and when it is not provided, lldb will
create a temporary file name according to the session date and time.

rdar://63347792

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>

Diff Detail

Event Timeline

mib created this revision.Jun 19 2020, 12:45 AM
mib retitled this revision from [lldb/interpreter] Add ability to save lldb session to a file to [WIP][lldb/interpreter] Add ability to save lldb session to a file.Jun 19 2020, 12:47 AM
mib added a comment.Jun 19 2020, 12:56 AM

Hey folks, this is a first implementation of the patch and surely there will be several other revisions before landing it.

Some functionalities are not supported yet, like:

  • Multi-line expression
  • Python/Lua? Interactive script interpreter
  • Outputs that aren't in the CommandReturnObject

I didn't provide tests because it's still WIP (obviously, it will come it the next revisions) , but at the moment, I'm not sure what would be a good test plan for this feature (suggestions are welcomed).

Thanks in advance for the feedback.

JDevlieghere added inline comments.Jun 19 2020, 11:06 AM
lldb/source/Commands/CommandObjectSession.cpp
11

Remove

44

StringRef is empty by default, remove = ""

49

Inline the variable below.

65

Missing period.

lldb/source/Commands/CommandObjectSession.h
21

Remove

27

Remove

lldb/source/Interpreter/CommandInterpreter.cpp
125

use make_shared

2820

this won't work for things like unambiguous abbreviations like sess save. The command should do the saving.

2903

If you're going to convert the StringRef to a std::string you might as well have that passed in by value so that the caller could move it when appropriate.

2919

Why are you going through the FileCache?

2931

I think we have a constant LLDB_INVALID_FILE_DESCRIPTOR or something? If not you should use std::numeric_limits.

mib marked 9 inline comments as done.Jun 19 2020, 1:28 PM
mib added inline comments.
lldb/source/Interpreter/CommandInterpreter.cpp
2919

I was thinking if the user saves several times during his session to the same file, that might be useful. Looking at the implementation, it uses the FileSystem instance, so I'm not sure why that would a problem ...

May be you could elaborate why I shouldn't use it ?

This seems like it could be useful in some circumstances, though for the use cases I am imagining (bug reporting) it would be easier to just copy-paste the terminal contents.

As for the implementation, if the intention is for this to eventually capture all debugger output, then I am not sure if this is the right design. I think it would be fine for python/lua interpreters as those are invoked from the lldb command interpreter, but I have a feeling that routing the output printed via Debugger::PrintAsync back to the command interpreter would look pretty weird. It may make sense for the core logic of this to live in the Debugger or the IOHandler(Stack) classes -- though I am not exactly sure about that either as the Debugger and CommandIntepreter classes are fairly tightly coupled. However, I think that would be consistent with the long term goal of reimplementing the command interpreter on top of the SB API (in which case the Debugger object should not know anything about the command interpreter (but it would still need to to "something" with the PrintAsync output).

The test plan sounds fairly straight forward -- run lldb, execute a bunch of commands, and finish it off with "session save". Then, verify that the file has the "right" contents (e.g. with FileCheck). Besides multiline commands, commands which do recursive processing are also interesting to exercise -- e.g. "command source" or breakpoint callbacks. You also should decide what do you want to happen with commands that are executed through the SB interface (SBCommandInterpreter::HandleCommand) -- those will not normally go to the debugger output, but I believe they will still be captured in the current design...

lldb/source/Interpreter/CommandInterpreter.cpp
2820

I don't think it's unreasonable to write to the "transcript" here, but the string matching is obviously suboptimal. However, it's not clear to me why is it even needed -- having the "save" command in the transcript does not necessarily seem like a bad thing, and I believe the way it is implemented means that the command will not show up in the session file that is currently being saved (only in the subsequent ones).

2825

These !empty() checks are pointless.

2832

As is this flush call.

2919

FileCache is a very specialist class, so I believe the default should be to _not_ use it. However, if you are looking for reasons, I can name a few:

  • the way you are using it means you get no caching benefits whatsoever -- each OpenFile call creates a new file descriptor
  • it makes it very easy to leak that descriptor (as you did here)
2935

Why not make m_session_transcripts a shared_ptr<StreamString> (or even a plain StreamString) in the first place?

mib updated this revision to Diff 273087.Jun 24 2020, 10:13 AM
mib marked 8 inline comments as done.

Addressed Pavel's and Jonas' comments

mib updated this revision to Diff 273090.Jun 24 2020, 10:17 AM

Ran clang-format

The code looks fine to me, though it sounds like there are still issues to be sorted out w.r.t commands run from inside data formatters, through sb apis, etc. And it needs tests, of course.

lldb/source/Commands/CommandObjectSession.h
23–27

You don't have to do this, but personally I'd delete all of this stuff -- the copy operations are implicitly deleted due to them being deleted in the base class, and the destructor will be implicitly overridden anyway.

lldb/source/Interpreter/CommandInterpreter.cpp
2928

It would be nice if this error message actually contained the reason why the open operation failed (similar to what we did with the core file patch a while back).

2935

Much better, though a plain StreamString would be even nicer (and AFAICT there is nothing preventing that).

mib marked 3 inline comments as done.Thu, Jul 16, 7:14 AM
mib updated this revision to Diff 279505.Tue, Jul 21, 6:42 AM
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.
  • Address previous comments
  • Add test
mib updated this revision to Diff 279519.Tue, Jul 21, 7:29 AM

Reformat.

JDevlieghere added inline comments.Tue, Jul 21, 9:42 AM
lldb/include/lldb/Interpreter/CommandInterpreter.h
532
  • Is there more than one transcript?
  • Maybe make the string optional as you have logic to deal with that so you can call SaveTranscripts() instead of SaveTranscripts("") which looks a bit messy.
  • Add a Doxygen comment documenting the empty-string behavior.
640

The current name isn't very descriptive of what this object is exactly, especially since it's plural. How about m_transcript_stream?

lldb/source/Commands/CommandObjectSession.cpp
26

I changed the CommandArgumentData constructor so you can do

arg1.emplace_back(eArgTypePath, eArgRepeatOptional)
lldb/source/Commands/CommandObjectSession.h
17

Redundant.

lldb/source/Interpreter/CommandInterpreter.cpp
2915

Something that's called from a command should not write to the debugger's output/error stream directly. This should return an Error instead which then can be dealt with in the caller. If the caller is a CommandObject, it can write it to the return object. If the caller is something else it can still decide to write it to the debugger's error stream.

lldb/test/API/commands/session/save/TestSessionSave.py
43

You probably didn't mean to leave this around.

mib updated this revision to Diff 279638.Tue, Jul 21, 2:59 PM
mib marked 6 inline comments as done.

Address @JDevlieghere comments.

mib updated this revision to Diff 279639.Tue, Jul 21, 3:02 PM

Reformat patch.

JDevlieghere accepted this revision.Tue, Jul 21, 11:12 PM

LGTM

lldb/source/Interpreter/CommandInterpreter.cpp
2941

I'd write this to the CommandReturnObject and include it in the transcript.

This revision is now accepted and ready to land.Tue, Jul 21, 11:12 PM
labath marked an inline comment as done.Wed, Jul 22, 1:09 AM
labath added inline comments.
lldb/source/Commands/CommandObjectQuit.cpp
75–80

I think this should be done only after we have done all the validation and decided that we actually want to quit (i.e. line ~109).

I am also not sure that a failure to save the transcript should abort the quit. Maybe it just print an error message but quit nonetheless?

lldb/source/Interpreter/CommandInterpreter.cpp
506–519

/me wonders how this file escaped Jonas's make_shared-ification.

lldb/test/API/commands/session/save/TestSessionSave.py
17–19

This will modify the global configuration object. Best just set the relevant settings manually...

mib updated this revision to Diff 279737.Wed, Jul 22, 2:40 AM
mib marked 3 inline comments as done.

Address @labath comments

This revision was automatically updated to reflect the committed changes.
JDevlieghere added inline comments.Wed, Jul 22, 9:34 AM
lldb/source/Interpreter/CommandInterpreter.cpp
506–519

Oh my god. Let's fix that ASAP: https://reviews.llvm.org/D84336