<div dir="ltr"><div dir="ltr"><div dir="ltr"><div>Hi Everyone,<br></div><div><br></div><div>In September I sent out an RFC [1] about adding reproducers to LLDB. Over the</div><div>past few months, I landed the reproducer framework, support for the GDB remote</div><div>protocol and a bunch of preparatory changes. There's still an open code review</div><div>[2] for dealing with files, but that one is currently blocked by a change to</div><div>the VFS in LLVM [3].</div><div><br></div><div>The next big piece of work is supporting user commands (e.g. in the driver) and</div><div>SB API calls. Originally I expected these two things to be separate, but Pavel</div><div>made a good case [4] that they're actually very similar.</div><div><br></div><div>I created a prototype of how I envision this to work. As usual, we can</div><div>differentiate between capture and replay.</div><div><br></div><div>## SB API Capture</div><div><br></div><div>When capturing a reproducer, every SB function/method is instrumented using a</div><div>macro at function entry. The added code tracks the function identifier</div><div>(currently we use its name with __PRETTY_FUNCTION__) and its arguments.</div><div><br></div><div>It also tracks when a function crosses the boundary between internal and</div><div>external use. For example, when someone (be it the driver, the python binding</div><div>or the RPC server) call SBFoo, and in its implementation SBFoo calls SBBar, we</div><div>don't need to record SBBar. When invoking SBFoo during replay, it will itself</div><div>call SBBar.</div><div><br></div><div>When a boundary is crossed, the function name and arguments are serialized to a</div><div>file. This is trivial for basic types. For objects, we maintain a table that</div><div>maps pointer values to indices and serialize the index.</div><div><br></div><div>To keep our table consistent, we also need to track return for functions that</div><div>return an object by value. We have a separate macro that wraps the returned</div><div>object.</div><div><br></div><div>The index is sufficient because every object that is passed to a function has</div><div>crossed the boundary and hence was recorded. During replay (see below) we map</div><div>the index to an address again which ensures consistency.</div><div><br></div><div>## SB API Replay</div><div><br></div><div>To replay the SB function calls we need a way to invoke the corresponding</div><div>function from its serialized identifier. For every SB function, there's a</div><div>counterpart that deserializes its arguments and invokes the function. These</div><div>functions are added to the map and are called by the replay logic.</div><div><br></div><div>Replaying is just a matter looping over the function identifiers in the</div><div>serialized file, dispatching the right deserialization function, until no more</div><div>data is available.</div><div><br></div><div>The deserialization function for constructors or functions that return by value</div><div>contains additional logic for dealing with the aforementioned indices. The</div><div>resulting objects are added to a table (similar to the one described earlier)</div><div>that maps indices to pointers. Whenever an object is passed as an argument, the</div><div>index is used to get the actual object from the table.</div><div><br></div><div>## Tool</div><div><br></div><div>Even when using macros, adding the necessary capturing and replay code is</div><div>tedious and scales poorly. For the prototype, we did this by hand, but we</div><div>propose a new clang-based tool to streamline the process.</div><div><br></div><div>For the capture code, the tool would validate that the macro matches the</div><div>function signature, suggesting a fixit if the macros are incorrect or missing.</div><div>Compared to generating the macros altogether, it has the advantage that we</div><div>don't have "configured" files that are harder to debug (without faking line</div><div>numbers etc).</div><div><br></div><div>The deserialization code would be fully generated. As shown in the prototype</div><div>there are a few different cases, depending on whether we have to account for</div><div>objects or not.</div><div><br></div><div>## Prototype Code</div><div><br></div><div>I created a differential [5] on Phabricator with the prototype. It contains the</div><div>necessary methods to re-run the gdb remote (reproducer) lit test.</div><div><br></div><div>## Feedback</div><div><br></div><div>Before moving forward I'd like to get the community's input. What do you think</div><div>about this approach? Do you have concerns or can we be smarter somewhere? Any</div><div>feedback would be greatly appreciated!</div><div><br></div><div>Thanks,</div><div>Jonas</div><div><br></div><div>[1] <a href="http://lists.llvm.org/pipermail/lldb-dev/2018-September/014184.html">http://lists.llvm.org/pipermail/lldb-dev/2018-September/014184.html</a></div><div>[2] <a href="https://reviews.llvm.org/D54617">https://reviews.llvm.org/D54617</a></div><div>[3] <a href="https://reviews.llvm.org/D54277">https://reviews.llvm.org/D54277</a></div><div>[4] <a href="https://reviews.llvm.org/D55582">https://reviews.llvm.org/D55582</a></div><div>[5] <a href="https://reviews.llvm.org/D56322">https://reviews.llvm.org/D56322</a></div></div></div></div>