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): = gdb.selected_frame().older() frame while frame: if frame.name() == self.frame: return True = frame.older() frame 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.