Metric Panda Games

One pixel at a time.

Tips for Productive Debugging with GDB

In this post I’ll share my favorite tips for getting the most out of GDB.

Tip #1: Try GDB Dashboard

This may or may not be your cup of tea, but if, like me, you like having access to more program information at every break stop you may want to try GDB Dashboard.

It is a fantastic modular interface that comes as a .gdbinit replacement and, with all modules enabled, looks like this:

gdb Dashboard all modules

Take a look at the Github repository for the project for more information on how to customize it to fit your needs.

Tip #2: Use a global .gdbinit and a project .gdbinit

.gdbinit is the configuration file that gdb sources at startup. You can have one in your home directory (~/.gdbinit) for global settings, and one in the current directory (./.gdbinit) for project specific settings.

As you will see from the following tips, project specific settings for gdb can be useful for custom formatters or aliases.

Note: you must enable project specific .gdbinit files with the set auto-load local-gdbinit.

Tip #3: Use custom formatters

Custom C/C++ types can be noisy to inspect in the debugger, especially if implemented using unions.

With gdb’s Pretty Printing you can define custom formatters for your data types in order to make them more readable.

For example, in Rival Fortress I have a matrix data type (MPEMatrix4) that is a union of many anonymous structs for developer convenience. This is what it looks like before and after using a custom print formatter:

gdb Pretty Printing

And this is the source for the custom formatter:

class MPEMatrixPrinter:
  """Print a 4x4 matrix."""

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

  def to_string(self):
    return ("\n\t[ %3g %3g %3g %3g ]"
            "\n\t[ %3g %3g %3g %3g ]"
            "\n\t[ %3g %3g %3g %3g ]"
            "\n\t[ %3g %3g %3g %3g ]") % \
                (float(self.val["flat"][0]), float(self.val["flat"][1]), float(self.val["flat"][2]), float(self.val["flat"][3]),
                float(self.val["flat"][4]), float(self.val["flat"][5]), float(self.val["flat"][6]), float(self.val["flat"][7]),
                float(self.val["flat"][8]), float(self.val["flat"][9]), float(self.val["flat"][10]), float(self.val["flat"][11]),
                float(self.val["flat"][12]), float(self.val["flat"][13]), float(self.val["flat"][14]), float(self.val["flat"][15]))

def project_type_lookups(val):
  lookup_tag = val.type.tag
  if lookup_tag == None:
    return None

  match = re.match(r"^MPEMatrix(\d)$", lookup_tag)
  if match:
    return MPEMatrixPrinter(val, match.group(1))

gdb.pretty_printers.append(project_type_lookups)

It assumes that the matrix type has a flat[16] member field.

Tip #4: Use aliases

Aliases are an excellent way to speed up your day to day debugging. You specify them in your .gdbinit and look like this:

alias -a w = dashboard expression watch

Tip #5: Use automatic $ variables

Whenever you inspect something using the print command, gdb automatically stores the result in a variable like so:

>>> print Identity
$1 = 
        [   1   0   0   0 ]
        [   0   1   0   0 ]
        [   0   0   1   0 ]
        [   0   0   0   1 ]

The $1 is the automatic variable, and you can reference it later like so:

>>> print $1
$2 = 
        [   1   0   0   0 ]
        [   0   1   0   0 ]
        [   0   0   1   0 ]
        [   0   0   0   1 ]

Tip #6: Inspect array pointers

When working with plain C-style pointer to an array you can use the following command:

>>> print [email protected]

This will print 10 items from the Array pointer.

Tip #7: Enable command history

Enable command history by adding the following to your .gdbinit:

set history save on

By default the history file (.gdb_history) is saved in the current directory, but if you want you can share histories by explicitly setting the output filename with set history filename <fname>.

Tip #8: Detect if the debugger is running

This is not really a gdb specific tip, but it is useful nonetheless.

On Windows you can use the IsDebuggerPresent function to detect if a debugger is running. You can do the same on Unix-like systems using the following function:

  #ifndef _WIN32
  #include <sys/ptrace.h>
  static int IsDebuggerPresent()
  {
    static int Detected;
    static int RunningUnderDebugger;
    if (!Detected)
    {
      Detected = 1;
      RunningUnderDebugger = ptrace(PTRACE_TRACEME, 0, 0, 0) == -1;
    }
    return RunningUnderDebugger;
  }
  #endif