Phil Muldoon added support for breakpoints to the Python API in gdb this past year. While work here is ongoing, you can already use it to do neat things which can’t be done from the gdb CLI.
The interface to breakpoints is straightforward. There is a new Breakpoint
class which you can instantiate. Objects of this type have various attributes and methods, corresponding roughly to what is available from the CLI — with one nice exception.
The new bit is that you can subclass Breakpoint
and provide a stop
method. This method is called when the breakpoint is hit and gets to determine whether the breakpoint should cause the inferior to stop. This lets you implement special breakpoints that collect data, but that don’t interfere with other gdb operations.
If you are a regular gdb user, you might think that this is possible by something like:
break file.c:73 commands silent python collect_some_data() cont end
Unfortunately, however, this won’t work — if you try to “next
” over this breakpoint, your “next
” will be interrupted, and the “cont
” will cause your inferior to start running free again, instead of stopping at the next line as you asked it to. Whoops!
Here’s some example code that adds a new “lprintf
” command. This is a “logging printf” — you give it a location and (gdb-style) printf
arguments, and it arranges to invoke the printf
at that location, without ever interrupting other debugging.
This code is a little funny in that the new breakpoint will still show up in “info break
“. Eventually (this is part of the ongoing changes) you’ll be able to make new breakpoints show up there however you like; but meanwhile, it is handy not to mark these as internal breakpoints, so that you can easily delete or disable them (or even make them conditional) using the normal commands.
import gdb class _LPrintfBreakpoint(gdb.Breakpoint): def __init__(self, spec, command): super(_LPrintfBreakpoint, self).__init__(spec, gdb.BP_BREAKPOINT, internal = False) self.command = command def stop(self): gdb.execute(self.command) return False class _LPrintfCommand(gdb.Command): """Log some expressions at a location, using 'printf'. Usage: lprintf LINESPEC, FORMAT [, ARG]... Insert a breakpoint at the location given by LINESPEC. When the breakpoint is hit, do not stop, but instead pass the remaining arguments to 'printf' and continue. This can be used to easily add dynamic logging to a program without interfering with normal debugger operation.""" def __init__(self): super(_LPrintfCommand, self).__init__('lprintf', gdb.COMMAND_DATA, # Not really the correct # completer, but ok-ish. gdb.COMPLETE_SYMBOL) def invoke(self, arg, from_tty): (remaining, locations) = gdb.decode_line(arg) if remaining is None: raise gdb.GdbError('printf format missing') remaining = remaining.strip(',') if locations is None: raise gdb.GdbError('no matching locations found') spec = arg[0:- len(remaining)] _LPrintfBreakpoint(spec, 'printf ' + remaining) _LPrintfCommand()
4 Comments
The gdb breakpoint API is neat! I used it to try enlighten gdb about the private loader my project uses:
http://code.google.com/p/dynamorio/source/browse/trunk/tools/libdynamorio.so-gdb.py
There’s some major problems with the approach, but it was good enough for making debugging liveable.
I would like to know what the problems are. This is a specific use case I would like to be able to support; in particular so we can handle things like kernel modules without having to add specific knowledge to gdb.
I skimmed the source you pointed to but didn’t see anything jump out.
Is there anything I can do to prevent gdb from paging output if the only output has been from lprintf?
You can disable pagination using “set pagination”. It should be reasonably easy to do this temporarily from Python.