LLDB Custom Data Formatters for C in Python
Tags: lldb, c, python, debugging
Published: 2020-11-02 18:59:45

---
title: LLDB Custom Data Formatters for C in Python
published: true
description: Using Python to format C data structures in LLDB.
tags: lldb, c, python, debugging
---

## Introduction

If you use [LLDB](https://lldb.llvm.org/) to debug C or C++ programs, you can customize the output of LLDB’s print (`p`) command to [print the contents of variables in a more enlightening way](https://lldb.llvm.org/use/variable.html) than the default.  You can even use [Python](https://www.python.org) to do it. However, pretty much all the [examples](https://lldb.llvm.org/use/variable.html#python-scripting) I could find showed only relatively trivial data structures. After lots of searching and trial-and-error, I’ve figured out how to use Python to print a non-trivial `struct`.

## Data Structures

The C data structures that I want to print are from [cdecl](https://github.com/paul-j-lucas/cdecl) and include [the following](https://github.com/paul-j-lucas/cdecl/blob/master/src/slist.h) to implement a singly linked list (somewhat trimmed for this example):

```{c}
typedef struct slist      slist_t;
typedef struct slist_node slist_node_t;

struct slist {
  slist_node_t *head;           // Pointer to list head.
  slist_node_t *tail;           // Pointer to list tail.
};

struct slist_node {
  slist_node_t *next;           // Pointer to next node or null.
  void         *data;           // Pointer to user data.
};
```

This is then used to implement storing C++ scoped names, e.g., `S::T`:
```{c}
typedef struct slist c_sname_t; // C++ scoped name.
```
where each scope’s name is stored as an `slist_node` where `data` is actually a `c_scope_data*` where a `c_scope_data` contains data about each scope, in particular, its name:
```{c}
struct c_scope_data {
    char const *name;           // The scope's name.
    // ...
};
```

For `S::T`, there would be one `slist` with two nodes: one for `S` and the second for `T`.

When debugging in LLDB and printing a variable of type `c_sname_t`, I want LLDB to print the _entire_ scoped name `S::T`, i.e., traverse the list printing every node’s name and not just the default of printing the `head` and `tail` pointers of the `slist` itself.

## LLDB Python Modules

To do this, I implemented an LLDB Python module. Such a module starts with:
```{py}
# cdecl_lldb.py

import lldb

def __lldb_init_module(debugger, internal_dict):
    cmd_prefix = 'type summary add -F ' + __name__
    debugger.HandleCommand(cmd_prefix + '.show_c_sname_t c_sname_t')
```

The `__lldb_init_module` function is called once by `LLDB` to initialize a module where `debugger` is the instance of LLDB itself and `internal_dict` is required by the signature, but you never need to interact with it.  You can use the function to bind C types to custom formatters.

The lines:
```{py}
    cmd_prefix = 'type summary add -F ' + __name__
    debugger.HandleCommand(cmd_prefix + '.show_c_sname_t c_sname_t')
```
declare `cmd_prefix` as a shorthand to be used on subsequent lines (where `__name__` is the name of the current Python file without the `.py` extension) and `HandleCommand` binds a formatter (`-F`) to the `show_c_sname_t` function for the type `c_sname_t`.  (Yes, `typedef`d names are fine.)

Before we get to the implementation of `show_c_sname_t`, we’ll need a utility function to help traverse the data structure:
```{py}
def null(ptr):
    """Gets whether the SBValue is a NULL pointer."""
    return not ptr.IsValid() or ptr.GetValueAsUnsigned() == 0
```
(An `SBValue` is an LLDB data structure that stores the contents of a variable, register, or expression.)

The implementation of `show_c_sname_t` starts with:
```{py}
def show_c_sname_t(c_sname, internal_dict):
    colon2 = False          # Print "::" scope separator?
    rv = ""                 # "string-ified" value to return
```
Since a `c_sname_t` is a `typedef` for an `slist`, we first need to get at the list’s `head`, then traverse the list.  As it happens, `SBValue` has a `linked_list_iter()` function that can be used to traverse a linked list provided the name of the `struct` member containing the next pointer:
```{py}
    head = c_sname.GetChildMemberWithName('head')
    for slist_node_ptr in head.linked_list_iter('next'):
```

For each node, we have to get its `data` member, then cast the `void*` to a `c_scope_data_t*`.  To do the cast, we need to get a hold of the `c_scope_data_t*` type from within Python.  That can be done once prior to the loop with:
```{py}
    target = lldb.debugger.GetSelectedTarget()
    c_scope_data_ptr_t = target.FindFirstType('c_scope_data_t').GetPointerType()
```
The complete remainder of the function is now:
```{py}
    head = c_sname.GetChildMemberWithName('head')
    for slist_node_ptr in head.linked_list_iter('next'):
        void_data_ptr = slist_node_ptr.GetChildMemberWithName('data')
        if not null(void_data_ptr):
            c_scope_data_ptr = void_data_ptr.Cast(c_scope_data_ptr_t)
            name_ptr = c_scope_data_ptr.GetChildMemberWithName('name')
            if not null(name_ptr):
                if colon2:
                    rv += '::'
                else:
                    colon2 = True
                rv += name_ptr.GetSummary().strip('"')

    return '"' + rv + '"'
```
Given `name_ptr`, we use `GetSummary()` to get the actual name string.  `GetSummary()` puts quotes around its result that we must strip because we want quotes around the entire return value, not each scope’s name.

## Loading a Module

To get LLDB to load the module every time it starts, I first created an `.lldbinit` file in cdecl’s `src` directory:
```
# .lldbinit
command script import cdecl_lldb.py
```

In order to get LLDB to load it, you also need to [allow loading of `.lldbinit` files from the current working directory](https://reviews.llvm.org/rL261280). To do that, add:
```{lldb}
settings set target.load-cwd-lldbinit true
```
to your global `~/.lldbinit` file.

## Using Python Interactively

While trying to come up with the correct Python, rather than having to edit your script, save it, launch LLDB, set a breakpoint, and run your program every time (the “edit-compile-run loop”), you can drop into a Python interpreter from within LLDB and use it to do trial-and-error to determine the right Python code to print a variable the way you want it.  For example, given a function that contains:
```{c}
c_sname_t const *sname = find_name( /*...*/ );
do_something( sname );
```
you can set a breakpoint on `do_something()` and run your program. When the breakpoint is hit and you’re dropped into LLDB, you can further drop into a Python interpreter via the `script` command:
```{lldb}
(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
>>>
```
The first thing you have to do is get a hold of the `sname` variable from Python, more specifically from within the current stack frame:
```{py}
>>> sname = lldb.frame.FindVariable('sname')
```
Now, you can try out Python code interactively shortening the edit-compile-run loop.