1---
  2title: Integrated GDB Debugging in Vim with TermDebug
  3url: integrated-gdb-debugging-in-vim-with-termdebug.html
  4date: 2026-01-09T16:13:13+02:00
  5type: post
  6draft: false
  7tags: []
  8---
  9
 10## Motivation
 11
 12Over time I have tried making my `~/.vimrc` more universal and capable of
 13serving all the different projects, and I always fail. Configuration just
 14balloons, and sometimes I need different tools for different projects, that
 15fight against each other.
 16
 17Based on that, I realized that per project `.vimrc` would be much better, and
 18would better suit my development style. This way I can keep my main `~/.vimrc`
 19short and to the point and have project specific configuration separate.
 20
 21> **Important**: GDB is amazing but sometimes you do require something with
 22> better UI and better ergonomics. If you need a graphical debugger that uses
 23> GDB as a backend give https://github.com/nakst/gf a try. If you use gf2 than
 24> this post does not apply.
 25
 26## Main goals and requirements
 27
 28- Easy to launch `make` and/or run the application I'm working on.
 29- If the compilation fails, I want `Quickfix List` to be populated with errors.
 30- Start a debugger and break on `main`.
 31- Start a debugger and break on `currently highlighted line` in Vim.
 32
 33## A quick demonstration
 34
 35<video width="100%" controls>
 36  <source src="/assets/posts/vim-gdb/demo.mp4" type="video/mp4">
 37</video>
 38
 39## Main `~/.vimrc`
 40
 41My `~/.vimrc` is very minimal. I only use these four plugins:
 42
 43- https://github.com/tpope/vim-commentary - comment stuff out
 44- https://github.com/ctrlpvim/ctrlp.vim - fuzzy file, buffer finder
 45- https://github.com/dense-analysis/ale - LSP integration
 46- https://github.com/mitjafelicijan/sniper.vim - buffer bookmark manager
 47
 48At the top of my configuration file I have the following.
 49
 50```vimrc
 51set nocompatible exrc secure
 52```
 53
 54- `exrc` - Allows Vim to read local configuration files.
 55- `secure` - Restricts what local vimrc/exrc files are allowed to do.
 56
 57`exrc` is the important one. This allows us to have `.vimrc` in the directory
 58of our project. And this will then only apply to that specific project while
 59not polluting the main `~/.vimrc` file.
 60
 61## Project `~/project/foo/.vimrc`
 62
 63The project for testing showcasing this will be a simple C project using [GNU
 64Make](https://www.gnu.org/software/make/) and [Clang](https://clang.llvm.org/).
 65
 66Project structure
 67
 68```
 69~/Projects/cproject
 70    main.c
 71    Makefile
 72    .vimrc
 73```
 74
 75```Makefile
 76# Makefile
 77cprogram: main.c
 78    clang -g -o cprogram main.c
 79```
 80
 81```c
 82// main.c
 83
 84#include <stdio.h>
 85#include <stdlib.h>
 86
 87typedef struct {
 88	int q;
 89	int w;
 90} Bar;
 91
 92int main(void) {
 93	const char *myenv = getenv("MYENV");
 94
 95	int a = 100;
 96	int b = 123;
 97	int c = a + b;
 98
 99	Bar bar = { .q = 565, .w = 949 };
100
101	printf("> MYENV: %s\n", myenv);
102	printf("> c: %d\n", c);
103	printf("> bar.q: %d\n", bar.q);
104
105	for (int i=0; i<10; i++) {
106		printf("> loop %d\n", i);
107	}
108
109	return 0;
110}
111```
112
113```vim
114" .vimrc
115let g:_executable = 'cprogram'
116let g:_arguments = ''
117let g:_envs = { 'MYENV': 'howdy' }
118let g:_make = 'make -B'
119
120set makeprg=make
121set errorformat=%f:%l:%c:\ %m
122packadd termdebug
123
124let g:termdebug_config = {}
125let g:termdebug_config['variables_window'] = v:true
126
127nnoremap <leader>x :call LocalRun()<CR>
128nnoremap <leader>c :call LocalMake()<CR>
129nnoremap <leader>m :call LocalDebugMain()<CR>
130nnoremap <leader>l :call LocalDebugLine()<CR>
131
132function! LocalRun() abort
133	let envs = join( map(items(g:_envs), { _, kv -> kv[0] . '=' . kv[1] }), ' ')
134	execute printf("term env %s ./%s %s", envs, g:_executable, g:_arguments)
135endfunction
136
137function! LocalDebugMain() abort
138	execute printf('Termdebug %s %s', g:_executable, g:_arguments)
139
140	for [k, v] in items(g:_envs)
141		call TermDebugSendCommand(printf('set env %s %s', k, v))
142	endfor
143
144	call TermDebugSendCommand('break main')
145	call TermDebugSendCommand('run')
146endfunction
147
148function! LocalDebugLine() abort
149        let cmd = printf("break %s:%d", expand('%'), line('.'))
150        execute printf('Termdebug %s %s', g:_executable, g:_arguments)
151
152        for [k, v] in items(g:_envs)
153                call TermDebugSendCommand(printf('set env %s %s', k, v))
154        endfor
155
156        call TermDebugSendCommand(cmd)
157        call TermDebugSendCommand('run')
158endfunction
159
160function! LocalMake() abort
161	for [k, v] in items(g:_envs)
162		execute printf("let $%s = %s", k, string(v))
163	endfor
164
165	silent make
166
167	" Filter non valid errors out of quicklist.
168	let qfl = getqflist()
169	let filtered = filter(copy(qfl), {_, entry -> entry.valid == 1})
170	call setqflist(filtered, 'r')
171
172	redraw!
173	execute len(filtered) > 0 ? 'copen' : 'cclose'
174endfunction
175```
176
177> **Note**: If any errors are found during the compilation mode the Quickfix
178> list will be populated and opened. You could use CtrlP Quickfix with
179> `:CtrlPQuickfix` instead of `:copen` for this as well, I just want to stick
180> to what Vim supports natively.
181
182Lets check these keybindings.
183
184- `Leader+x` - runs the binary in the terminal above and providing environment variables and argument
185- `Leader+c` - runs make and puts error in Quickfix List then opens with `:copen`
186- `Leader+m` - launches DebugTerm/DBG debugger with all variables and breaks on main
187- `Leader+l` - launches DebugTerm/DBG debugger with all variables and breaks on current line
188
189This setup can get even more elaborate. Depending on your needs. This example
190works really well for projects using C, but anything goes here.
191
192## Why use `:term` and `:TermDebug`
193
194It's very easy to yank and paste from internal terminal buffers even if you are
195not using `tmux` as multiplexer. Just makes the whole thing much easier. I do
196however use `tmux` as well but for compile/debug loop this proved to be a much
197better experience.
198
199`:TermDebug` is also a no brainier. The integration of GDB directly in Vim
200makes adding new breakpoints with `:Break` just seamless. This goes for all
201other commands as well. You can read more about other commands with `:h
202Termdebug` or on https://vimhelp.org/terminal.txt.html.
203
204## Isn't this setup a bit elaborate?
205
206Yes and no. I don't change these files much at all. When I start a new C
207project I just copy an existing one from a different project to my new one and
208call it a day.  And any significantly complex project will require certain per
209project specific configurations anyway.
210
211This also makes it shareable with other people without dumping your whole setup
212on them. There are drawback like with anything else, but I have found this
213approach to be the lesser of two evils kinda think. Even Vim users can ignore
214these files by not enabling local configuration files.
215
216Like any other tool, it's on you to make your development workflow as
217frictionless as possible and no tool is perfect.