DAPとdlvでデバッグする
Debug & Debugger
テストはプログラムのバグを 探す プロセスです。 デバッグは、何らかの方法で発見されたバグを 修正 するプロセスです。
debug はその名の通りde-bug、バグを潰すことを示す。探す手段はテストでも何でもよくて、発見した後にバグを修正するプロセスのことをデバッグという。 debugger はバグを発見するための手段の一つだが、バグを発見する以外にも挙動のわからないプログラムを調べる際にも使われる。
余談だが、DEBUG HACKSは良い本なのでオススメ。
DAPとは
DAP1はDebug Adapter Protocolの略で、LSPをご存知の方ならLSPのデバッグ用プロトコルだという理解で問題ない2。
LSPのように、エディタごとにデバッグ支援プラグイン等を実装することなく、言語ごとに一つでも Debug Adapter
を実装すれば、どのエディタからも同じようにデバッグ支援機能を利用できるというものだ。
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))
OPTION GoのデバッガDelveのインストール
Goのデバッグツールであるdlv6がまだ入っていなければ入れていく。macOSの場合、単純にgo getしただけだと毎回権限チェックダイアログが表示されて面倒なので、Developer Modeを有効にするとよい。
$ go get github.com/go-delve/delve/cmd/dlv
$ sudo /usr/sbin/DevToolsSecurity -enable
Goのプログラムをデバッグする
現在開いているファイルをデバッグする
- 動きを調査したい箇所にBreakpointを設定する
- M-xから
dap-debug
を呼び出すと、デバッグ設定を聞かれるのでGo Launch File Configuration
を選択する - 画面上の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))))
このコマンドを実行して、インストラクション通りに質問に回答していくとデバッグ設定が登録される。
登録したデバッグ設定は、 (dap-debug)
コマンドから呼び出せる。
.