Tuesday, February 7, 2012

Backporting "watch -l" from GDB 7.4+ using Python

GDB 7.4 added a very useful "-l / -location" option to the "watch expr" command[1]:

"Ordinarily a watchpoint respects the scope of variables in expr. The -location argument tells gdb to instead watch the memory referred to by expr. In this case, gdb will evaluate expr, take the address of the result, and watch the memory at that address.".

i.e. If you do "watch p->field_that_gets_corrupted" the watch point will get deleted when local variable p goes out of scope while "watch -l" works the way you want.

For those stuck using GDB 7.2 (as shipped in Ubuntu 11.04) the following GDB Python script ( gdb-watch-location.py) might help:

import gdb

def _watch_location(expr, watch_point_class):
    l = gdb.parse_and_eval(expr).address
    wp_str = '*(%(type)s)(%(address)s)' % dict(type=l.type,
                                               address=l)
    gdb.Breakpoint(wp_str, gdb.BP_WATCHPOINT, watch_point_class)

class _WatchLocationCommand(gdb.Command):
    'Like "watch -l" in gdb 7.4+'
    def __init__(self):
        gdb.Command.__init__(self, 'watch-l',
                             gdb.COMMAND_BREAKPOINTS,
                             gdb.COMPLETE_SYMBOL)

    def invoke(self, arg, from_tty):
        _watch_location(arg, gdb.WP_WRITE)

class _RWatchLocationCommand(gdb.Command):
    'Like "rwatch -l" in gdb 7.4+'
    ...

class _AWatchLocationCommand(gdb.Command):
    'Like "awatch -l" in gdb 7.4+'
    ...

_WatchLocationCommand()
_RWatchLocationCommand()
_AWatchLocationCommand()

Suppose you're working with the following C code snippet:

struct bag {
        int a, b;
};

static void bag_poke(struct bag *p)
{
        p->a = 1;
}

Here's how gdb-watch-locations.py works:

In [1]: gdb.parse_and_eval('p->a')
Out[1]: <gdb.Value at 0x294a070>
  • A gdb.Value has a type and an address
In [2]: v = gdb.parse_and_eval('p->a')

In [3]: print v.type, v.address
int 0x7fffffffdc30
  • We use that to create a GDB watchpoint expression which we later pass to gdb.Breakpoint:
In [4]: '*(%(type)s)(%(address)s)' % dict(type=v.address.type, address=v.address)
u'*(int *)(0x7fffffffdc30)'
  • The code then adds three new GDB commands: "watch-l", "rwatch-l" and "awatch-l". Tells GDB that they should be classified as breakpoint commands in the online help system and use symbols for TAB completion.

Sample session:

$ gdb -x gdb-watch-locations.py data-access
(gdb) break bag_poke
Breakpoint 1 at 0x40050c: file data-access.c, line 9.
(gdb) run
Starting program: /home/scottt/work/scottt-gdb/data-access 

Breakpoint 1, bag_poke (p=0x7fffffffdc40) at data-access.c:9
9  p->a = 1;
(gdb) wa
watch    watch-l  
(gdb) watch-l p->a
Hardware watchpoint 2: *(int *)(0x7fffffffdc40)
(gdb) continue 
Continuing.
Hardware watchpoint 2: *(int *)(0x7fffffffdc40)

Old value = 0
New value = 1
bag_poke (p=0x7fffffffdc40) at data-access.c:10
10 }

See Also