Minimal integration between vim and GDB

2021-10-30

Since version 8.1 vim came bundled with termdebug, a plugin that somewhat integrates GDB within vim. While termdebug does a lot of things right, like providing bindable vim commands for program control, it can be quite awkward to work with multiple terminal windows within vim, especially when you need to both scroll and input in the interactive window.

This can get pretty disruptive for people that use tiling window managers and are not eager to C-w l instead of Mod-l, or to C-w N C-u/d instead of simply scrolling with Shift-PgUp/PgDn in the terminal emulator.

Don’t get me wrong, termdebug can probably be configured and hacked into submission in most cases, but some people (myself included) prefer to keep DGB in a separate window. There is, however, one major drawback to this setup: code browsing. While GDB can display the source code context of one or two dozen lines around the current position, that is many times insufficient and we need to hop back into vim, browse to the location and follow symbols from there.

In this sequence of events, browsing to the current location is the one that is most time consuming (and downright annoying). Achieving this automation is pretty straightforward tho, and only requires a small Python script (which you can grab from my GitHub repository).

This approach does not use any vim plugins, but actually the client-server capability of vim. When started with the --servername argument vim will listen to remote keystrokes which can be sent by using the vim binary with the --remote-send argument. For example, one can start vim:

vim --servername myserv

and send keystrokes from another terminal with:

vim --servername myserv --remote-send '<Esc>iHello World!<CR><Esc>'

It should be obvious that we can use this to open file.cpp in the current buffer and browse to line 123:

vim --servername myserv --remote-send '<Esc>:edit /path/to/file.cpp<CR>:123<CR>zz'

There are two notes to this: the server can be started within vim at a later state by running the vim command

:call remote_startserver('myserv')

but once a server has been started it cannot be stopped and its name cannot be changed.

In order to send these commands form GDB we can use its support for Python scripts; in .gdbinit we can add:

source myscript.py

and GDB will load the script and execute it on startup.

Getting the current location can be extracted from the current frame through a symtab and line object:

sal = gdb.selected_frame().find_sal()
return sal.symtab.fullname(), sal.line

Communicating with vim can be achieved by a simple os.system() call:

keystrokes = '<Esc>:edit {}<CR>:{}<CR>zz'.format(file, line)
os.system('vim --servername {} --remote-send "{}"'.format(server_name, keystrokes))

and the above can be made to run every time GDB presents us with a prompt:

def prompt_handler():
    f, l = get_location()
    vim_goto_location(f, l)

gdb.events.before_prompt.connect(prompt_handler)

Everything can be bundled in a GDB command by extending the gdb.Command class, with options for starting/stopping the following behavior and setting the server name.

There we go! Fancy, schmancy debuggy thingy, and in my opionion enough for efficient context browsing during debugging.