Low-level Debugger (LLDB) Extension for Structure Visualization

Debuggers are essential tools to dig into code issues. They can be a huge time-saver for developers.

However, debuggers need help to correctly handle nontrivial data structures. They need the insight to understand the internals of the data model. Recursive structures found in trees are particularly challenging.

In this blog, we will see how to implement an LLDB (low-level debugger) extension to improve variable formatting. We will explore how to customize LLDB to greatly improve our data structure visualization thus our debugging efficiency. We will play with toy structures that form the base of an Abstract Syntax Tree (AST).

Data Model

The first structure we need is a chained list. Chained lists are found everywhere in C, yet they are usually painful to debug. They contain a lot of indirection forcing the user to unravel them.

Here is the simple linked list we will work with. It can contain two types of data int or Node*.

Because of the chained nature of the list, if we display the list we don’t see its actual content.

Even in a GUI linked lists need a lot of clicks to be displayed.

lldb extension

The other structure we need is a composite struct. It’s a very common idiom in C.

Because each node version contains a Node element as the first variable, we can use polymorphism. The true form of a node is contained in its label field. The issue is only the programmer knows that Node* pointers can be downcasted to their subtypes.

With only those two building blocks we can start building an interesting data structure:

To see the content of the tree we must cast it in LLDB.

Improving Visualization

LLDB provides two solutions to improve variable formatting.

  • Summary strings
  • Synthetics

Summaries are a good quick way to improve the readability of objects. A simple format string is used to build the object representation.

However, summaries have some limitations. They must use fields already present in the variable.

Synthetics are a more powerful tool. They allow adding “fake” children to composed types through python scripting.

To implement a Synthetic provider for a type we must implement this interface:

LinkedLists

Let’s start with LinkedLists. We don’t have to start from scratch, we can use the children already provided in LLDB.

Note that the GetChildAtIndex() match the C structure.

To register the synthetic we can use those commands in LLDB:

(lldb) command script import ~/Repos/blog/lldb_plugin/main.py
(lldb) type synthetic add -l main.ListSynthetic List

To automate this, we can create a .lldbinit in our home directory with the same lines:

command script import ~/Repos/blog/lldb_plugin/main.py
type synthetic add -l main.ListSynthetic List

Now if we look at our intList in LLDB we can see the newly added child.

Copying the size is not very useful, let’s unroll our list values instead.

We transform our renaming trick into a small function.

Now we want to access every value in the linked list. Conveniently, we have the list size. We can access it with the GetChildMemberWithName method. The .valuemethod of an SBValue always returns a string as displayed in the debugger. Thus we need to cast it to an int.

Let’s use this new function in our init:

As of now, there is no need to manually unroll the list anymore. We can see the result in LLDB.

The display of the unions is still not perfect. The list stores the true type of its data in the label field. We can use this label to downcast the union to its real value. (Vocabulary)

First, we need a function to retrieve an SBType object from a typename. Types of information are stored in a frame’s symbol context. Only basetype are stored, there is no match for Node*.

Then, we can create a method to retrieve the true SBType corresponding to the list kind. To do so we have to match the kind field with its different possibilities.

We can now use this kind to cast item values:

Now if we display our list in LLDB we should see item true value at the top level.

Composite Structs

The other building block we need is composite structs.

Because LLDB only sees *Node pointers, the formatting is very limited.

We can solve this issue in a similar way we did for unions in linked lists.

We start by creating a new synthetic:

Now we can implement a _kind_typemethod for the Node synthetic similar to the List one.

Then we can use this base type to downcast our initial valobj and extract its true children. Base and Pointer types must be handled differently. A base type must be converted to a pointer before being downcasted.

We can now extract children of the base_object_ptr. node child is excluded to avoid infinite recursion. We also add the kind value of the base object.

All those transformations would fail if the pointer is not initialized. We must enclose them in a try block. We end up with:

We can now visualize our nodes in LLDB:

This is even better in a GUI debugger (here Clion).

lldb extension image
 
Conclusion

LLDB scripting API is powerful. We have seen here how to use it to improve variable formatting, but it can also be used to add new commands or to implement smart breakpoints.

Improving variable formatting is an investment that pays off very quickly. Moreover, because in C most idioms are very common, a synthetic can be easily reused in a different context.

Sign up for our newsletter and stay up to date