3. gdb convenience functions

In the previous installment we found out how to write new gdb commands in Python.  Now we’ll see how to write new functions, so your Python code can be called during expression evaluation.  This will make gdb’s command language even more powerful.

A longstanding oddity of gdb is that its command language is unreflective.  That is, there is a lot of information that gdb has that is extremely difficult to manipulate programmatically.  You can often accomplish seemingly impossible tasks if you are willing to do something very ugly: for instance, you can use “set logging” to write things to a file, then use “shell” to run sed or something over the file to produce a second file in gdb syntax, then “source” the result.  This is a pain, not to mention slow.  The Python interface removes this hackery and replaces it with what you really want anyway: an ordinary programming language with a rich set of libraries.

If you use gdb regularly, then you already know about convenience variables.  These are special variables whose names start with a dollar sign, that can be used to name values.  They are part of gdb’s state, but you can freely use them in expressions:

(gdb) set var $conv = 23
(gdb) call function ($conv)

On the python branch, we introduce convenience functions.  These use a similar syntax (in fact the same syntax: they are actually implemented as convenience variables whose type is “internal function”), and when invoked simply run a bit of Python code.  As you might expect after reading the previous post, a new function is implemented by subclassing gdb.Function.  Our example this time is the one that got me hacking on python-gdb in the first place: checking the name of the caller.

import gdb
class CallerIs (gdb.Function):
    """Return True if the calling function's name is equal to a string.
This function takes one or two arguments.
The first argument is the name of a function; if the calling function's
name is equal to this argument, this function returns True.
The optional second argument tells this function how many stack frames
to traverse to find the calling function.  The default is 1."""

    def __init__ (self):
        super (CallerIs, self).__init__ ("caller_is")

    def invoke (self, name, nframes = 1):
        frame = gdb.get_current_frame ()
        while nframes > 0:
            frame = frame.get_prev ()
            nframes = nframes - 1
        return frame.get_name () == name.string ()

CallerIs()

Let’s walk through this.

The Python docstring is used as the help string in gdb.  Convenience function documentation is shown by typing “help function” in the CLI.

Function‘s initializer takes one argument: the name of the function.  We chose “caller_is“, so the user will see this as “$caller_is“.

When the function is invoked, gdb calls the invoke method with the arguments that the user supplied.  These will all be instances of the gdb.Value class.  A Value is how a value from the debuggee (called the “inferior” in gdb terminology) is represented.  Value provides all the useful methods you might expect, many in a nicely pythonic form.  For example, a Value representing a integer can be added to Python integers, or converted to one using the built-in int function. (See the manual for a full rundown of Value.)

gdb just tries to pass all the arguments the user supplied down to the invoke method.  That is, gdb doesn’t care about the function arity — so you can do fun things, like implement varargs functions, or, as you see above, use Python’s default value feature.

This invoke method takes a function name and an optional number of frames.  Then it sees if the Nth frame’s function name matches the argument — if so, it returns true, otherwise it returns false.  You can use this to good effect in a breakpoint condition, to break only if your function was called from a certain place.  E.g.:

(gdb) break function if $caller_is("main")

(Hmm.  Perhaps we should have just let the user register any callable object, instead of requiring a particular subclass.)

Value and its as-yet-unintroduced friend, Type, are actually a bit like Python’s ctypes — however, instead of working in the current process, they work on the process being debugged.  I actually looked a little at reusing the ctypes API here, but it was not a good fit.

Next we’ll revisit commands a little; specifically, we’ll see how to write a new “set” command, and we’ll explore the “require” command.  After that we’ll go back to writing new stuff.

2 Comments

  • [...] peuvent par exemple faciliter l’écriture de breakpoints conditionels. Nous allons reprendre un exemple et écrire une fonction caller_is(func_name), qui retourne vrai si l’appelant est bien [...]

  • A popen2.Popen3 connect to gdb, and When I write a command through Popen3 to gdb,if it will return somrthing immediately? I am reading some code about this now,I don’t what it return.

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>