ruby/debugとエディタ

ユニットテストで問題を再現させてデバッグするといった使い方が多いので、サンプルコードをtest-unitを使ったものを用意した。

# Gemfile
source 'https://rubygems.org'
gem 'debug'
gem 'test-unit'
# アプリケーション
class Foo
  def foo
    :foo
  end
end
# ユニットテスト
require 'test/unit'
require_relative 'foo'

class FooTest < Test::Unit::TestCase
  test 'foo' do
    foo = Foo.new
    assert_equal :foo, foo.foo
  end
end

端末でデバッグする

まずは基本的な使い方ができることを確認しておく。

デバッガrdbgを介してデバッグ対象を実行する。すると、デバッグ対象の起動直後に動作が停止してデバッグセッションが始まる。

rdbgコマンドを直接使用する

rdbgコマンドを直接使用する

Visual Studio Codeを起動してデバッグする

次にrdbgコマンドを使用するが、デバッグセッションをVisual Studio Codeで開始させてみる。

VSCode rdbg Ruby Debuggerが必要だが、事前の設定は必要ないようだ。

$ PATH=$PATH:'/Applications/Visual Studio Code.app/Contents/Resources/app/bin' \
> bundle exec rdbg --open=vscode foo_test.rb
DEBUGGER: Debugger can attach via UNIX domain socket (/var/folders/25/5gm2mn2w8xlfbc008k8yhkkh0000gp/T/ruby-debug-sock-502/ruby-debug-akira-64773)
Launching: code /private/tmp/rdbg-sample/
Loaded suite foo_test
Started
DEBUGGER: wait for debugger connection...
rdbgコマンドからVisual Studio Codeを起動した

rdbgコマンドからVisual Studio Codeを起動した

Visual Studio Codeで「Shell Command: Install 'code' command in PATH」を実行しておくか、あらかじめPATHを通しておけば環境変数の指定は必要ない。

Visual Studio Codeで開いているファイルをデバッグする

Visual Studio Codeからrdbgコマンドを使用してデバッグセッションを開始する。事前の設定が必要となるが、VSCode rdbg Ruby Debuggerの説明に従えばよい。

設定がまだないときは「実行とデバッグ」で表示される「launch.jsonファイルを作成します」をクリックすると雛型が作成される。設定内容の選択肢が表示されたときは「rdbg」とあるものを選ぶ。

「Debug current file with rdbg」を選択し、「⏵」をクリックすると実行するコマンドの入力を求められる。ただし、この場合にはデバッグ対象を起動直後に停止させることはない(※1)ようなので、事前にブレークポイントを設定しておく。(またはコード中にbinding.bなどでブレークポイントを記述しておく。)

ブレークポイントの設定

ブレークポイントの設定

そして「⏵」をクリック。

コマンドラインの入力

コマンドラインの入力

デバッグセッション開始

デバッグセッション開始

リモートデバッグ

rdbgはDAPというプロトコルをサポートしていて「開発ツール ⇄ DAPアダプタ・デバッガ ⇄ デバッグ対象」という構成でデバッグできる。このうちの開発ツールとしてはDAPをサポートしているソフトウェアを使用できる。上述のVisual Studio Codeを使用した方法も実はリモートデバッグである。

ここではrdbgコマンドでコードを実行し、開始されたデバッガに外部から接続してみる。

デバッガを起動して接続待ちにする

シンプルな方法はrdbgコマンドに--openを指定して実行する。接続方法にはUNIXドメインソケットを使う方法と、TCPを使う方法がある。

$ bundle exec rdbg --open --sock-path /tmp/rdbg.sock --command -- ruby foo_test.rb
DEBUGGER: Debugger can attach via UNIX domain socket (/tmp/rdbg.sock)
Loaded suite foo_test
Started
DEBUGGER: wait for debugger connection...
$ bundle exec rdbg --open --port 12345 --command -- ruby foo_test.rb
DEBUGGER: Debugger can attach via TCP/IP (127.0.0.1:12345)
Loaded suite foo_test
Started
DEBUGGER: wait for debugger connection...

デバッグセッションがすでに開始しているときはrdbgのコマンドのopenを使用すると接続待ちにできる。

コード中にbinding.bなどを埋め込んでいるときは環境変数RUBY_DEBUG_OPENを設定しておくとその場で接続待ちとなる。

rdbgコマンドで接続する

接続方法に合わせて接続先を引数で指定する。

$ bundle exec rdbg --attach /tmp/rdbg.sock
$ bundle exec rdbg --attach 12345

開始されるデバッグセッションはリモートデバッグではない場合と変わりはない。

DEBUGGER (client): Connected. PID:72865, $0:foo_test.rb

[1, 10] in foo_test.rb
     1| # frozen_string_literal: true
     2| 
=>   3| require 'test/unit'
     4| require_relative 'foo'
     5| 
     6| class FooTest < Test::Unit::TestCase
     7|   test 'foo' do
     8|     foo = Foo.new
     9|     assert_equal :foo, foo.foo
    10|   end
=>#0    <main> at foo_test.rb:3
(rdbg:remote)

Visual Studio Codeで接続する

Visual Studio Codeから接続するときは、接続先を.vscode/launch.jsonで指定する。

上のUNIXドメインソケットの例に合わせると"name": "Attach with rdbg"のほうに"debugPort": "/tmp/rdbg.sock"を加える。

TCP接続する場合は"debugPort": "12345"を加え、さらに"localfs": trueも加える。(localfsの指定をしておかないと、Visual Studio Code上で設定したブレークポイントが無視される。)

そして「Attach with rdbg」を選択して「⏵」をクリックするとデバッグセッションが始まる。

なお、このときはデバッグ対象の起動直後に停止する(※2)ため事前にブレークポイントを設定していなくてもよい。

Neovimとの連携

nvim-dapはNeovim用のDAP実装で、デバッガとの標準入出力またはTCPでの接続をサポートしている。

標準入出力はruby/debugがサポートしていないと思うのでTCPで進める。

シンプルな設定

local dap = require('dap')

dap.adapters.ruby = function(callback, config)
  callback {
    type = 'server',
    port = '12345',
  }
end

dap.configurations.ruby = {
  {
    type = 'ruby',
    name = 'Attach with rdbg',
    request = 'attach',
    localfs = true, -- TCP接続のときは必要
  },
}

rdbg --openでTCP接続待ちにしておき、Neovimから:DapContinueを実行するとデバッグセッションが始まる。

nvim-dapからの接続

nvim-dapからの接続

この例の設定ではデバッグ対象の起動直後に停止するかどうかは実行中のrdbgのオプション指定に従って決まる。(※1)(※2)

デバッグ対象の起動直後に停止するかどうか

基本としてrdbgのオプションに--nonstopがあれば起動直後には停止せず、なければ起動直後に停止する。

リモートデバッグのときは、そこからさらに使用されるDAPリクエストによって動作が変わる。DAPリクエストはVisual Studio Codeやnvim-dapの設定の中のrequestによって決まる。この値がattachのときは停止するがlaunchのときは停止しない。

上述の(※1)で停止せず(※2)で停止するのはこの違いによる。

起動直後の停止については--stop-at-loadという別のオプションがあり、こちらはデバッグ機能が有効になった直後に動作を停止する。

Neovimで開いているファイルをデバッグする

Visual Studio Codeの雛型と同じように、開いているファイルを実行してデバッグできるようにするには以下のように設定する。

local dap = require('dap')

dap.adapters.ruby = function(callback, config)
  if config.request == 'attach' then
    callback {
      type = 'server',
      port = '12345',
    }
  else
    callback {
      type = 'server',
      port = '${port}',
      executable = {
        command = 'bundle',
        args = {
          'exec', 'rdbg', '--stop-at-load',
          '--open', '--host', '127.0.0.1', '--port', '${port}',
          '--command', '--', 'bundle', 'exec', config.command, config.script,
        },
      },
    }
  end
end

dap.configurations.ruby = {
  {
    type = 'ruby',
    name = 'Attach with rdbg',
    request = 'attach',
    localfs = true,
  },
  {
    type = 'ruby',
    name = 'Debug current file with rdbg',
    request = 'launch',
    localfs = true,
    command = 'ruby',
    script = '${file}',
  },
}

この設定の「Debug current file with rdbg」は、Visual Studio Codeの例と同じくブレークポイントでのみ停止する。そのため事前にブレークポイントを設定しておく。(またはコード中にbinding.bなどでブレークポイントを記述しておく。)

設定の選択

設定の選択

Bとあるのが設定したブレークポイント。

デバッグセッション開始

デバッグセッション開始

nvim-dap-ruby

ここでは設定を手書きしたが、環境によってはnvim-dap-rubyを利用できる。