Projects¶

The following is a mostly unordered set of the ideas for improvements to the LLDB debugger. Some are fairly deep, some would require less effort.

Speed up type realization in lldb¶

The type of problem I’m addressing here is the situation where you are debugging a large program (lldb built with debug clang/swift will do) and you go to print a simple expression, and lldb goes away for 30 seconds. When you sample it, it is always busily churning through all the CU’s in the world looking for something. The problem isn’t that looking for something in particular is slow, but rather that we somehow turned an bounded search (maybe a subtype of “std::string” into an unbounded search (all things with the name of that subtype.) Or didn’t stop when we got a reasonable answer proximate to the context of the search, but let the search leak out globally. And quite likely there are other issues that I haven’t guessed yet. But if you end up churning though 3 or 4 Gig of debug info, that’s going to be slow no matter how well written your debug reader is…

My guess is the work will be more in the general symbol lookup than in the DWARF parser in particular, but it may be a combination of both.

As a user debugging a largish program, this is the most obvious lameness of lldb.

Symbol name completion in the expression parser¶

This is the other obvious lameness of lldb. You can do:

(lldb) frame var foo.b


and we will tell you it is “foo.bar”. But you can’t do that in the expression parser. This will require collaboration with the clang/swift folks to get the right extension points in the compiler. And whatever they are, lldb will need use them to tell the compiler about what names are available. It will be important to avoid the pitfalls of #1 where we wander into the entire DWARF world.

Make a high speed asynchronous communication channel¶

All lldb debugging nowadays is done by talking to a debug agent. We used the gdb-remote protocol because that is universal, and good enough, and you have to support it anyway since so many little devices & JTAG’s and VM’s etc support it. But it is really old, not terribly high performance, and can’t really handle sending or receiving messages while the process is supposedly running. It should have compression built in, remove the hand-built checksums and rely on the robust communication protocols we always have nowadays, allow for out-of-order requests/replies, allow for reconnecting to a temporarily disconnected debug session, regularize all of the packet formatting into JSON or BSON or whatever while including a way to do large binary transfers. It must be possible to come up with something faster, and better tunable for the many communications pathways we end up supporting.

Fix local variable lookup in the lldb expression parser¶

The injection of local variables into the clang expression parser is currently done incorrectly - it happens too late in the lookup. This results in namespace variables & functions, same named types and ivars shadowing locals when it should be the other way around. An attempt was made to fix this by manually inserting all the visible local variables into wrapper function in the expression text. This mostly gets the job done but that method means you have to realize all the types and locations of all local variables for even the simplest of expressions, and when run on large programs (e.g. lldb) it would cause unacceptable delays. And it was very fragile since an error in realizing any of the locals would cause all expressions run in that context to fail. We need to fix this by adjusting the points where name lookup calls out to lldb in clang.

Support calling SB & commands everywhere and support non-stop debugging¶

There is a fairly ad-hoc system to handle when it is safe to run SB API’s and command line commands. This is actually a bit of a tricky problem, since we allow access to the command line and SB API from some funky places in lldb. The Operating System plugins are the most obvious instance, since they get run right after lldb is told by debugserver that the process has stopped, but before it has finished collating the information from the stop for presentation to the higher levels. But breakpoint callbacks have some of the same problems, and other things like the scripted stepping operations and any fancier extension points we want to add to the debugger are going to be hard to implement robustly till we work on a finer-grained and more explicit control over who gets to control the process state.

We also won’t have any chance of supporting non-stop debugging - which is a useful mode for programs that have a lot of high-priority or real-time worker threads - until we get this sorted out.

Finish the language abstraction and remove all the unnecessary API’s¶

An important part of making lldb a more useful “debugger toolkit” as opposed to a C/C++/ObjC/Swift debugger is to have a clean abstraction for language support. We did most, but not all, of the physical separation. We need to finish that. And then by force of necessity the API’s really look like the interface to a C++ type system with a few swift bits added on. How you would go about adding a new language is unclear and much more trouble than it is worth at present. But if we made this nice, we could add a lot of value to other language projects.

Add some syntax to generate data formatters from type definitions¶

Uses of the data formatters fall into two types. There are data formatters for types where the structure elements pretty much tell you how to present the data, you just need a little expression language to express how to turn them into what the user expects to see. Then there are the ones (like pretty much all our Foundation/AppKit/UIKit formatters) that use deep magic to figure out how the type is actually laid out. The latter are pretty much always going to have to be done by hand.

But for the ones where the information is expressed in the fields, it would be great to have a way to express the instructions to produce summaries and children in some form you could embed next to the types and have the compiler produce a byte code form of the instructions and then make that available to lldb along with the library. This isn’t as simple as having clang run over the headers and produce something from the types directly. After all, clang has no way of knowing that the interesting thing about a std::vector is the elements that you get by calling size (for the summary) and [] for the elements. But it shouldn’t be hard to come up with a generic markup to express this.

Allow the expression parser to access dynamic type/data formatter information¶

This seems like a smaller one. The symptom is your object is Foo child of Bar, and in the Locals view you see all the fields of Foo, but because the static type of the object is Bar, you can’t see any of the fields of Foo. But if you could get this working, you could hijack the mechanism to make the results of the value object summaries/synthetic children available to expressions. And if you can do that, you could add other properties to an object externally (through Python or some other extension point) and then have these also available in the expression parser. You could use this to express invariants for data structures, or other more advanced uses of types in the debugger.

Another version of this is to allow access to synthetic children in the expression parser. Otherwise you end up in situations like:

(lldb) print return_a_foo()

Add a “testButDontAbort” style test to the UnitTest framework¶

The way we use unittest now (maybe this is the only way it can work, I don’t know) you can’t report a real failure and continue with the test. That is appropriate in some cases: if I’m supposed to hit breakpoint A before I evaluate an expression, and don’t hit breakpoint A, the test should fail. But it means that if I want to test five different expressions, I can either do it in one test, which is good because it means I only have to fire up one process, attach to it, and get it to a certain point. But it also means if the first test fails, the other four don’t even get run. So though at first we wrote a bunch of test like this, as time went on we switched more to writing “one at a time” tests because they were more robust against a single failure. That makes the test suite run much more slowly. It would be great to add a “test_but_dont_abort” variant of the tests, then we could gang tests that all drive to the same place and do similar things. As an added benefit, it would allow us to be more thorough in writing tests, since each test would have lower costs.

Convert the dotest style tests to use lldbutil.run_to_source_breakpoint¶

run_to_source_breakpoint & run_to_name_breakpoint provide a compact API that does in one line what the first 10 or 20 lines of most of the old tests now do by hand. Using these functions makes tests much more readable, and by centralizing common functionality will make maintaining the testsuites easier in the future. This is more of a finger exercise, and perhaps best implemented by a rule like: “If you touch a test case, and it isn’t using run_to_source_breakpoint, please make it do so”.

Unify Watchpoint’s & Breakpoints¶

Option handling isn’t shared, and more importantly the PerformAction’s have a lot of duplicated common code, most of which works less well on the Watchpoint side.

Reverse debugging¶

This is kind of a holy grail, it’s hard to support for complex apps (many threads, shared memory, etc.) But it would be SO nice to have…

Non-stop debugging¶

By this I mean allowing some threads in the target program to run while stopping other threads. This is supported in name in lldb at present, but lldb makes the assumption “If I get a stop, I won’t get another stop unless I actually run the program.” in a bunch of places so getting it to work reliably will be some a good bit of work. And figuring out how to present this in the UI will also be tricky.

Fix and continue¶

We did this in gdb without a real JIT. The implementation shouldn’t be that hard, especially if you can build the executable for fix and continue. The tricky part is how to verify that the user can only do the kinds of fixes that are safe to do. No changing object sizes is easy to detect, but there were many more subtle changes (function you are fixing is on the stack…) that take more work to prevent. And then you have to explain these conditions the user in some helpful way.

Unified IR interpreter¶

Currently IRInterpreter implements a portion of the LLVM IR, but it doesn’t handle vector data types and there are plenty of instructions it also doesn’t support. Conversely, lli supports most of LLVM’s IR but it doesn’t handle remote memory and its function calling support is very rudimentary. It would be useful to unify these and make the IR interpreter – both for LLVM and LLDB – better. An alternate strategy would be simply to JIT into the current process but have callbacks for non-stack memory access.

Teach lldb to predict exception propagation at the throw site¶

There are a bunch of places in lldb where we need to know at the point where an exception is thrown, what frame will catch the exception.

For instance, if an expression throws an exception, we need to know whether the exception will be caught in the course of the expression evaluation. If so it would be safe to let the expression continue. But since we would destroy the state of the thread if we let the exception escape the expression, we currently stop the expression evaluation if we see a throw. If we knew where it would be caught we could distinguish these two cases.

Similarly, when you step over a call that throws, you want to stop at the throw point if you know the exception will unwind past the frame you were stepping in, but it would annoying to have the step abort every time an exception was thrown. If we could predict the catching frame, we could do this right.

And of course, this would be a useful piece of information to display when stopped at a throw point.

Add predicates to the nodes of settings¶

It would be very useful to be able to give values to settings that are dependent on the triple, or executable name, for targets, or on whether a process is local or remote, or on the name of a thread, etc. The original intent (and there is a sketch of this in the settings parsing code) was to be able to say:

(lldb) settings set target{arch=x86_64}.process.thread{name=foo}...


The exact details are still to be worked out, however.