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 “
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
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.
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.