SysTutorials

Debugging C and C++ with cgdb and Vim Keybindings

· updated April 11, 2026

cgdb is a curses-based frontend to gdb that keeps your source code visible while you debug. Unlike raw gdb, it splits the interface into two panes: source code on top, gdb command prompt below. If you’re already comfortable with Vim keybindings, cgdb feels natural—it uses the same navigation keys and modal editing paradigms you’re used to.

Why cgdb over gdb alone

Raw gdb hides your code context. You type commands and watch output scroll past, losing track of where you are in the source. cgdb solves this by maintaining a visual split: navigate the code pane with Vim keys (hjkl, /, G, gg), set breakpoints by pressing spacebar, and issue debug commands without context switching.

Function keys handle common operations—F5 to run, F8 to step over, F10 to step into. When you need arbitrary gdb commands, press i to drop into GDB mode, type what you need, then Ctrl+[ to return to code viewing. This is faster than reaching for Escape on a split keyboard.

Essential cgdb shortcuts

Shortcut Action
F5 Run program
F6 Continue execution
F7 Finish current function (step out)
F8 Next (step over)
F10 Step (step into)
Spacebar Toggle breakpoint at current line
t Set temporary breakpoint at current line
d Delete breakpoint at current line
i Enter GDB command mode
I Enter TTY mode (interact with program stdin/stdout)
Ctrl+[ Return to code viewer mode
/ Search in current file
G Jump to end of file
gg Jump to start of file

The I key is particularly useful for programs that read input. It allows you to interact with the running program’s stdin while maintaining the debugger state.

Building with debug symbols

Always compile with -g. For macro expansion and better stepping accuracy, use -g3. Avoid high optimization levels during development:

gcc -g3 -Og -Wall -Wextra -o my_program main.c

-Og optimizes for debugging—it enables some optimizations without breaking stepping or variable inspection. If you use -O2 or -O3, stepping becomes unintuitive and local variables disappear.

For C++, add the same flags:

g++ -g3 -Og -Wall -Wextra -std=c++20 -o my_program main.cpp

Configuration

Create or edit ~/.cgdbrc:

set winsplit=top_big
set syntax=on
set showlinenum=on
set autosourcereload=on
set srcpath=/path/to/source

If your binary was built in one directory but sources live elsewhere, add multiple srcpath entries:

set srcpath=/home/user/project/src
set srcpath=/home/user/project/include

Custom keybindings

Map frequently used gdb operations to fewer keystrokes:

bind c F6
bind s F8
bind n F10
bind f F7
bind b spacebar

Now c continues, s steps over, n steps into, f finishes the function, and b toggles breakpoints. Avoid remapping navigation keys (hjkl, /, G, gg) as they conflict with Vim-style movement.

Workflow example

Compile with debug symbols:

gcc -g3 -Og -Wall -o my_program main.c

Start cgdb:

cgdb ./my_program arg1 arg2

Navigate to the line where you want to break and press spacebar:

spacebar

Run with F5:

F5

The program executes and stops at your breakpoint. Step through with F8 (next) or F10 (step into). To enter a function, press F10. To skip over it, press F8. To exit the current function, press F7.

For variable inspection, press i to enter GDB mode:

print my_variable
p *(my_ptr)
p my_array@10
bt
info registers

Print variables, dereference pointers, inspect array elements, print backtraces, and examine registers. Press Ctrl+[ to return to the code viewer.

Debugging multi-threaded programs

For programs with multiple threads, use GDB mode to inspect them:

info threads
thread 2
bt

Switch between threads and print their backtraces. Set thread-specific breakpoints:

break main.c:42 thread 2

This breakpoint only triggers when thread 2 reaches line 42. cgdb displays the thread ID when a breakpoint is hit.

For race conditions, you can temporarily disable other threads:

thread apply all step

Step all threads together, or:

thread apply 1 continue

Continue only thread 1 while others remain suspended.

Debugging optimized binaries

High optimization levels (-O2, -O3) break cgdb’s ability to map instructions back to source. Local variables vanish, stepping jumps unexpectedly, and the source view becomes misleading.

If you must debug optimized code, use print-based debugging or conditional breakpoints in GDB mode:

break main.c:42 if condition
commands
print my_var
continue
end

This breakpoint prints a variable every time it’s hit before automatically continuing—useful for tracing program state in optimized builds.

Installation

On most Linux distributions:

# Debian/Ubuntu
apt install cgdb

# Fedora/RHEL
dnf install cgdb

# Arch
pacman -S cgdb

# macOS
brew install cgdb

cgdb requires readline and ncurses development libraries. On older systems, you may need to build from source:

git clone https://github.com/cgdb/cgdb.git
cd cgdb
./autogen.sh
./configure
make
sudo make install

Alternatives and modern approaches

For pure source-level debugging without launching a separate tool, Neovim’s DAP integration (Debug Adapter Protocol) has matured significantly. Plugins like nvim-dap and nvim-dap-ui let you debug within your editor:

If you’re already in Neovim and want an integrated experience, DAP is worth exploring. For quick debugging sessions from the terminal or when jumping between projects, cgdb’s lightweight startup and Vim-like navigation remain faster than configuring editor plugins.

Common gotchas

Breakpoints not hit: Ensure the binary has debug symbols (readelf -S my_program | grep debug should show .debug_* sections). Recompile with -g.

Variables unavailable: You’re likely debugging optimized code. Recompile with -Og instead of -O2 or -O3.

Source not found: Use set srcpath in ~/.cgdbrc to tell cgdb where your sources live, or use directory /path/to/source in GDB mode.

Program doesn’t stop at breakpoint: Check that the breakpoint was set (highlighted in red in the source pane). Use info breakpoints in GDB mode to verify.

← All posts