How to create LLDB type summaries and synthetic children for your custom types

If you use a LLDB based toolchain (for example, working with C++ in Xcode or CLion), you can easily customize how your IDE displays variables while debugging.

The variable formatting docs are a bit dense, so here’s a friendly getting started guide.

What are Type Summaries?

Summaries show when you hover over a variable in your IDE while debugging:

Xcode shows the just the summary, not the type name

It’s what displays when you po (print object) on the LLDB console:

You can teach LLDB to provide any arbitrary string as the summary:

A custom summary string in CLion shows the type name and summary

For custom types without defined summaries, Xcode won’t show anything (which makes for a somewhat inconsistent experience). CLion shows the type name (which is nicer):

What Are Type Children?

When inspecting a type, you can click the + (CLion) or disclosure triangle (Xcode) to see “children” of the type

Click that lil’ disclosure triangle!
Happy debugging

Children are members of the the current type (or “items” of an array type) and their summaries. This lets you drill down while debugging.

Like type summaries, type children are customizable. You can teach LLDB how to create “synthetic” children for your custom type.

This lets you display and organize the information you care about. Especially with convoluted modern C++, a type might have a lot of nesting or internal complexity that gets in the way when debugging.

Note that the synthetic children are using a juce::String type summary I’ve also defined

Customizing Summaries and Children with Python

On startup, your IDE will look for a file named ~/.lldbinit and parse and run it.

Here’s the current contents of my ~/.lldbinit:

command script import /Users/sudara/projects/librariesAndExamples/juce-toys/juce_lldb_xcode.py
command script import /Users/sudara/projects/melatonin_audio_sparklines/sparklines.py

It just loads two other python files, which is where the code that does stuff lives.

You can also manually type these import commands on the lldb console:

The lldb console is underrated!

This way you can keep editing and reimporting the file in the debugger, even while it sits on a breakpoint! This makes iterating and developing these summaries very easy.

(Tip: make sure you don’t paste in the the filename with extra whitespace characters after it or you’ll see an invalid pathname error.)

Python file

So what do these imported python files do?

Firstly, they define a __lbdb_init_module function which gets called on script import. Usually this will have a few HandleCommand calls that tell LLDB which summaries and synthetics to add.

The string you pass to HandleCommand will get run just as if you had pasted it into the lldb console.

Note that print statements get output to the lldb console.

import sys
import lldb

def __lldb_init_module(debugger, dict):
    print("loading melatonin audio sparklines for JUCE AudioBlocks...")
    debugger.HandleCommand('type synthetic add -x "juce::dsp::AudioBlock<" --python-class sparklines.AudioBlockChannelsProvider -w juce')
    debugger.HandleCommand('type summary add -x "juce::dsp::AudioBlock<" -F sparklines.audio_block_summary -w juce')

Customizing Summaries

Summary strings are the nice and easy way to format types. If you just want to display some text or some values of members, --summary-string is the way to go.

Just specify the format you want directly in the HandleCommand call:

debugger.HandleCommand('type summary add juce::String --summary-string "${var.text.data}" -w juce')

In a summary, var always refers to the variable the summary is being created for.

You can drill down and access the summaries for public or private members like I do above (text and data are both private members of this type) as well as do a bit of formatting.

The -w assigns the summary to a category.

Categories can then be enabled and disabled like so: type category enable juce.

via python function

If you need to do some fancier formatting, specify a python file and function with the -F option, like so:

debugger.HandleCommand('type summary add juce::ValueTree -F juce_lldb_xcode.value_tree_summary -w juce')

Python API

The python API is somewhat tersely documented, so code examples are your friend.

To start with: SBValue is the python representation of a variable in your code.

This is what gets passed into to your summary function. In code examples, it’s passed in with the name valueObject.

Here are some tips on SBValue‘s methods:

GetChildMemberWithName()

This digs down through members of your variable, handing you back the SBValue representation of that child.

Private members too! This is important, as it’s hard (possible, but ugly) to call functions in your code on/with an SBValue. So private members are your new best friend.

Warning: if you are provided synthetic children for a type (more on that later), you replaced the “natural” child members with your synthetics. That means the members of the type are removed and you can only drill down into the child members you manually defined! More on that later.

GetValue() returns a string

GetValue() returns a string representation of your variable.

You can use GetValueAsSigned() and GetValueAsUnsigned() for integer representations.

GetSummary() grabs type summaries

LLDB provides built-in summaries for some basic types, such as strings.

Using GetSummary() in your type summary is useful for things like containers or when you want your children to have nice summaries too.

An example I have for the the JUCE framework:

A juce::String summary is used in the juce::var summary which is used in the juce::NamedValueSet::NamedValue summary which is used in the juce::NamedValueSet summary, which is used in the juce::ValueTree summary!

Synthetic Children

This lets you replace the children (members) that lldb shows with custom (synthetic) children.

Basically, you need to craft a class with specific methods, as seen in the docs.

Note: The __init__ method of the synthetic provider is called when you hover over a type in your IDE and the a type summary is about to be displayed. This means: For summaries, “natural” children are already replaced by your new “synthetic” children. If you want to refer to an original child member in a python function, store it manually in __init__, or use GetNonSyntheticValue().

get_child_index

Note that LLDB uses the brackets operator for array type objects. This is why you’ll see a lot of examples that uses a pattern that strip brackets off like so:

def get_child_index(self, name):
    return int(name.lstrip('[').rstrip(']'))

CreateChildAtOffset

Likewise, array examples often use CreateChildAtOffset in the get_child_at_index method and manually create children with string names like [2].

def get_child_at_index(self, index):
    offset = index * self.data_size
    return self.first_element.CreateChildAtOffset('[' + str(index) + ']', offset, self.data_type)

Note that we’re relying on self.data_size, self.data_type and self.first_element in this particular array example. Those are specific to this example good place to set things like that would be the update method:

def update(self):
    # dig down and find the pointer to the start of the data
    self.first_element = self.valueObject.GetChildMemberWithName('values').GetChildMemberWithName('elements').GetChildMemberWithName('data')

    # type of first template argument. For example juce::Array<int> would be int
    self.data_type = self.valueObject.GetType().GetTemplateArgumentType(0)
    self.data_size = self.data_type.GetByteSize()

CreateValueFromData

This is a higher octane way to create children, letting you construct them from data you have in python (like a string you manually constructed).

Check out examples

My audio sparklines example (displaying real time audio in your IDE!) might interest you (it gets pretty complex).

There are some examples in the lldb repo and some formatters for the JUCE C++ framework I’ve contributed to.

Let me know if you have a go at it — it’ll help justify the time I spent digging in!

Leave a comment

Your email address will not be published.