DEV Community

Martin Vejdarski
Martin Vejdarski

Posted on

Prettifying Debug Variables in C++ with LLDB

Prerequisites

This post is a quick introduction to variable formatting in LLDB, and so it assumes some familiarity with LLDB and C++ debugging.
To learn more about LLDB, please visit either of the following:

What is variable formatting?

LLDB has ways of quickly representing the values of variables we see when debugging either through an IDE or the command line.

For example, this is how debug variables and their values look like in CLion:

Variables in CLion's Debug View

Why do we need to customize them?

While the defaults work perfectly well in the majority of cases, there are times when having a custom summary can significantly enhance readability and save time.

Consider the following example with color:

using Color = uint32_t;
Color color = 0xFF880022; // ARGB

Without any formatters, the value would appear as an integer:
Color without formatting

We can format Color types as hex by running the following command in an LLDB command-line session:

type format add -f hex Color

The value is now more readable:
Color with formatting

The ARGB channels can also be displayed via Python scripting (more below):
Color with summary

How to add custom summaries?

LLDB commands may be run either through the command-line (also available during a debug session in the IDE) or via a .lldbinit file in the home directory.

It is also possible to automatically load .lldbinit files from the current directory by specifying settings set target.load-cwd-lldbinit true in ~/.lldbinit , which is useful for project-specific customizations.

Additional .lldb and .py files may be loaded via:

command script import /path/to/script.py
command source /path/to/script.lldb

The ARGB summary above can now be defined as a Python function:

def ColorSummary(valobj, internal_dict):
  value = valobj.GetValueAsUnsigned(0);
  alpha = (value >> 24) & 0xFF
  red = (value >> 16) & 0xFF
  green = (value >> 8) & 0xFF
  blue = (value >> 0) & 0xFF
  return 'A: {}, R: {}, G: {}, B: {}'.format(alpha, red, green, blue)

To register it, first import the file, Summaries.py, and associate the ColorSummary function to the Color type:

command script import ./Summaries.py
type summary add -F Summaries.ColorSummary Color

More information about defining function summaries can be found here.
The valobj variable is just a wrapper around the C++ variable and its type is an lldb.SBValue.

Synthetic Children

Color with Synthetic Children
LLDB also supports defining custom children for any type. Instead of having the color channels be part of the summary, let's define them as synthetic children.

class ColorSyntheticProvider:
  def __init__(self, valobj, internal_dict):
      self.valobj = valobj

  def num_children(self):
    return 4

  def get_child_at_index(self, index):
    if index < 0 or index > 3:
      return None

    value = self.valobj.GetValueAsUnsigned(0);
    value = (value >> 24 - index * 8) & 0xFF
    name = ['A', 'R', 'G', 'B'][index]
    childType = self.valobj.target.GetBasicType(lldb.eBasicTypeInt)
    return self.valobj.CreateValueFromData(name, 
                                           lldb.SBData.CreateDataFromInt(value), 
                                           childType)

Reference: Synthetic Children, lldb.eBasicTypeInt, lldb.SBData

And the command to register the provider inside the .lldbinit file:

type synthetic add -l Summaries.ColorSyntheticProvider Color

Where to go from here?

  • The official documentation is excellent and covers all the details.
  • Filters may be used to hide children that are not useful for debugging.
  • Regex Typenames are particularly handy for registering summaries for templated types.
  • More examples can be found on the LLVM repository.
  • For specific information about any command, simply type

    help <command> <sub-command> .

Top comments (0)