Skip to content

Gdb's Pretty Print feature

Ernie Pasveer edited this page Nov 3, 2024 · 1 revision

Introduction

Gdb has a feature that can customize the printing of a class/struct/object. This is particularly helpful when printing template classes and containers.

Consider this simple std::example. (Credits: [1] [2])

#include <string>
std::string str = "hello world";
int main ()
{
  return 0;
}

When printing the value of str, you may get this, which is hard to read:

(gdb) print str
$1 = {static npos = 4294967295,
  _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, 
  _M_p = 0x804a014 "hello world"}}

All we want to see is this:

(gdb) print str
$1 = hello world

This Wiki is an example of implementing your own gdb Pretty Printer.

Preparation.

This Wiki will use a different example. Two simple C++ structs. One struct is called Person and the other struct is called Location. Location will be a member of Person.

Here's the C++ code:

#include <string>

struct Location {
    std::string      city;
    std::string      state;
    int              zip;
    unsigned int     cell;
};

struct Person {
    std::string      name;
    int              age;
    float            salary;
    struct Location  location;
};

int main (int argc, char** argv) {

    Location where;
    where.city         = "Houston";
    where.state        = "Texas";
    where.zip          = 77063;
    where.cell         = 2226669999;


    Person me;
    me.name            = "Pasveer, Ernie";
    me.age             = 60;
    me.salary          = 0.25;
    me.location.city   = "Houston";
    me.location.state  = "Texas";
    me.location.zip    = 77063;
    me.location.cell   = 2226669999;

    return 0;
}

To compile it, simply use this:

$ g++ -g -o hellopp hellopp.cpp

For now, I'll just use gdb to show how it prints these structs.

$ gdb -q hellopp 
Reading symbols from hellopp...
(gdb) b 35
Breakpoint 1 at 0x400866: file hellopp.cpp, line 35.
(gdb) run
Starting program: hellopp 

Breakpoint 1, main (argc=1, argv=0x7fffffffd208) at hellopp.cpp:35
35          return 0;

(gdb) p where
$1 = {city = "Houston", state = "Texas", zip = 77063, cell = 2226669999}
(gdb) p me
$2 = {name = "Pasveer, Ernie", age = 60, salary = 0.25, location = {city = "Houston", state = "Texas", zip = 77063, cell = 2226669999}}
(gdb) 

It's not too bad looking. But this is just a simple case. Structs and classes can get quite messy. But we now have a test program that we can play with.

Gdb's Pretty Print.

First, here's some good resources to read over and learn. I must admit, I feel this feature of gdb not documented very well. The first thing you need is a good understanding of Python. Pretty Printing is implemented with gdb's use of python.

https://sourceware.org/gdb/current/onlinedocs/gdb.html/Pretty-Printing.html#Pretty-Printing https://sourceware.org/gdb/current/onlinedocs/gdb.html/Pretty_002dPrinter-Introduction.html#Pretty_002dPrinter-Introduction https://sourceware.org/gdb/current/onlinedocs/gdb.html/Writing-a-Pretty_002dPrinter.html#Writing-a-Pretty_002dPrinter https://sourceware.org/gdb/current/onlinedocs/gdb.html/Pretty_002dPrinter-Example.html#Pretty_002dPrinter-Example https://sourceware.org/gdb/current/onlinedocs/gdb.html/Pretty_002dPrinter-Commands.html#Pretty_002dPrinter-Commands

Lets start with the Location struct. Here's the Python code.

$ more LocationStruct_pp.py
import gdb

# The PrettyPrinter object.
class LocationPrinter:

    # Save a copy of the Location.
    def __init__(self, val):
        self.__val = val

    # Convert it to a string. gdb will call this function.
    def to_string(self):
        ret = ""

        # Construct a python string with the values of the Location struct.
        # It's good to handle any errors. Hence the try/catch.
        try:
            ret = "Location=(" + str(self.__val['city']) + "," + str(self.__val['state']) + "," + str(self.__val['zip']) + "," + str(self.__val['cell']) + ")"

        except Exception as e:
            # Any exception will be saved in the return string.
            ret = str(e)

        return ret

    # A hint for gdb on the return type.
    def display_hint (self):
        return 'string'

# Specify a function to detect a Location struct type and will call the PrettyPrinter object for it.
#
# gdb maintains a large list of PrettyPrinters for lots of data/class types. It will go through the
# list looking for one that matches the type. If one is found, it uses it. Otherwise, gdb will
# default to the regular way of printing data types.
def LocationPrinter_func(val):
    if str(val.type) == 'Location':
        return LocationPrinter(val)

# Add the function to gdb's list of PrettyPrinter functions.
gdb.pretty_printers.append(LocationPrinter_func)

The basic flow is you create a Python class to handle the printing of Location. You then register this class with gdb via a Python function.

When gdb wants to print a value for a variable/struct/class, it first checks with all the Pretty Printers that are registered. If there is one, it is used.

Using our Pretty Printer - Location struct.

So now we have a Pretty Printer for the Location class in a Python .py file. Lets use it. Before we tell gdb to Run our program, we need to load the Python file.

$ gdb -q hellopp 
Reading symbols from hellopp...
(gdb) source LocationStruct_pp.py
(gdb) b 35
Breakpoint 1 at 0x400866: file hellopp.cpp, line 35.
(gdb) run
Starting program: hellopp 

Breakpoint 1, main (argc=1, argv=0x7fffffffd208) at hellopp.cpp:35
35          return 0;
(gdb) p where
$1 = "Location=(\"Houston\",\"Texas\",77063,2226669999)"
(gdb) 

This is how it looked without a Pretty Printer.

(gdb) p where
$1 = {city = "Houston", state = "Texas", zip = 77063, cell = 2226669999}

Not a huge difference. But you can see how you can customize it.

Using our Pretty Printer - Person struct.

So I won't list the Pretty Printer for the Person struct here in the Wiki. But you can get it, and all other files, from here:

https://github.com/epasveer/seer/tree/main/tests/hellopp

When you look at the Pretty Printer for Person, you'll see that it doesn't have any knowledge of Location. Gdb will see the value of Location and will use the Pretty Printer for that. It all just works.

$ gdb -q hellopp 
Reading symbols from hellopp...
(gdb) source LocationStruct_pp.py
(gdb) source PersonStruct_pp.py 
(gdb) b 35
Breakpoint 1 at 0x400866: file hellopp.cpp, line 35.
(gdb) run
Starting program: hellopp 

Breakpoint 1, main (argc=1, argv=0x7fffffffd208) at hellopp.cpp:35
35          return 0;

(gdb) p me
$1 = "Person=(\"Pasveer, Ernie\",60,0.25,\"Location=(\\\"Houston\\\",\\\"Texas\\\",77063,2226669999)\")"
(gdb) 

Typically, the sourcing of the Python .py files is put into your .gdbinit file in your home directory. This way you won't have to do it manually.

$ more ~/.gdbinit 

source /path/to/my/gdb/scripts/LocationStruct_pp.py
source /path/to/my/gdb/scripts/PersonStruct_pp.py

How things look in Seer

So this is how Seer shows the Person and Location struct without a Pretty Printer.

image

And this is how it looks with the Pretty Printers. Note the single line description of the variables. No nesting.

image

Summary

Pretty Printers allow for a simplified view of a struct or class. There are many good cases to use them. The std:: library already has them for containers and strings.

Certainly my example is a simple case and you could argue Pretty Printers are not needed. However, having worked on some large code bases, there are some complex struct and class hierarchies that can be better displayed in gdb with Pretty Printers.

There are some more Pretty Printer features, including better handling of large amount of printers for large numbers of structs and classes. I'll leave that for another day.

Let me know what you would like added or described better.