Categories
Uncategorized

Debugging in Vim

Vim 8.1 was released in May 2018. The “main new feature” was official support for running a terminal within vim. Along with this came a built-in debugger plugin, termdebug, which provides a visual interface for interacting with gdb. This post walks through an example session using termdebug.

Let’s use termdebug to step through and inspect the following C program that calculates the factorial of a number. I’ll be using Ubuntu 18.04, along with a version of vim installed from the Ubuntu repo—8.0.1453—that includes the relevant feature that was officially released as part of vim 8.1.

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
/*
* Calculates factorial.
* Overflow is not checked/handled.
*/
int factorial(int x) {
assert(x >= 0);
int result = 1;
for (int i = 1; i <= x; ++i) {
result *= i;
}
return result;
}
int main(int argc, char* argv[]) {
assert(argc == 2);
int x = atoi(argv[1]);
int result = factorial(x);
printf("%d\n", result);
return EXIT_SUCCESS;
}
view raw factorial.c hosted with ❤ by GitHub

In addition to vim, a working version of gdb is required for debugging. For Ubuntu, this can be installed from the Ubuntu repo. For macOS, gdb will have to be code signed, and I’ve found that gdb versions earlier than 8.3 are problematic (tested on macOS Mojave). I haven’t tested debugging on Windows. Before trying to debug with vim, I suggest verifying that gdb works as expected on your system.

The path and/or name of gdb can be modified by setting the termdebugger variable before invoking the termdebug plugin.

Loading the termdebug Plugin

After loading the factorial.c source code, let’s load the plugin.

:packadd termdebug
:Termdebug

The tab will split into three windows, which I’ve rearranged so that the editor window is on the right, as shown below.

The top-left window is for interacting with gdb. The bottom-left window is for interacting with the program loaded by gdb. The editor window on the right—with the corresponding source code—will show breakpoints and highlight lines when stepping through a program. This window also has clickable buttons at the top for interacting with gdb.

Loading a Program

We’ll have to compile the code using the -g flag, which will include debugger information in the compiled program.

:!gcc -g factorial.c -o factorial

We can use the gdb window to load the program. In addition to <c-w> commands and/or mouse clicks, we can use the :Gdb command to jump to the gdb window. The following gdb command will load the program (highlighted in the image that follows).

(gdb) file factorial

Alternatively, we could have passed factorial as an argument when calling :Termdebug earlier.

Setting Breakpoints

Breakpoints can be set in the gdb window using ordinary gdb commands.

(gdb) b factorial
(gdb) b 23

Alternatively, breakpoints can be set by navigating to a line of code in the editor window and entering :Break.

Lines with breakpoints are indicated by >> in the editor window.

Breakpoints can be removed with gdb commands or by navigating to the relevant lines and entering :Delete. Update 2019/05/12 6:45pm ET: On my version of vim, :Delete is used to remove breakpoints, but this was changed to :Clear in a commit on March 3, 2018.

Program Execution

The :Run command starts the program.

:Run [args]

Or alternatively, a program can be launched by entering the run command directly in the gdb window.

(gdb) run [args]

In either case, [args] should be replaced by the program’s arguments. Let’s use the value 6 to run the factorial program.

Both :Run and (gdb) run will pause execution at the first breakpoint. An alternative way to launch a program is to use gdb’s start command, which will pause execution at the beginning of the program.

I’ve launched the program using the vim command :Run 6. Execution is paused at the first breakpoint, with the corresponding line highlighted.

Stepping through Code

There are various ways to step through a program.

  1. Invoking gdb stepping commands directly in the gdb window
  2. Clicking Step, Next, Finish, Cont, Stop, Eval in the editor window
  3. Issuing vim commands :Step, :Over, :Finish, :Continue, :Stop

Program Inspection

The values of variables can be inspected in various ways.

  1. Invoking gdb inspection commands directly in the gdb window
  2. Hovering your mouse over variables in the editor window
    1. This requires the +balloon_eval compile-time feature
    2. This worked for me in gvim, but didn’t work reliably when running vim in a terminal
  3. Issuing vim command :Evaluate {expr}
    Omitting {expr} will evaluate the expression under the cursor, which can also be performed by pressing K

Conclusion

After passing all breakpoints and running the program until termination, we can see 720 in the program window, which is the expected output for the calculation of 6!.

The built-in help page includes thorough documentation.

:help terminal-debug

40 replies on “Debugging in Vim”

Hi Beamer, I’ve rearranged the windows so that the editor window is on the right. To do this, I moved the cursor to the editor window and entered ctrl-w L.

Documentation for moving windows is at :help window-moving.

Hi Daniel. Are you aware of any plugin/.vimrc which builds on termdebug AND Vim 8.1’s terminal windows to provide an easier workflow?

I’m referring to keys being mapped to :Step, :Next, and the likes, for instance, as 6 or more keystrokes everytime I simply want to move to the next line are a bit too much.

Furthermore, I see that commands like :packadd termdebug and :Termdebug vim have to be run everytime I start a new Vim session.

I wonder if such a thing exists. There are some results on GitHub, but having your thoughts on this topic would be useful!

Hi Enrico,

I’m not aware of any plugins that build upon termdebug.

:packadd termdebug can be added in your .vimrc so that you don’t have to enter that command for each Vim session.

" Source the termdebug plugin
packadd termdebug

With that added, termdebug can be launched with :Termdebug or with a custom mapping in your .vimrc, like the example below that loads termdebug with <leader>td (where <leader> would be a backslash, unless modified from its default).

" Add mapping to load termdebug
noremap <silent> <leader>td :Termdebug<cr>

There are various ways to speed up the entry of :Step, :Over, and the others. The following approaches are mentioned in the blog post:

  1. Invoke gdb stepping commands directly in the gdb window
  2. Click Step, Next, Finish, Cont, Stop, Eval in the editor window

In case you’re looking for an alternative approach, the following custom mappings—or some variation—for your .vimrc may be helpful. These map the stepping commands to <leader>s and <leader>o (where <leader> would be a backslash, unless modified from its default).

" Add mappings for :Step and :Over
noremap <silent> <leader>s :Step<cr>
noremap <silent> <leader>o :Over<cr>

The >> symbols doesn’t appear neither the top bar, even If I use :Winbar.
Anyone know what it could be? I have terminal activated

Hi GGCristo, does the breakpoint symbol appear after you enter CTRL-L or :redraw! to redraw the screen? If so, there is seemingly a problem preventing the screen from being updated when it should be.

Thanks for answer, I have never seen << neither lines highlight, neither the clickable bar.
At first I tought that that windows it was a regular code source, but I can press K above a variable and it tells me it value, so it doesn't seem to be the problem here.

Hi Daniel.
how to switch to the source code window from the gdb window after i enter some commonds in the gdb windows? I tried , but that didn’t work.

Hi wckang, there are various ways to switch to the source window.

  1. Use ctrl-w commands to navigate away from the gdb window.
    • ctrl-w h
    • ctrl-w j
    • ctrl-w k
    • ctrl-w l
  2. Navigate to the desired window with a mouse click.
  3. Within the gdb window, switch to Terminal-Normal mode with ctrl-w N or ctrl-\ ctrl-n, followed by :Source. Terminal-Job mode can be entered with i in the gdb window for entering additional gdb commands there.

Hi Daniel.
I have another question:how to remap the shortcuts in termdebug? In my neovim configure, the K has been mapped, so that will conflict the K in termdebug.

The K key in termdebug is equivalent to :Evaluate. The following example maps this to <leader>K (where <leader> would be a backslash, unless modified from its default).

noremap <silent> <leader>K :Evaluate<cr>

Hi Makmek. The gdb window is for interacting with gdb by entering debugging commands directly. For example, breakpoints can be entered here with b, and stepping commands can be entered here too (e.g., step and next). The program window is for I/O from the running program.

I do not know how to remove these windows. I haven’t tested, but an alternative could be to change window sizes (e.g., ctrl-w _ and ctrl-w |) and/or move windows to separate tabs (e.g., ctrl-w T).

Thanks for the response.

I was not referring to the gdb pane. I was referring to the one below the gdb pane in your picture (Sorry I got the status bars confused in the previous post). That pane is an extra annoyance when switching panes (between vim and gdb).

I am currently using ctrl-w _ and it is a good workaround to get more space for the gdb pane.

I found a way to do this. You have to ‘set hidden’ first. Then you can use C-w c to close the pane while keeping the buffer in the background. When that pane is closed you can do C-w w to switch back and forth between gdb and vim easily.

The program window is for I/O from the running program, for displaying the stdout and stderr streams, and taking input through the stdin stream.

I guess this window is not useful if e.g., your program takes input from command line arguments and/or files, and writes output to files without using stdout nor stderr.

Another option, as an alternative to resizing the program window with ctrl-w _, is to navigate to the program window and close it with ctrl-w c. The following custom mapping—or some variation—for your .vimrc may be helpful. This maps ctrl-\ c to a key sequence that closes the program window. The mapping assumes it’s run from the gdb window in terminal-job mode.

" Add mapping to move to the program window and close it.
tnoremap <c-\>c <c-\><c-n>:Program<cr><c-w>c:Gdb<cr>i

Hi Daniel,

I case you (or someone else reading this post) like to use :set nu and :set rnu in Vim but you would like to :set nornu when you’re in the !gdb terminal window, so that it’s easier to use the break command, for instance, please check out the autocommand I’ve defined here.

Cheers,
Enrico Maria

Hi Enrico, I had been formatting comments using HTML tags. I see that you’ve used markdown, so I’ve turned on the markdown in comments functionality, and your comment now appears properly formatted.

I’ve found this place to be the best summary (still :help terminal-debug is a quite good read and not too long).

There is one thing that puzzle me:
How can I pass arguments to GDB upon start? Previously I had written every necessary GDB commands into a .gdb file and then start gdb as gdb -q -x my.cmds.gdb myexecutable and I have not yet found a way to pass those gdb commands into the GDB session under VIm’s control. Starting a vim’d gdb similar to above can be done via /opt/vim/bin/vim -c "packadd termdebug" -c "let g:termdebug_wide=1" -c "Termdebug myexecutable" -c "resize +10" so the only missing part is to send gdb the source my.cmds.gdb. Any idea how to do this?

Hi Simon,

I don’t know if there is a built-in way to do that.

As an alternative approach, you can create a wrapper around gdb, which uses your custom arguments, and then set g:termdebugger to use that wrapper.

For example, a gdb.sh wrapper could be the following.

#!/usr/bin/env sh
gdb -q -x my.cmds.gdb "$@"

After 1) setting that script’s executable bit (e.g., chmod u+x gdb.sh), 2) adding it to some directory on the PATH, and 3) running let g:termdebugger='gdb.sh' in Vim, then :Termdebug would utilize your custom arguments.

Alternatively, if you don’t add gdb.sh to a directory on your PATH (or update PATH to include ghd.sh’s directory), then g:termdebugger can be set using the full path to the wrapper (e.g., let g:termdebugger='/some/path/to/gdb.sh').

After the wrapper script is created, setting g:termdebugger accordingly could be incorporated into the call to Vim that you posted above.

Thank you for the tip. I’ve later read about the variable in the help and for a short moment thought this may be a possible hack but then thought there should be a way to put anything from vi into gdb.
Maybe switch to its window/buffer and place the command + CR into it (I have no idea if something like this works with vim)?
After the post I’ve also checked the termdebug source a bit and the MI protocol an it looks like it may be able to Send gdb the original command via VIM->MI or write to the hidden buffer “gdb communication”. But also in these two cases I have no clue how to do that :-/

There’s a TermDebugSendCommand function for sending commands to gdb.

That could be called from your launcher command, combined with your my.cmds.gdb commands file, hopefully achieving the desired outcome.

vim ... -c Termdebug -c "call TermDebugSendCommand('source my.cmds.gdb')" ...

That looks cool. I wasn’t aware of the :call option, works fine in general and is also quite handy for additional key mappings as mentioned in https://vimhelp.org/terminal.txt.html.

Only things to take care: all of those additional commands need to be placed after the initial -c TermDebug and the command file should not include an attach (as that would not use the stdin/out buffer of vim), possibly also not a file (haven’t checked that).

Starting a new VIM-gdb’d debug with manual commands work perfectly now and I guess I’ll also find out how to attach that way…
:Termdebug myexecutable 1234 leads to a strange screen state where I need to [ctrl]+[c], then answer gdb to “continue” an “no”, don’t kill the process, even when called from within VIm, the behavior with -c 'Termdebug' is identical. But altogether I’ve learned a good amount of VIm and Termdebug stuff today – thanks!

If 1234 is intended as an argument for myexecutable, the :Termdebug command won’t work as shown in your example, as it doesn’t permit arguments to be passed for the program being debugged. The second argument to Termdebug is interpreted as a process ID (for attaching to an already running executable) or a core file.

To pass arguments to the program, there is :TermdebugCommand.

:TermdebugCommand myexecutable 1234

As my post was about attach 1234 was intended to be a PID. The attach actual “works somehow”:

Termdebug is started
gdb is started with the correct file myexecutable and seems to attach to 1234

But: then I need to [CTRL]+[C] first, then gdb-“c”ontinue the pagination, then tell gdb to “n”ot start a new process and to not kill the process and then get out of the debugging.

GDB and Vim are both on latest release. A plain attach with plain GDB works, running a new program in Termdebug also works fine, but the combination does not.

A possible workaround could be to attach using a gdb command directly (through the plugin), instead of using an argument to Termdebug.

One approach to do that could be to run :Termdebug myexecutable and then attach to a process by entering attach 1234 in the gdb window.

Or alternatively, the same idea could be incorporated into your launcher.

vim ... -c 'Termdebug myexecutable' -c "call TermDebugSendCommand('attach 1234')" ...

Leave a Reply to Enrico Maria De Angelis Cancel reply

Your email address will not be published. Required fields are marked *