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.