aboutsummaryrefslogtreecommitdiff
path: root/lldb/docs/design/reproducers.rst
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2021-02-16 20:13:02 +0000
committerDimitry Andric <dim@FreeBSD.org>2021-02-16 20:13:02 +0000
commitb60736ec1405bb0a8dd40989f67ef4c93da068ab (patch)
tree5c43fbb7c9fc45f0f87e0e6795a86267dbd12f9d /lldb/docs/design/reproducers.rst
parentcfca06d7963fa0909f90483b42a6d7d194d01e08 (diff)
downloadsrc-b60736ec1405bb0a8dd40989f67ef4c93da068ab.tar.gz
src-b60736ec1405bb0a8dd40989f67ef4c93da068ab.zip
Vendor import of llvm-project main 8e464dd76bef, the last commit beforevendor/llvm-project/llvmorg-12-init-17869-g8e464dd76bef
the upstream release/12.x branch was created.
Diffstat (limited to 'lldb/docs/design/reproducers.rst')
-rw-r--r--lldb/docs/design/reproducers.rst212
1 files changed, 212 insertions, 0 deletions
diff --git a/lldb/docs/design/reproducers.rst b/lldb/docs/design/reproducers.rst
new file mode 100644
index 000000000000..d8ad3dd7866d
--- /dev/null
+++ b/lldb/docs/design/reproducers.rst
@@ -0,0 +1,212 @@
+Reproducers
+===========
+
+As unbelievable as it may sound, the debugger has bugs. These bugs might
+manifest themselves as errors, missing results or even a crash. Quite often
+these bugs don't reproduce in simple, isolated scenarios. The debugger deals
+with a lot of moving parts and subtle differences can easily add up.
+
+Reproducers in LLDB improve the experience for both the users encountering bugs
+and the developers working on resolving them. The general idea consists of
+*capturing* all the information necessary to later *replay* a debug session
+while debugging the debugger.
+
+.. contents::
+ :local:
+
+Usage
+-----
+
+Reproducers are a generic concept in LLDB and are not inherently coupled with
+the command line driver. The functionality can be used for anything that uses
+the SB API and the driver is just one example. However, because it's probably
+the most common way users interact with lldb, that's the workflow described in
+this section.
+
+Capture
+```````
+
+Until reproducer capture is enabled by default, you need to launch LLDB in
+capture mode. For the command line driver, this means passing ``--capture``.
+You cannot enable reproducer capture from within LLDB, as this would be too
+late to capture initialization of the debugger.
+
+.. code-block:: bash
+
+ > lldb --capture
+
+In capture mode, LLDB will keep track of all the information it needs to replay
+the current debug session. Most data is captured lazily to limit the impact on
+performance. To create the reproducer, use the ``reproducer generate``
+sub-command. It's always possible to check the status of the reproducers with
+the ``reproducer status`` sub-command. Note that generating the reproducer
+terminates the debug session.
+
+.. code-block:: none
+
+ (lldb) reproducer status
+ Reproducer is in capture mode.
+ (lldb) reproducer generate
+ Reproducer written to '/path/to/reproducer'
+ Please have a look at the directory to assess if you're willing to share the contained information.
+
+
+The resulting reproducer is a directory. It was a conscious decision to not
+compress and archive it automatically. The reproducer can contain potentially
+sensitive information like object and symbol files, their paths on disk, debug
+information, memory excerpts of the inferior process, etc.
+
+Replay
+``````
+
+It is strongly recommended to replay the reproducer locally to ensure it
+actually reproduces the expected behavior. If the reproducer doesn't behave
+correctly locally, it means there's a bug in the reproducer implementation that
+should be addressed.
+
+To replay a reproducer, simply pass its path to LLDB through the ``--replay``
+flag. It is unnecessary to pass any other command line flags. The flags that
+were passed to LLDB during capture are already part of the reproducer.
+
+.. code-block:: bash
+
+ > lldb --replay /path/to/reproducer
+
+
+During replay LLDB will behave similar to batch mode. The session should be
+identical to the recorded debug session. The only expected differences are that
+the binary being debugged doesn't actually run during replay. That means that
+you won't see any of its side effects, like things being printed to the
+terminal. Another expected difference is the behavior of the ``reproducer
+generate`` command, which becomes a NOOP during replay.
+
+Augmenting a Bug Report with a Reproducer
+`````````````````````````````````````````
+
+A reproducer can significantly improve a bug report, but it in itself is not
+sufficient. Always describe the expected and unexpected behavior. Just like the
+debugger can have bugs, the reproducer can have bugs too.
+
+
+Design
+------
+
+
+Replay
+``````
+
+Reproducers support two replay modes. The main and most common mode is active
+replay. It's called active, because it's LLDB that is driving replay by calling
+the captured SB API functions one after each other. The second mode is passive
+replay. In this mode, LLDB sits idle until an SB API function is called, for
+example from Python, and then replays just this individual call.
+
+Active Replay
+^^^^^^^^^^^^^
+
+No matter how a reproducer was captured, they can always be replayed with the
+command line driver. When a reproducer is passed with the ``--replay`` flag, the
+driver short-circuits and passes off control to the reproducer infrastructure,
+effectively bypassing its normal operation. This works because the driver is
+implemented using the SB API and is therefore nothing more than a sequence of
+SB API calls.
+
+Replay is driven by the ``Registry::Replay``. As long as there's data in the
+buffer holding the API data, the next SB API function call is deserialized.
+Once the function is known, the registry can retrieve its signature, and use
+that to deserialize its arguments. The function can then be invoked, most
+commonly through the synthesized default replayer, or potentially using a
+custom defined replay function. This process continues, until more data is
+available or a replay error is encountered.
+
+During replay only a function's side effects matter. The result returned by the
+replayed function is ignored because it cannot be observed beyond the driver.
+This is sound, because anything that is passed into a subsequent API call will
+have been serialized as an input argument. This also works for SB API objects
+because the reproducers know about every object that has crossed the API
+boundary, which is true by definition for object return values.
+
+
+Passive Replay
+^^^^^^^^^^^^^^
+
+Passive replay exists to support running the API test suite against a
+reproducer. The API test suite is written in Python and tests the debugger by
+calling into its API from Python. To make this work, the API must transparently
+replay itself when called. This is what makes passive replay different from
+driver replay, where it is lldb itself that's driving replay. For passive
+replay, the driving factor is external.
+
+In order to replay API calls, the reproducers need a way to intercept them.
+Every API call is already instrumented with an ``LLDB_RECORD_*`` macro that
+captures its input arguments. Furthermore, it also contains the necessary logic
+to detect which calls cross the API boundary and should be intercepted. We were
+able to reuse all of this to implement passive replay.
+
+During passive replay is enabled, nothing happens until an SB API is called.
+Inside that API function, the macro detects whether this call should be
+replayed (i.e. crossed the API boundary). If the answer is yes, the next
+function is deserialized from the SB API data and compared to the current
+function. If the signature matches, we deserialize its input arguments and
+reinvoke the current function with the deserialized arguments. We don't need to
+do anything special to prevent us from recursively calling the replayed version
+again, as the API boundary crossing logic knows that we're still behind the API
+boundary when we re-invoked the current function.
+
+Another big difference with driver replay is the return value. While this
+didn't matter for driver replay, it's key for passive replay, because that's
+what gets checked by the test suite. Luckily, the ``LLDB_RECORD_*`` macros
+contained sufficient type information to derive the result type.
+
+Testing
+-------
+
+Reproducers are tested in the following ways:
+
+ - Unit tests to cover the reproducer infrastructure. There are tests for the
+ provider, loader and for the reproducer instrumentation.
+ - Feature specific end-to-end test cases in the ``test/Shell/Reproducer``
+ directory. These tests serve as integration and regression tests for the
+ reproducers infrastructure, as well as doing some sanity checking for basic
+ debugger functionality.
+ - The API and shell tests can be run against a replayed reproducer. The
+ ``check-lldb-reproducers`` target will run the API and shell test suite
+ twice: first running the test normally while capturing a reproducer and then
+ a second time using the replayed session as the test input. For the shell
+ tests this use a little shim (``lldb-repro``) that uses the arguments and
+ current working directory to transparently generate or replay a reproducer.
+ For the API tests an extra argument with the reproducer path is passed to
+ ``dotest.py`` which initializes the debugger in the appropriate mode.
+ Certain tests do not fit this paradigm (for example test that check the
+ output of the binary being debugged) and are skipped by marking them as
+ unsupported by adding ``UNSUPPORTED: lldb-repro`` to the top of the shell
+ test or adding the ``skipIfReproducer`` decorator for the API tests.
+
+Additional testing is possible:
+
+ - It's possible to unconditionally capture reproducers while running the
+ entire test suite by setting the ``LLDB_CAPTURE_REPRODUCER`` environment
+ variable. Assuming no bugs in reproducers, this can also help to reproduce
+ and investigate test failures.
+
+Knows Issues
+------------
+
+The reproducers are still a work in progress. Here's a non-exhaustive list of
+outstanding work, limitations and known issues.
+
+ - The VFS cannot deal with more than one current working directory. Changing
+ the current working directory during the debug session will break relative
+ paths.
+ - Not all SB APIs are properly instrumented. We need customer serialization
+ for APIs that take buffers and lengths.
+ - We leak memory during replay because the reproducer doesn't capture the end
+ of an object's life time. We need to add instrumentation to the destructor
+ of SB API objects.
+ - The reproducer includes every file opened by LLDB. This is overkill. For
+ example we do not need to capture source files for code listings. There's
+ currently no way to say that some file shouldn't be included in the
+ reproducer.
+ - We do not yet automatically generate a reproducer on a crash. The reason is
+ that generating the reproducer is too expensive to do in a signal handler.
+ We should re-invoke lldb after a crash and do the heavy lifting.