Skip to content

C C Rust (via codelldb)

Mathias Fußenegger edited this page Apr 17, 2022 · 19 revisions

C/C++/Rust (via codelldb)

Configuration examples are in Lua. See :help lua-commands if your Neovim setup so far uses a init.vim file.

Installation

Install codelldb:

  • Download the VS Code extension.
  • Unpack it. .vsix is a zip file and you can use unzip to extract the contents.

Adapter definition

codelldb uses TCP for the DAP communication - that requires using the server type for the adapter definition. See :help dap-adapter.

Using server means that by default nvim-dap won't launch codelldb automatically when you start a new debug session. Instead you have to start codelldb manually in a terminal whenever you want to start a debug session or add some extra logic to automate it. (Read on to see an example on how to automate it)

Start codelldb manually in a terminal.

Up to including version 1.6.10 you could launch codelldb and it printed out a port it was listening to:

$ codelldb
Listening on port 13123

Starting with version 1.7.0 it is necessary to specify the port:

$ codelldb --port 13000

To have nvim-dap connect to it, you can define an adapter like this:

local dap = require('dap')
dap.adapters.codelldb = {
  type = 'server'
  host = '127.0.0.1',
  port = 13000 -- 💀 Use the port printed out or specified with `--port`
}

Note that any time you want to debug an application you'll have to launch the codelldb process in a terminal first.

Autolaunch codelldb

nvim-dap supports defining adapters as a function which it executes whenever the user starts a new debug session. This allows to start codelldb ad-hoc within the adapter definition

For version 1.6.10 and earlier
local dap = require('dap')
dap.adapters.codelldb = function(on_adapter)
  local stdout = vim.loop.new_pipe(false)
  local stderr = vim.loop.new_pipe(false)

  -- CHANGE THIS!
  local cmd = '/absolute/path/to/codelldb/extension/adapter/codelldb'

  local handle, pid_or_err
  local opts = {
    stdio = {nil, stdout, stderr},
    detached = true,
  }
  handle, pid_or_err = vim.loop.spawn(cmd, opts, function(code)
    stdout:close()
    stderr:close()
    handle:close()
    if code ~= 0 then
      print("codelldb exited with code", code)
    end
  end)
  assert(handle, "Error running codelldb: " .. tostring(pid_or_err))
  stdout:read_start(function(err, chunk)
    assert(not err, err)
    if chunk then
      local port = chunk:match('Listening on port (%d+)')
      if port then
        vim.schedule(function()
          on_adapter({
            type = 'server',
            host = '127.0.0.1',
            port = port
          })
        end)
      else
        vim.schedule(function()
          require("dap.repl").append(chunk)
        end)
      end
    end
  end)
  stderr:read_start(function(err, chunk)
    assert(not err, err)
    if chunk then
      vim.schedule(function()
        require("dap.repl").append(chunk)
      end)
    end
  end)
end
For version 1.7.0 and later
-- 💀 Adjust the path to your executable
local cmd = '/absolute/path/to/codelldb/extension/adapter/codelldb'

local dap = require('dap')

dap.adapters.codelldb = function(on_adapter)
  -- This asks the system for a free port
  local tcp = vim.loop.new_tcp()
  tcp:bind('127.0.0.1', 0)
  local port = tcp:getsockname().port
  tcp:shutdown()
  tcp:close()

  -- Start codelldb with the port
  local stdout = vim.loop.new_pipe(false)
  local stderr = vim.loop.new_pipe(false)
  local opts = {
    stdio = {nil, stdout, stderr},
    args = {'--port', tostring(port)},
  }
  local handle
  local pid_or_err
  handle, pid_or_err = vim.loop.spawn(cmd, opts, function(code)
    stdout:close()
    stderr:close()
    handle:close()
    if code ~= 0 then
      print("codelldb exited with code", code)
    end
  end)
  if not handle then
    vim.notify("Error running codelldb: " .. tostring(pid_or_err), vim.log.levels.ERROR)
    stdout:close()
    stderr:close()
    return
  end
  vim.notify('codelldb started. pid=' .. pid_or_err)
  stderr:read_start(function(err, chunk)
    assert(not err, err)
    if chunk then
      vim.schedule(function()
        require("dap.repl").append(chunk)
      end)
    end
  end)
  local adapter = {
    type = 'server',
    host = '127.0.0.1',
    port = port
  }
  -- 💀
  -- Wait for codelldb to get ready and start listening before telling nvim-dap to connect
  -- If you get connect errors, try to increase 500 to a higher value, or check the stderr (Open the REPL)
  vim.defer_fn(function() on_adapter(adapter) end, 500)
end

  • Rust-Tools only If you are using this adapter for debugging Rust and are using the rust-tools extension, there is a helper function defined for setting up CodeLLDB. This helper function works the same way as the function defined in the point above. Set it up as defined here.

Have a look at this issue for more information on CodeLLDB definition.

Configuration

The codelldb manual contains a full reference for all options supported by the debug adapter.

A common configuration example:

local dap = require('dap')
dap.configurations.cpp = {
  {
    name = "Launch file",
    type = "codelldb",
    request = "launch",
    program = function()
      return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file')
    end,
    cwd = '${workspaceFolder}',
    stopOnEntry = true,
  },
}

If you want to use this debug adapter for other languages, you can re-use the configurations:

dap.configurations.c = dap.configurations.cpp
dap.configurations.rust = dap.configurations.cpp

The executables that you want to debug need to be compiled with debug symbols.