GDB/Python: Executing Code Upon Events
When I script GDB to develop model-centric debugging support, I often need to execute code upon specific events, namely after breakpoints hits. It looks like that:
import gdb class MyBP(gdb.Breakpoint): def __init__(self): gdb.Breakpoint.__init__(self, "a_function") self.silent = True def stop(self): print "##### breakpoint" return True # stop the execution at this point MyBP()
However, in this Breakpoint::stop callback, you're not free to do what ever you want:
- alter the execution state of the inferior (i.e., step, next, etc.),
- alter the current frame context (i.e., change the current active frame), or
- alter, add or delete any breakpoint.
As a general rule, you should not alter any data within gdb or the inferior at this time.
That's a "general rule" in the documentation, however it's not enforced at runtime, so in practice, there are many things you can do, but the result in not guaranteed!
What can go wrong?
I sometimes have to delete breakpoint, and I thought that gdb.post_event(this_bp.delete) would be safe. And it is, if your GDB is configured with set height 0. You don't see the link? Fair enough, it's very tricky! set height 0 tells GDB not to stop when the screen is full, which is an artifact of the past.
When GDB does stop at the end of the window, it processes the "posted events", and actually deletes the breakpoint structure. However the breakpoint is not removed from all GDB low-level lists, and than leads to a segmentation fault when GDB tries to access it. Here are the stack traces (deletion and invalid access) from Valgrind that helped me to figure out what was happening:
Address 0x18c0bd40 is 0 bytes inside a block of size 200 free'd # stack of the free(breakpoint) at 0x4A07577: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) by 0x763558: xfree (common-utils.c:98) # here the breakpoint structure is freed by 0x58D49D: delete_breakpoint (breakpoint.c:14074) by 0x525F92: bppy_delete_breakpoint (py-breakpoint.c:287) # that's the breakpoint delete function by 0x3BB384A0D2: PyObject_Call (in /usr/lib64/libpython2.7.so.1.0) by 0x3BB38DC026: PyEval_CallObjectWithKeywords (in /usr/lib64/libpython2.7.so.1.0) by 0x522C9C: gdbpy_run_events (python.c:934) # process Python events by 0x4AC258: run_async_handler_and_reschedule (ser-base.c:137) by 0x4AC328: fd_event (ser-base.c:182) by 0x61C281: handle_file_event (event-loop.c:762) by 0x61B768: process_event (event-loop.c:339) by 0x61B80A: gdb_do_one_event (event-loop.c:391) Invalid read of size 8 # stack of the illegal memory access at 0x57C2B9: bpstat_explains_signal (breakpoint.c:4430) # here the breakpoint structure is accessed by 0x5FCFE3: handle_signal_stop (infrun.c:4474) by 0x5FC484: handle_inferior_event (infrun.c:4110) by 0x5FA831: fetch_inferior_event (infrun.c:3261) by 0x61E506: inferior_event_handler (inf-loop.c:57) by 0x4BC10E: handle_target_event (linux-nat.c:4448) by 0x61C281: handle_file_event (event-loop.c:762) by 0x61B768: process_event (event-loop.c:339) by 0x61B80A: gdb_do_one_event (event-loop.c:391) by 0x727F0D: maybe_wait_sync_command_done (top.c:386) by 0x728199: execute_command (top.c:478) by 0x6148E7: catch_command_errors (main.c:373)
Among the other thing that cannot be done (and that I wanted to do), we find thread switching:
def stop(self): print("##### breakpoint") gdb.selected_inferior().threads().switch() gdb.selected_inferior().threads().switch() return False # don't stop
which leads to another segfault:
[New Thread 0x7ffff7fca700 (LWP 22661)] ##### breakpoint [Thread 0x7ffff7fca700 (LWP 22661) exited] [Inferior 1 (process 22657) exited normally]  22651 segmentation fault (core dumped) gdb-fedora -ex "source test.py" ./thread -ex run
or altering the instruction pointer of the inferior:
[New Thread 0x7ffff7fca700 (LWP 23100)] ##### breakpoint Traceback (most recent call last): File "test.py", line 11, in stop gdb.execute("next") gdb.error: Cannot execute this command while the selected thread is running.
But what if you really what to do it ?! Then you have to find alternatives! and there are a few (but they assume you want to stop the execution at this breakpoint, and give the prompt back to the user).
class MyBP(gdb.Breakpoint): def stop(self): print("##### breakpoint") gdb.events.stop.connect(stop_event) gdb.prompt_hook = prompt gdb.post_event(posted_event) return True
Subscribing to the stop events
def stop_event(evt): print("#### stop event") gdb.events.stop.disconnect(stop_event)
Hooking the prompt:
def prompt(current): print("#### prompt") gdb.prompt_hook = current
Posting events (which seem to work well when height=0)
def posted_event(): print("#### posted event")
These callbacks are executed in this order:
##### breakpoint #### stop event #### prompt (gdb) #### posted event
Note that the prompt hook is executed right before giving the control to the user, so gdb.execute("<command>") works very close to what you can expect from the command line. That saved the work of my afternoon yesterday!