|
diff --git a/content/posts/2026-01-09-vim.md b/content/posts/2026-01-09-vim.md
|
|
|
1 |
--- |
|
|
2 |
title: Integrated GDB Debugging in Vim with TermDebug |
|
|
3 |
url: integrated-gdb-debugging-in-vim-with-termdebug.html |
|
|
4 |
date: 2026-01-09T16:13:13+02:00 |
|
|
5 |
type: post |
|
|
6 |
draft: false |
|
|
7 |
tags: [] |
|
|
8 |
--- |
|
|
9 |
|
|
|
10 |
## Motivation |
|
|
11 |
|
|
|
12 |
Over time I have tried making my `~/.vimrc` more universal and capable of |
|
|
13 |
serving all the different projects, and I always fail. Configuration just |
|
|
14 |
balloons, and sometimes I need different tools for different projects, that |
|
|
15 |
fight against each other. |
|
|
16 |
|
|
|
17 |
Based on that, I realized that per project `.vimrc` would be much better, and |
|
|
18 |
would better suit my development style. This way I can keep my main `~/.vimrc` |
|
|
19 |
short 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 |
|
|
|
41 |
My `~/.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 |
|
|
|
48 |
At the top of my configuration file I have the following. |
|
|
49 |
|
|
|
50 |
```vimrc |
|
|
51 |
set 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 |
|
|
58 |
of our project. And this will then only apply to that specific project while |
|
|
59 |
not polluting the main `~/.vimrc` file. |
|
|
60 |
|
|
|
61 |
## Project `~/project/foo/.vimrc` |
|
|
62 |
|
|
|
63 |
The project for testing showcasing this will be a simple C project using [GNU |
|
|
64 |
Make](https://www.gnu.org/software/make/) and [Clang](https://clang.llvm.org/). |
|
|
65 |
|
|
|
66 |
Project structure |
|
|
67 |
|
|
|
68 |
``` |
|
|
69 |
~/Projects/cproject |
|
|
70 |
main.c |
|
|
71 |
Makefile |
|
|
72 |
.vimrc |
|
|
73 |
``` |
|
|
74 |
|
|
|
75 |
```Makefile |
|
|
76 |
# Makefile |
|
|
77 |
cprogram: 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 |
|
|
|
87 |
typedef struct { |
|
|
88 |
int q; |
|
|
89 |
int w; |
|
|
90 |
} Bar; |
|
|
91 |
|
|
|
92 |
int 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 |
|
|
115 |
let g:_executable = 'cprogram' |
|
|
116 |
let g:_arguments = '' |
|
|
117 |
let g:_envs = { 'MYENV': 'howdy' } |
|
|
118 |
let g:_make = 'make -B' |
|
|
119 |
|
|
|
120 |
set makeprg=make |
|
|
121 |
set errorformat=%f:%l:%c:\ %m |
|
|
122 |
packadd termdebug |
|
|
123 |
|
|
|
124 |
let g:termdebug_config = {} |
|
|
125 |
let g:termdebug_config['variables_window'] = v:true |
|
|
126 |
|
|
|
127 |
nnoremap <leader>x :call LocalRun()<CR> |
|
|
128 |
nnoremap <leader>c :call LocalMake()<CR> |
|
|
129 |
nnoremap <leader>v :call LocalDebugMain()<CR> |
|
|
130 |
nnoremap <leader>b :call LocalDebugLine()<CR> |
|
|
131 |
|
|
|
132 |
function! 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) |
|
|
135 |
endfunction |
|
|
136 |
|
|
|
137 |
function! 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('directory ' . getcwd()) |
|
|
145 |
call TermDebugSendCommand('break main') |
|
|
146 |
call TermDebugSendCommand('run') |
|
|
147 |
endfunction |
|
|
148 |
|
|
|
149 |
function! LocalDebugLine() abort |
|
|
150 |
let cmd = printf("break %s:%d", expand('%'), line('.')) |
|
|
151 |
execute printf('Termdebug %s %s', g:_executable, g:_arguments) |
|
|
152 |
|
|
|
153 |
for [k, v] in items(g:_envs) |
|
|
154 |
call TermDebugSendCommand(printf('set env %s %s', k, v)) |
|
|
155 |
endfor |
|
|
156 |
|
|
|
157 |
call TermDebugSendCommand(cmd) |
|
|
158 |
call TermDebugSendCommand('run') |
|
|
159 |
endfunction |
|
|
160 |
|
|
|
161 |
function! LocalMake() abort |
|
|
162 |
let envs = join( map(items(g:_envs), { _, kv -> kv[0] . '=' . kv[1] }), ' ') |
|
|
163 |
execute printf('silent !env %s %s', g:_make, envs) |
|
|
164 |
|
|
|
165 |
" Filter non valid errors out of quicklist. |
|
|
166 |
let qfl = getqflist() |
|
|
167 |
let filtered = filter(copy(qfl), {_, entry -> entry.valid == 1}) |
|
|
168 |
call setqflist(filtered, 'r') |
|
|
169 |
|
|
|
170 |
redraw! |
|
|
171 |
|
|
|
172 |
if len(filtered) > 0 |
|
|
173 |
execute exists(':CtrlPQuickfix') ? 'CtrlPQuickfix' : 'copen' |
|
|
174 |
else |
|
|
175 |
cclose |
|
|
176 |
endif |
|
|
177 |
endfunction |
|
|
178 |
``` |
|
|
179 |
|
|
|
180 |
I am using the CtrlP plugin, so I also use it for displaying the Quickfix List. |
|
|
181 |
But if the plugin is not found; it will default to native quicklist with |
|
|
182 |
`:copen`. |
|
|
183 |
|
|
|
184 |
Lets check these keybindings. |
|
|
185 |
|
|
|
186 |
- `Leader+x` - runs the binary in the terminal above and providing environment variables and argument |
|
|
187 |
- `Leader+c` - runs make and puts error in Quickfix List then opens with `:copen` |
|
|
188 |
- `Leader+v` - launches DebugTerm/DBG debugger with all variables and breaks on main |
|
|
189 |
- `Leader+b` - launches DebugTerm/DBG debugger with all variables and breaks on current line |
|
|
190 |
|
|
|
191 |
This setup can get even more elaborate. Depending on your needs. This example |
|
|
192 |
works really well for projects using C, but anything goes here. |
|
|
193 |
|
|
|
194 |
## Why use `:term` and `:TermDebug` |
|
|
195 |
|
|
|
196 |
It's very easy to yank and paste from internal terminal buffers even if you are |
|
|
197 |
not using `tmux` as multiplexer. Just makes the whole thing much easier. I don |
|
|
198 |
however use `tmux` as well but for compile/debug loop this proved to be a much |
|
|
199 |
better experience. |
|
|
200 |
|
|
|
201 |
`:TermDebug` is also a no brainier. The integration of GDB directly in Vim |
|
|
202 |
makes adding new breakpoints with `:Break` just seamless. This goes for all |
|
|
203 |
other commands as well. You can read more about other commands with `:h |
|
|
204 |
Termdebug` or on https://vimhelp.org/terminal.txt.html. |
|
diff --git a/templates/base.html b/templates/base.html
|
| ... |
| 32 |
header nav span.title { font-weight: bold; } |
32 |
header nav span.title { font-weight: bold; } |
| 33 |
|
33 |
|
| 34 |
section { margin-block-start: 3em; margin-block-end: 3em; } |
34 |
section { margin-block-start: 3em; margin-block-end: 3em; } |
| 35 |
|
35 |
blockquote { border-left: 0.2em solid black; padding-left: 1em; margin-left: 0; } |
| 36 |
blockquote { |
|
|
| 37 |
border-left: 0.3em solid black; |
|
|
| 38 |
padding-left: 1em; |
|
|
| 39 |
margin-left: 0; |
|
|
| 40 |
} |
|
|
| 41 |
|
|
|
| 42 |
footer { font-size: small; } |
36 |
footer { font-size: small; } |
| 43 |
|
37 |
|
| 44 |
ul.post-list { padding: 0em; } |
38 |
ul.post-list { padding: 0em; } |
| ... |
| 53 |
article h1 { font-size: 130%; line-height: 110%; } |
47 |
article h1 { font-size: 130%; line-height: 110%; } |
| 54 |
article code { background: lemonchiffon; padding: 0 0.2em; } |
48 |
article code { background: lemonchiffon; padding: 0 0.2em; } |
| 55 |
article pre { border: 1px solid var(--border-color); padding: 1em; line-height: 140%; text-wrap: nowrap; overflow-x: auto; } |
49 |
article pre { border: 1px solid var(--border-color); padding: 1em; line-height: 140%; text-wrap: nowrap; overflow-x: auto; } |
| 56 |
article pre > code { background: initial; } |
50 |
article pre > code { background: initial; padding: 0; } |
| 57 |
|
51 |
|
| 58 |
img, video, audio { max-width: 100%; } |
52 |
img, video, audio { max-width: 100%; } |
| 59 |
figure { display: flex; justify-content: center; margin: 1.5em 0; } |
53 |
figure { display: flex; justify-content: center; margin: 1.5em 0; } |
| ... |