osa1 github gitlab twitter cv rss

gdb breakpoints with conditions on backtrace

April 25, 2020 - Tagged as: en, gdb.

Being able so specify conditions in gdb breakpoints is quite useful. For example, if I’m interested in mmap(NULL, ...) calls I can do

break mmap if addr == 0

and gdb doesn’t break on mmap when the addr == 0 condition doesn’t hold.

I’ve used this many times to great effect, but it’s not always sufficient, sometimes I need to break not when a variable or argument has a specific value but the function is called (directly or indirectly) from another function. For example, when debugging a GHC RTS issue I sometimes want to inspect mmap calls made by the garbage collector.

As far as I know this is not possible using the standard break syntax, but gdb provides a Python API that allows setting breakpoints with conditions implemented in Python. Using this API it’s takes a few lines to implement this:

class FrameBp(gdb.Breakpoint):
    def __init__(self, spec, *args, frame=None, **kwargs):
        self.frame = frame
        super(FrameBp, self).__init__(spec, *args, **kwargs)

    def stop (self):
        frame = gdb.selected_frame().older()

        while frame:
            if frame.name() == self.frame:
                return True

            frame = frame.older()

        return False

When calling the constructor the first argument is the breakpoint specifier, which is basically the part after break ... in gdb’s break command. The frame argument is the function we look for before actually breaking. We only break if the function exists in the backtrace. Here’s an example use:

>>> python FrameBp("mmap", frame="GarbageCollect")
Breakpoint 1 at 0x7f3366243f00: file ../sysdeps/unix/sysv/linux/mmap64.c, line 44.

This will only break on mmap if the backtrace has GarbageCollect at some point. An example backtrace when the breakpoint is hit:

Breakpoint 1, __GI___mmap64 (addr=0x4200200000, len=1048576, prot=3, flags=50, fd=-1, offset=0) at ../sysdeps/unix/sysv/linux/mmap64.c:44
44        if (offset & MMAP_OFF_MASK)

>>> bt
#0  __GI___mmap64 (addr=0x4200200000, len=1048576, prot=3, flags=50, fd=-1, offset=0) at ../sysdeps/unix/sysv/linux/mmap64.c:44

...

#19 0x0000000003022c83 in GarbageCollect (collect_gen=0, do_heap_census=false, deadlock_detect=false, gc_type=0, cap=0x37ef500
<MainCapability>, idle_cap=0x0) at rts/sm/GC.c:449

...

With some effort you could probably turn this into a proper gdb command and run it without the python ... part, but so far this works good enough for me.