My Neovim DAP Setup for C Debugging

Setting up the Debug Adapter Protocol (DAP) in Neovim transforms C and C++ workflows by integrating full-featured debugging—breakpoints, call stacks, REPL execution, and variable evaluation—directly into your modal editing experience.

Rather than maintaining static code snippets in this post, you can inspect my active setup directly on GitHub:

👉 VincentTam/nvim-config: lua/plugins/dap.lua


Core Architecture

The setup relies on three primary components managed through lazy.nvim:


Ergonomic Navigation: Shift + Arrow Shortcuts

While traditional DAP keymaps often use multi-key leader sequences (e.g., <Leader>dn), navigating execution during a active step-by-step session is much faster with single-key modifications.

Inspired by a tip in nvim-dap README’s, you can map stepping operations directly to directional Shift + Arrow combinations:

  • <S-Up>: Continue execution to next breakpoint (dap.continue())
  • <S-Down>: Step Over statement (dap.step_over())
  • <S-Right>: Step Into function (dap.step_into())
  • <S-Left>: Step Out / Up stack frame (dap.step_out())

These bindings allow rapid execution stepping without leaving home-row focus or typing multi-key combinations repeatedly while inspecting loop iterations.


Direct GDB Integration & DAP Quirks

Connecting GDB through native nvim-dap requires specific configurations that are not fully detailed in the default documentation:

1. Modern GDB DAP Flag Modern GDB (v14+) ships with native DAP support.

Instead of relying on intermediate wrapper scripts, configure the GDB adapter directly with the --interpreter=dap flag:

dap.adapters.gdb = {
  type = "executable",
  command = "gdb",
  args = { "--interpreter=dap", "--eval-command", "set print pretty on" }
}

Then configure the C/C++ file debugging.

local c_cpp_config = {
  {
    name = "Launch file",
    type = "gdb",
    request = "launch",
    program = function()
      return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file')
    end,
    cwd = '${workspaceFolder}',
    stopAtBeginningOfMainSubprogram = false,
    -- Add this line to map current workspace to root relative paths:
    sourceFileMap = {
      ["${workspaceFolder}"] = "${workspaceFolder}"
    },
  },
}
dap.configurations.c = c_cpp_config
dap.configurations.cpp = c_cpp_config

I used Gemini to generate this file under ~/.config/nvim. It skipped some lines in the official config wiki. It has suggested the sourceFileMap for suppressing some errors that appeared when I was using this plugin. However, after doing so, the same red cross was still there.

2. REPL Function Calls & Stdout Flushing

When evaluating expressions inside the [dap-repl] console, calling non-returning void C functions or functions containing printf statements requires explicit GDB execution commands:

  • Prefix Calls: Execute functions with explicit call syntax (e.g., call displayMaze(maze)) so DAP routes the request directly to GDB rather than treating it as a standard variable evaluation.
  • Stream Flushing: C output buffers often delay rendering to standard output. Issuing call fflush(0) directly inside the REPL forces pending stdout buffers to flush immediately into the debug output window.
NeoVim  DAP  GDB  C 

No comment

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