GDB debugging: How to write user-defined commands in Python

Last time we looked at user-defined commands in the GDB command-line. In this GDB tutorial, we look at writing our user-defined functions in Python.

In this tutorial, Greg Law shows you how to write user-defined GDB commands in Python.

The advantage of creating user-defined commands in Python is that you can automate a more complicated task or something that you might want to check into source control and share with your teammates. Using Python is more robust too; it’s more extensible and although a bit more work, it gives you better results in multiple ways.

Boilerplate to get you started

Let’s start with the initial boilerplate for our Python code. We write it in a file; bugreport.py.

$ vim bugreport.py

We create a class which we call BugReport, and we inherit it from the gdb.command class.

class BugReport (gdb.Command):

We give it a docstring, so you get an inbuilt type of help in the GDB command-line.

"""Collect required info for a bug report"""

We need to give it a constructor. We call a superclass constructor; we give it a name and a command. There are different command-classes, for example, maintenance commands, obscure commands and various others. We go with COMMAND_USER.

def__init__(self):
super(BugReport, self).__init__("bugreport", gdb.COMMAND_USER)

We make a function that we call invoke, which is where the magic happens. This function takes an arg, which is just a string that contains all the arguments. It also has a boolean from_tty, so we know whether it ran from a script or the command line. The body of the function is not terribly interesting for this GDB tutorial, let's just have it get a backtrace of all threads and the kernel version.

def invoke(self, arg, from_tty):
gdb.execute("thread apply all backtrace full")
import os
os.system("uname -a")

Finally, we need to instantiate one of these classes; otherwise, nothing is going to happen.

BugReport()

Now from the GDB command-line, you can source bugreport.py and hey presto, you have a new command called bugreport.

Watch my video for this GDB tutorial to see how these few lines of Python code create a simple user-defined command for a bug report and with help text too.

Pagination on/ off?

In part 1 of this GDB tutorial series, you may recall that we sorted pagination with a bit of a hack; we just turned pagination off with a “hammer.” The problem with that is that we must not forget to turn it back on, otherwise, that could potentially cause some confusion. But what if the user had themselves already elected to turn pagination off?

Happily, now we’ve got Python so we can do things properly.

We could, for example, add pagination to our invoke function to ensure we end the command with pagination turned on or off, whatever your preference.

def invoke(self, arg, from_tty):
pagination = gdb.execute("show pagination", to_string=True).endswith("on.\n")
if pagination: gdb.execute("set pagination off")
gdb.execute('thread apply all backtrace full')
import os
os.system('uname -a')
if pagination: gdb.execute("set pagination on")

The to_string=True is a handy little thing that executes the command but returns a string rather than writes it to the console.

There is a better way with gdb.parameter().

def invoke(self, arg, from_tty):
pagination = gdb.parameter("pagination")
if pagination: gdb.execute("set pagination off")
gdb.execute('thread apply all backtrace full')
import os
os.system('uname -a')
if pagination: gdb.execute("set pagination on")

Check out my video to see how this code for pagination works.

Write bug report output to a file

You may find it helpful if the bug report output went straight into a file, so let’s do that now.

We could do a simple gdb.execute("set logging file"), but we are writing in Python, why not do this a little bit more cleanly?

def invoke(self, arg, from_tty):
pagination = gdb.parameter("pagination")
if pagination: gdb.execute("set pagination off")
f = open("/tmp/bugreport.txt", "w")
f.write(gdb.execute("thread apply all backtrace full", to_string=True))
f.close()
import os
os.system("uname -a >> /tmp/bugreport.txt")
if pagination: gdb.execute("set pagination on")

Note: Make sure you open the file for writing; adding “w”. Also, use to_string=True again to write the output to a string, not to the console.

Watch the video to see how this code for file logging works.

Final Python code

To help you get started quickly, create a file, for example, mybugreport.py.

$ vim mybugreport.py

Copy and paste the final Python code.

import osclass BugReport (gdb.Command):
"""Collect required info for a bug report"""
def__init__(self):
super(BugReport, self).__init__("bugreport", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
pagination = gdb.parameter("pagination")
if pagination: gdb.execute("set pagination off")
f = open("/tmp/bugreport.txt", "w")
f.write(gdb.execute("thread apply all backtrace full", to_string=True))
f.close()
os.system("uname -a >> /tmp/bugreport.txt")
if pagination: gdb.execute("set pagination on")
BugReport()

Done!

I hope you found this GDB tutorial a useful introduction to user-defined commands in GDB, and that it motivates you to start creating your user-defined commands.

In my next GDB tutorial, we’ll look at how to use the hook command in user-defined commands; both in the GDB command-line and in Python.

Don’t forget to watch, subscribe, like, and share my video.

Good luck debugging.

Originally published at https://undo.io.

LiveRecorder is an enterprise software failure replay platform. We help developers Record, Replay & Resolve bugs & accelerate MTTR. https://undo.io