Neovimでリモートデバッグの設定をする

この記事について

Neovimではnvim-dap,nvim-dap-ui を使用することで、デバッグを行うことができます。 しかし、Neovimからデバッグを開始する方法では引数を与える場合、通常ターミナルで実行する場合とは異なる慣れない入力をする必要があるため なかなか面倒です

デバッグの手法の中には、デバッガ側でプログラムを起動した状態にしておき、そこにエディタ側からアタッチする方法があります。 この方法ではデバッガとエディタの間の通信がTCP/IPで行なわれ、別のマシンで実行しているプログラムをデバッグすることができるので、リモートデバッグと呼ばれています。

Python向けには、1年ほど前にリモートデバッグが実行できるように設定していました。 しかし、C/C++,Rustの設定は長いこと設定していなかったので、ようやく重い腰を上げて設定をしました。

今回はあくまでも、同一ホストで動かしているプログラムのデバッグに限定しますが、接続先のホスト名(もしくはIPアドレス)を変更すれば本当に"リモート"なデバッグにも応用できます。

前提

今回の記事ではNeovimでのデバッグ実行の設定ですが、以下のことを前提にしています。

デバッガ側

Pythonの場合

Pythonのデバッグにはdebugpyを使用し、インストールはプロジェクトごとに以下のように実行します。

1
2
3
uv init
uv add debugpy --dev # dev-dependencyとしてインストール
source .venv/bin/activate # venvの有効化

ここで、uvによるインストールですが、uv add <pkg name>でインストールする際には--devを付けることでdev-dependencyとしてインストールすることができます。 この場合、uv buildでwheelをビルドする場合などで、dev-dependencyとしてインストールされたパッケージが除外されます。

C/C++/Rustの場合

gdbをインストールして、gdbserverが実行できるようになっていればOKです。

Neovim側

Python by debugpy

nvim-dapの設定は以下のように行いました。 内容としては、IPアドレスlocalhost(127.0.0.1),ポート5678で立てたサーバー(debugpy)に対してクライアント(エディタ)をアタッチするようになっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
dap.adapters = {
  debugpy_tcp = {
    type = 'server',
    port = assert('5678', '`connect.port` is required for a python `attach` configuration'),
    host = '127.0.0.1',
    options = {
      source_filetype = 'python',
    },
  },
}

dap.configurations = {
  python = {
    {
      name = 'attach to debugpy',
      type = 'debugpy_tcp',
      request = 'attach',
      program = '${file}',
      pythonPath = function()
        local cwd = vim.fn.getcwd()
        if vim.fn.executable(cwd .. '/venv/bin/python') == 1 then
          return cwd .. '/venv/bin/python'
        elseif vim.fn.executable(cwd .. '/.venv/bin/python') == 1 then
          return cwd .. '/.venv/bin/python'
        else
          return '/usr/bin/python'
        end
      end,
    },
  }
}

C/C++/Rust by gdb

以下を参考に設定しました。Pythonの場合と意味としては同じで、IPアドレスlocalhost(127.0.0.1),ポート5678で立てたサーバー(gdbserver)に対してクライアント(エディタ)をアタッチするようになっています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
local dap = require('dap')

dap.adapters = {
  gdb = {
    type = 'executable',
    command = 'gdb',
    args = { '--interpreter=dap', '--eval-command', 'set print pretty on' },
  },
  --[[
  other settings
  --]]
}

dap.configurations = {
  cpp = {
  --[[
  other settings
  --]]
    {
      name = 'attach to gdbserver',
      type = 'gdb',
      request = 'attach',
      target = string.format('localhost:%d', port_default),
      cwd = '${workspaceFolder}',
    },
  },
  rust = {
  --[[
  other settings
  --]]
    {
      name = 'attach to gdbserver',
      type = 'gdb',
      request = 'attach',
      target = string.format('localhost:%d', port_default),
      cwd = '${workspaceFolder}',
    },
  }

デバッグの実行

Pythonの場合は、以下のコマンドでデバッガのサーバーを起動します。

1
python -m debugpy --wait-for-client --listen 5678 <program> <args> 

一方、C/C++,Rustの場合では以下のようにデバッガのサーバーを起動します。

1
gdbserver :5678 ./<program> <args> 

サーバー側が起動した後では以下の手順でデバッグを開始します。

  1. require('dap').continue()を実行してポップアップからattach to gdbserver(C/C++,Rustの場合)もしくは,attach to debugpy(Pythonの場合)となるアダプターを選択する。
  2. require('dap').toggle_breakpoint()で適当な場所にブレークポイントを設定する。
  3. require('dap').continue()でデバッグ開始する。

筆者はこれらの操作を以下のようなキーに割り当てています。

key function 操作
:B require(‘dap’).toggle_breakpoint() 現在のカーソル位置でのブレークポイントのトグル
:Bc require(‘dap’).clear_breakpoints() 設定したブレークポイントをすべてクリア
:C require(‘dap’).continue() 継続
:Cr require(‘dap’).restart() セッションの再起動
:Ct require(‘dap’).terminate() セッションの終了
Alt + n require(‘dap’).step_over() 次の行の実行で、関数呼び出しは実行するが、中には入らない
Alt + s require(‘dap’).step_into() 次の行の実行で、関数呼び出しがあったら、その中に入る
Alt + f require(‘dap’).step_out() 現在いる関数を最後まで実行して、呼び出し元に戻る

コンテナの名のプラグラムをデバッグする場合

また、コンテナの中のプログラムをデバッグするには、コンテナの起動時にポートフォワーディング(-p <port>:<port>)して、コンテナ内でのサーバーの起動時のIPアドレスとポートを"0.0.0.0:<port>“に設定するとそのまま使用できます。

感想

C/C++,Rustのgdbによるリモートデバッグの設定は結構ややこしいように感じていましたが、思ったよりも簡単に設定できました。 これからのデバッグがより快適になりそうです。

CC BY
Hugo で構築されています。
テーマ StackJimmy によって設計されています。