Understanding Swift + LLDB customization points as a third party

As a developer of a third-party library, I want to provide the best possible debugging experience. But, the rub: the primary type I seek to improve working with involves a) an opaque structure and b) user-provided closures. Swift-native Mirror types provide a great solution to overcoming the first problem, but that's then invalidated by any attempt to solve the second problem; Swift function types all print in-process as just (Function)!

Luckily, I see that once p gets ahold of a function reference, LLDB puts a ton of work into recovering a good display name even in the face of reabstractions (which is really cool!). But the fine difference between p and po is likely to mystify some. Worse, p can't see into my opaque type without lots of work…

I've delved into Swift's patches atop LLDB, and found its various and sundry implementations of Data Formatter types. Going down this road seems promising, but has produced a few questions I don't have great answers to.

  1. Are there reasons why Swift's synthetic children providers don't talk to Mirror and instead dump all of that representation to a string in-process?
  2. Is messing with custom providers a good idea for me as a third party to invest in? Just looking at what swift-lldb's providers are doing, a lot of things I intend to write go beyond simple method calls and would end up coupling to the ABI representation somehow (f.ex. having to work around SR-7565). My debugging aids could be more frustrating than helpful if they break suddenly!
  3. If synthetic children are The Right Way™️ of doing this (at least for the near future), what should I be doing to make mine resilient to or at least of aware of the ABI representation?
1 Like

@Jim_Ingham might be able to help with this?

  1. I'm not sure what you're asking here. By "Swift's synthetic children providers" do you mean the synthetic child providers that are part of swift Mirror class (the children property thereof) or lldb's synthetic children?

If the latter, we are careful not to write summary or synthetic child providers in lldb that require running code in the target program. Remember that not only "print" but also "frame variable" and more importantly the SBValue printing uses this facility as well. The SBValue API's are used to populate the Xcode "locals" view - and other IDE's that use lldb take the same path. We found over time that having to call functions in the target for every visible local variable for every step in the debugger was not performant, and raised issues like what happens when the functions access data structures that lock themselves, or are not initialized yet, etc. So lldb doesn't use the Swift Mirrors facility for anything but "po", which is explicitly a code-running bit of functionality.

  1. I'm not sure I would call problems like SR-7565 a problem with the ABI, but rather with the internal details of the representation of some of the standard library objects. In particular, enums are fairly complex little beasts, and to render them in all cases requires a detailed understanding of how they are laid out. In some cases the algorithms needed to comprehend the layout are complex, and we're trying to figure out a way to share the algorithms rather than hand code them in lldb.

Not sure why this would be an issue for you, however. If your child provider decides it has found an enum somewhere in your object and you want to print it, you should be able to make an SBValue which is of the right enum type and located wherever you've discovered that it lives, and the built-in lldb enum provider will then take care of rendering the child enums properly. If the latter bit isn't working, that's a bug that we need to fix in lldb, but it shouldn't affect the strategy your data formatters should take.

  1. I think I need more details about what you intend to do to answer this question helpfully.

Yeah, the confusion is warranted - I was straddling the line between both, sorry about that.

More specifically: I have closures nested inside a custom data structure that I want to expose (symbol names and all) to a clients while debugging. Do I have it right that using an lldb synthetic provider and asking users to use the “locals” view and p is the only way this is possible? Or am I missing some feature for doing this library-side and exposing it in my Mirror?

This provides some clarity to my above question. It looks like the library-side vs. lldb-side distinction shows no risk of going away any time soon, so that’s promising as to whether it’s the right thing to do separately from my above question.

Are we looking for this to be solved by 5.0, or is this a more immediate bug-fixing type thing?

Good to know, thanks! I’ll keep an eye out.

I don't have any timeline on when/how we will get the data formatters like enum working in a less ad hoc way. This is more of a design goal than something we actually know how to do right now. You could for instance imagine that lldb runs the Mirror for the type in the copy of the standard library that it loaded, but using a remote memory reader to fetch the contents. That seems promising but you aren't guaranteed that the same things get laid out the same way on different architectures, and this needs to be cross architecture... We do a similar thing for figuring out the real types of parameters from metadata (using RemoteAST). But that asks to solve a simpler problem.

So for the medium term, we may be down to fixing cases in the current hand-coded introspection in the data formatters.