Here is a quick way to pretty-print structures in GDB

Written by Dr Greg Law | Last updated 13th Dec 2021

Using pretty-printers can save you a lot of time staring at your computer screen and improve the flow of your debugging too!

In this tutorial, I start off with writing a basic pretty-printer to display the value of the si_signo field in the siginfo_t structure, before doing some more advanced stuff.

Why we need pretty-printers

We love debugging in GDB. We all use structures and classes in the code we write, and GDB strives to display what these are but misses the contextual information; when you use a handful of members, and in particular unions, then interpreting these more complicated structures quickly becomes overwhelming.

When GDB prints a value, it first checks whether there is a pretty-printer registered for that value. If there is, then GDB uses the pretty-printer to display the value. Otherwise, the value prints in the usual way. 

For example, in my previous tutorial, I used the print info command to confirm whether the inferior program received my Ctrl-C. But, because I didn't have a pretty-printer for the siginfo_t structure, the command returned all the data of the structure, including expanding the many unions that structure uses.

Output - Print Info

Messy and not easy to read.

See how easily you can write a basic pretty-printer

To start off, we write a basic pretty-printer that returns the si_signo value from the siginfo_t structure for an interrupt signal that the inferior program receives.

Almost every pretty-printer comprises of two main elements:

  • The lookup function to identify the value type, and
  • the printer function itself.

We write our program in Python. 

$ vim
# Start off with defining the printer as a Python object.

class SiginfoPrinter:

# The constructor takes the value and stores it for later.

def __init__(self, val):
self.val = val

  # The to_string method returns the value of the
# si_signo attribute of the directory.

def to_string(self):
signo = self.val['si_signo']
return str(signo)

# Next, define the lookup function that returns the
# printer object when it receives a siginfo_t.

# The function takes the GDB value-type, which, in
# our example is used to look for the siginfo_t.

def my_pp_func(val):
  if str(val.type) == 'siginfo_t': return SiginfoPrinter(val)

# Finally, append the pretty-printer as object/ function to
# the list of registered GDB printers.


# Our pretty-printer is now available when we debug
# the inferior program in GDB.

Now, run your inferior program and hit Ctrl-C to quit. Our pretty-printer returns the value 2; the value for a SIGINT.

(gdb) source
(gdb) print info
$4 = 2

Much easier to read.

I demonstrate this basic pretty-printer in my video. Do watch it here.

Of course, we can and should improve our printer. In my next example, I print the textual name of the si_signo member. I use a file called signames,  which simply contains a list of the signal numbers with their corresponding textual signal names.

class SiginfoPrinter:

def _init_(self, val):
  self.val = val

# Watch the variable type that you print.
# signames is an object and takes an integer variable.

def to_string(self):
  signo = int (self.val['si_signo'])
  signame = signames[signo]
return str(signo) + ' ('+ signame +')'

def my_pp_func(val):
  if str(val.type) == 'siginfo_t': return SiginfoPrinter(val)


When we run our inferior program again and hit Ctrl-C to quit, now the printer returns the value 2, plus the textual name SIGINT.

(gdb) source
(gdb) source
(gdb) print info
$5 = 2 (SIGINT)

 That’s pretty easy.

UDB Time Travel Debugger
Find and fix bugs in minutes  - including C/C++ concurrency issues
Learn more »

Are you ready to build out the pretty-printer some more?

The possibilities are endless. You can extract and print any value of a member in a structure with your pretty-printer program. In my next example, I use the code field to identify the kill signal, but also return the sender of that kill command.

class SiginfoPrinter:

def __init__(self, val):
  self.val = val

def to_string(self):
  signo = int(self.val['si_signo'])

  # Determine the signal with the code member,
# for example, 0 = kill, and 128 = Ctrl-C.

  code = int(self.val['si_code'])
  signame = signames[signo]

  # Determine sender of the kill with the union _sifields

  sender = self.val['_sifields']['_kill']['si_pid']

  # If the signal is a kill (code =0) then print sender.

  if code == 0:
  return str(signo) + ' ('+ signame +') sender = '+ str(sender)

  # For any other signal print the code.

  return str(signo) + ' ('+ signame +') code = '+ str(code)

def my_pp_func(val):
  if str(val.type) == 'siginfo_t': return SiginfoPrinter(val)


Writing pretty-printers pays off

That is it. We now have a pretty-printer for the siginfo_t structure. But, of course, you can extrapolate this handy printer to display whatever structure you want. 

Investing a very small amount of time in creating your pretty-printers upfront pays off quickly and almost certainly moves up your debugging a couple of gears. You will find that you become more productive and write code you can be truly proud of.

Consider making a gdbinit per project, and committing to your project’s source control so that everyone working on that project can benefit.

Job done!

In my video, I do explain and run all the code I have written in this tutorial, so I do encourage you to watch it here.

UDB Time Travel Debugger
Find and fix bugs in minutes  - save time on debugging
Learn more »

Learn more about pretty printers

Want to know more about what you can do with pretty printers? Check out Part 2

Do not miss my next GDB tutorial: sign up for the gdbWatchPoint mailing below.
Get tutorials straight to your inbox
Become a GDB Power User. Get Greg's debugging tips directly in your inbox every 2 weeks.
    Find and fix bugs in minutes, not days - rapidly fix test failures and concurrency defects in complex codebases.

Explore unfamiliar codebases with UDB

Learn more