DAPとdlvでデバッグする

Figure 1: dap-debug起動時の画面

Figure 1: dap-debug起動時の画面

Debug & Debugger

テストはプログラムのバグを 探す プロセスです。 デバッグは、何らかの方法で発見されたバグを 修正 するプロセスです。

DEBUG HACKS - 1章 デバッグとテストより

debug はその名の通りde-bug、バグを潰すことを示す。探す手段はテストでも何でもよくて、発見した後にバグを修正するプロセスのことをデバッグという。 debugger はバグを発見するための手段の一つだが、バグを発見する以外にも挙動のわからないプログラムを調べる際にも使われる。

余談だが、DEBUG HACKSは良い本なのでオススメ。

DAPとは

DAP1はDebug Adapter Protocolの略で、LSPをご存知の方ならLSPのデバッグ用プロトコルだという理解で問題ない2。 LSPのように、エディタごとにデバッグ支援プラグイン等を実装することなく、言語ごとに一つでも Debug Adapter を実装すれば、どのエディタからも同じようにデバッグ支援機能を利用できるというものだ。

Figure 2: 従来のデバッグツールとエディタの関係(左)、DAPを使った場合(右) [DAP Overviewより]​

Figure 2: 従来のデバッグツールとエディタの関係(左)、DAPを使った場合(右) [DAP Overviewより]​

Goはもちろんのこと、DebugAdapterはすでに多くの言語に対応している。実装済みのリストは公式サイトから参照してほしい3

GoとDebugAdapterのセットアップ

GoのDebugAdapterは golang/vscode-go 内に実装されている4。名前にはvscodeと入っているがEmacsや他のエディタからも利用可能だ。 例えば、Emacsではdap-mode5がこれをDebugAdapterとして利用している。

DelveにもDAPサーバとしての機能が実装されているが、2020-05-31現在では実験的な機能で、引数の設定や debug, test 以外の実行ができない。vscode-goでも、これをサポートするためのDbugAdapter2が追加されているがしばらく様子見で良さそうだ。

Emacsの設定

設定方法はCode 1の通り。Adapterのセットアップは自動化されているので、Emacsで dap-go-set を呼び出すだけでvscode-goのインストールが完了する。

(use-package dap-mode
  :after lsp-mode
  :custom
  (dap-auto-configure-features '(sessions locals breakpoints expressions repl controls tooltip))
  :config
  (dap-mode 1)
  (dap-auto-configure-mode 1)
  (require 'dap-hydra) ; hydraでDAPの操作を楽にするもの(Optional)
  (require 'dap-go))
Code 1: DAPの設定

OPTION GoのデバッガDelveのインストール

Goのデバッグツールであるdlv6がまだ入っていなければ入れていく。macOSの場合、単純にgo getしただけだと毎回権限チェックダイアログが表示されて面倒なので、Developer Modeを有効にするとよい。

$ go get github.com/go-delve/delve/cmd/dlv
$ sudo /usr/sbin/DevToolsSecurity -enable
Code 2: dlvコマンドのセットアップ

Goのプログラムをデバッグする

Figure 3: FringeをクリックしてBreakpointを設定する例

Figure 3: FringeをクリックしてBreakpointを設定する例

現在開いているファイルをデバッグする

  1. 動きを調査したい箇所にBreakpointを設定する
  2. M-xから dap-debug を呼び出すと、デバッグ設定を聞かれるので Go Launch File Configuration を選択する
  3. 画面上のNext/StepInボタンを使っても良いし、M-xから dap-hydra を使ってキーボードで操作しても良い

デバッグ設定を生成する

単純に開いているファイルをデバッグする方法だけではなく、リモートプログラムのデバッグだったり、テストのデバッグだったり、 ビルド引数や環境変数などを変更して実行することもできる。

VS CodeのGoデバッガのドキュメント7を見ると分かるように、さまざまな設定値がある。 このドキュメントを見ながら都度設定していってもいいのだが、少々面倒なのでインタラクティブな設定ファイルの自動生成コマンドを書く。

(defun go-debug-config-generator ()
  "Generate debug configuration for Go dap-mode."
  (interactive)
  (let ((tpl (list :type "go")))
    (plist-put tpl :request (if (y-or-n-p "[必須] デバッグ対象は起動中?")
                       "attach"
                     "launch"))
    (if (y-or-n-p "[必須] デバッグ対象はリモートにある?")
        (plist-put tpl
                   :mode "remote"
                   :host (read-string "[必須] リモートマシンのホスト: ")
                   :port (read-string "[必須] デバッグ対象のポート番号: ")
                   :remotePath (read-string "[必須] デバッグ対象の絶対パス: "))
      (if (eq (plist-get tpl :request) "attach")
          (plist-put tpl
                     :mode "local"
                     :processId (read-number "[必須] デバッグ対象のプロセスID: "))
        (if (y-or-n-p "[必須] デバッグ対象はテストですか?")
            (progn
              (plist-put tpl :mode "test")
              (let ((func (read-string "テスト関数の指定 (e.g. TestMyFunc) (default: \"\"): "))
                    (build (read-string "ビルドフラグの指定 (e.g. -tags fixtures) (default: \"\"): ")))
                (if (not (equal func "")) (plist-put tpl :args ("-test.run" func)))
                (if (not (equal build "")) (plist-put tpl :buildFlags (split-string build)))))
          (plist-put tpl :mode (if (y-or-n-p "[必須] デバッグ対象はソースコードですか?")
                                   "debug"
                                 "exec"))
          (let ((build (read-string "ビルドフラグの指定 (e.g. -tags fixtures) (default: \"\"): ")))
            (if (not (equal build "")) (plist-put tpl :buildFlags (split-string build)))))
        (let ((env (read-string "引数の設定 (e.g. (:env1 var :env2 var2)) (default: \"\"): ")))
          (if (not (equal env "")) (plist-put tpl :env (read env)))))
      (plist-put tpl :program (ivy-read "[必須] デバッグ対象のファイル(ディレクトリ): " 'read-file-name-internal
                                        :matcher #'counsel--find-file-matcher
                                        :action
                                        (lambda (x)
                                          (print x)))))
    (let ((name (read-string "[必須] 登録するデバッグ設定名: ")))
      (require 'dap-mode)
      (dap-register-debug-template name tpl)
      (message "Register go debug configuration as " name))))
Code 3: デバッグ設定ジェネレータ

このコマンドを実行して、インストラクション通りに質問に回答していくとデバッグ設定が登録される。 登録したデバッグ設定は、 (dap-debug) コマンドから呼び出せる。

.